diff --git a/src/camera-manager/browse-media/engine-browse-media.ts b/src/camera-manager/browse-media/engine-browse-media.ts index 599eed67..c4b46462 100644 --- a/src/camera-manager/browse-media/engine-browse-media.ts +++ b/src/camera-manager/browse-media/engine-browse-media.ts @@ -1,5 +1,5 @@ import { StateWatcherSubscriptionInterface } from '../../card-controller/hass/state-watcher'; -import { CameraConfig } from '../../config/types'; +import { CameraConfig } from '../../config/schema/cameras'; import { HomeAssistant } from '../../ha/types'; import { canonicalizeHAURL } from '../../utils/ha'; import { BrowseMediaManager } from '../../utils/ha/browse-media/browse-media-manager'; diff --git a/src/camera-manager/camera.ts b/src/camera-manager/camera.ts index 062536dc..0a3692a5 100644 --- a/src/camera-manager/camera.ts +++ b/src/camera-manager/camera.ts @@ -1,5 +1,5 @@ import { StateWatcherSubscriptionInterface } from '../card-controller/hass/state-watcher'; -import { CameraConfig } from '../config/types'; +import { CameraConfig } from '../config/schema/cameras'; import { localize } from '../localize/localize'; import { HassStateDifference, isTriggeredState } from '../utils/ha'; import { Capabilities } from './capabilities'; diff --git a/src/camera-manager/engine-factory.ts b/src/camera-manager/engine-factory.ts index 44934b21..b198f7c9 100644 --- a/src/camera-manager/engine-factory.ts +++ b/src/camera-manager/engine-factory.ts @@ -1,5 +1,5 @@ import { StateWatcherSubscriptionInterface } from '../card-controller/hass/state-watcher'; -import { CameraConfig } from '../config/types'; +import { CameraConfig } from '../config/schema/cameras'; import { HomeAssistant } from '../ha/types'; import { localize } from '../localize/localize'; import { BrowseMediaManager } from '../utils/ha/browse-media/browse-media-manager'; diff --git a/src/camera-manager/engine.ts b/src/camera-manager/engine.ts index 2f113a82..66952f8b 100644 --- a/src/camera-manager/engine.ts +++ b/src/camera-manager/engine.ts @@ -1,5 +1,5 @@ -import { PTZAction } from '../config/ptz'; -import { ActionPhase, CameraConfig } from '../config/types'; +import { PTZAction, PTZActionPhase } from '../config/schema/actions/custom/ptz'; +import { CameraConfig } from '../config/schema/cameras'; import { HomeAssistant } from '../ha/types'; import { ViewMedia } from '../view/media'; import { Camera } from './camera'; @@ -134,7 +134,7 @@ export interface CameraManagerEngine { cameraConfig: CameraConfig, action: PTZAction, options?: { - phase?: ActionPhase; + phase?: PTZActionPhase; preset?: string; }, ): Promise; diff --git a/src/camera-manager/frigate/camera.ts b/src/camera-manager/frigate/camera.ts index 4b515c85..90189871 100644 --- a/src/camera-manager/frigate/camera.ts +++ b/src/camera-manager/frigate/camera.ts @@ -1,6 +1,6 @@ import uniq from 'lodash-es/uniq'; import { StateWatcherSubscriptionInterface } from '../../card-controller/hass/state-watcher'; -import { CameraConfig } from '../../config/types'; +import { CameraConfig } from '../../config/schema/cameras'; import { HomeAssistant } from '../../ha/types'; import { localize } from '../../localize/localize'; import { PTZCapabilities, PTZMovementType } from '../../types'; diff --git a/src/camera-manager/frigate/engine-frigate.ts b/src/camera-manager/frigate/engine-frigate.ts index 73a1f65d..6dfe50a1 100644 --- a/src/camera-manager/frigate/engine-frigate.ts +++ b/src/camera-manager/frigate/engine-frigate.ts @@ -4,8 +4,8 @@ import orderBy from 'lodash-es/orderBy'; import throttle from 'lodash-es/throttle'; import uniqWith from 'lodash-es/uniqWith'; import { StateWatcherSubscriptionInterface } from '../../card-controller/hass/state-watcher'; -import { PTZAction } from '../../config/ptz'; -import { ActionPhase, CameraConfig } from '../../config/types'; +import { PTZAction, PTZActionPhase } from '../../config/schema/actions/custom/ptz'; +import { CameraConfig } from '../../config/schema/cameras'; import { HomeAssistant } from '../../ha/types'; import { allPromises, @@ -1011,7 +1011,7 @@ export class FrigateCameraManagerEngine cameraConfig: CameraConfig, action: PTZAction, options?: { - phase?: ActionPhase; + phase?: PTZActionPhase; preset?: string; }, ): Promise { diff --git a/src/camera-manager/frigate/media.ts b/src/camera-manager/frigate/media.ts index 8617b4a1..f6c5a154 100644 --- a/src/camera-manager/frigate/media.ts +++ b/src/camera-manager/frigate/media.ts @@ -1,6 +1,6 @@ import { fromUnixTime } from 'date-fns'; import isEqual from 'lodash-es/isEqual'; -import { CameraConfig } from '../../config/types'; +import { CameraConfig } from '../../config/schema/cameras'; import { EventViewMedia, RecordingViewMedia, diff --git a/src/camera-manager/frigate/util.ts b/src/camera-manager/frigate/util.ts index 8303aea8..0305b176 100644 --- a/src/camera-manager/frigate/util.ts +++ b/src/camera-manager/frigate/util.ts @@ -1,5 +1,5 @@ import { toZonedTime } from 'date-fns-tz'; -import { CameraConfig } from '../../config/types'; +import { CameraConfig } from '../../config/schema/cameras'; import { ClipsOrSnapshots } from '../../types'; import { formatDateAndTime, prettifyTitle } from '../../utils/basic'; import { FrigateEvent, FrigateRecording } from './types'; diff --git a/src/camera-manager/generic/engine-generic.ts b/src/camera-manager/generic/engine-generic.ts index b978d24b..ed71cf00 100644 --- a/src/camera-manager/generic/engine-generic.ts +++ b/src/camera-manager/generic/engine-generic.ts @@ -1,8 +1,8 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { StateWatcherSubscriptionInterface } from '../../card-controller/hass/state-watcher'; -import { PTZAction } from '../../config/ptz'; -import { ActionPhase, CameraConfig } from '../../config/types'; +import { PTZAction, PTZActionPhase } from '../../config/schema/actions/custom/ptz'; +import { CameraConfig } from '../../config/schema/cameras'; import { HomeAssistant } from '../../ha/types'; import { getEntityTitle } from '../../utils/ha'; import { ViewMedia } from '../../view/media'; @@ -241,7 +241,7 @@ export class GenericCameraManagerEngine implements CameraManagerEngine { _cameraConfig: CameraConfig, _action: PTZAction, _options?: { - phase?: ActionPhase; + phase?: PTZActionPhase; preset?: string; }, ): Promise { diff --git a/src/camera-manager/manager.ts b/src/camera-manager/manager.ts index 32a852fa..2788b690 100644 --- a/src/camera-manager/manager.ts +++ b/src/camera-manager/manager.ts @@ -3,8 +3,8 @@ import cloneDeep from 'lodash-es/cloneDeep'; import sum from 'lodash-es/sum'; import PQueue from 'p-queue'; import { CardCameraAPI } from '../card-controller/types.js'; -import { PTZAction } from '../config/ptz.js'; -import { ActionPhase, CameraConfig, CamerasConfig } from '../config/types.js'; +import { PTZAction, PTZActionPhase } from '../config/schema/actions/custom/ptz.js'; +import { CameraConfig, CamerasConfig } from '../config/schema/cameras.js'; import { MEDIA_CHUNK_SIZE_DEFAULT } from '../const.js'; import { localize } from '../localize/localize.js'; import { @@ -16,7 +16,6 @@ import { } from '../utils/basic.js'; import { getCameraID } from '../utils/camera.js'; import { log } from '../utils/debug.js'; -import { getConfiguredPTZAction } from './utils/ptz.js'; import { ViewMedia } from '../view/media.js'; import { Capabilities } from './capabilities.js'; import { CameraManagerEngineFactory } from './engine-factory.js'; @@ -56,6 +55,7 @@ import { RecordingSegmentsQueryResultsMap, ResultsMap, } from './types.js'; +import { getConfiguredPTZAction } from './utils/ptz.js'; import { sortMedia } from './utils/sort-media.js'; export class QueryClassifier { @@ -785,7 +785,7 @@ export class CameraManager { cameraID: string, action: PTZAction, options?: { - phase?: ActionPhase; + phase?: PTZActionPhase; preset?: string; }, ): Promise { diff --git a/src/camera-manager/motioneye/engine-motioneye.ts b/src/camera-manager/motioneye/engine-motioneye.ts index 2fc92480..c2965c30 100644 --- a/src/camera-manager/motioneye/engine-motioneye.ts +++ b/src/camera-manager/motioneye/engine-motioneye.ts @@ -1,6 +1,6 @@ import { add, endOfDay, parse, startOfDay } from 'date-fns'; import orderBy from 'lodash-es/orderBy'; -import { CameraConfig } from '../../config/types'; +import { CameraConfig } from '../../config/schema/cameras'; import { HomeAssistant } from '../../ha/types'; import { allPromises, formatDate, isValidDate } from '../../utils/basic'; import { diff --git a/src/camera-manager/reolink/engine-reolink.ts b/src/camera-manager/reolink/engine-reolink.ts index ec0b9d69..c398ac37 100644 --- a/src/camera-manager/reolink/engine-reolink.ts +++ b/src/camera-manager/reolink/engine-reolink.ts @@ -1,6 +1,6 @@ import { add, endOfDay, parse, startOfDay } from 'date-fns'; import { orderBy } from 'lodash-es'; -import { CameraConfig } from '../../config/types'; +import { CameraConfig } from '../../config/schema/cameras'; import { HomeAssistant } from '../../ha/types'; import { allPromises, formatDate, isValidDate } from '../../utils/basic'; import { sortMediaByStartDate } from '../../utils/ha/browse-media/browse-media-manager'; diff --git a/src/camera-manager/store.ts b/src/camera-manager/store.ts index ac662827..6127b4a9 100644 --- a/src/camera-manager/store.ts +++ b/src/camera-manager/store.ts @@ -1,4 +1,4 @@ -import { CameraConfig } from '../config/types'; +import { CameraConfig } from '../config/schema/cameras'; import { CapabilityKey } from '../types'; import { allPromises } from '../utils/basic'; import { ViewMedia } from '../view/media'; diff --git a/src/camera-manager/types.ts b/src/camera-manager/types.ts index 76ce8342..87677f3b 100644 --- a/src/camera-manager/types.ts +++ b/src/camera-manager/types.ts @@ -1,4 +1,5 @@ -import { AdvancedCameraCardView, SSLCiphers } from '../config/types'; +import { SSLCiphers } from '../config/schema/cameras'; +import { AdvancedCameraCardView } from '../config/schema/common/const'; import { CapabilityKey, Icon } from '../types'; import { ViewMedia } from '../view/media'; diff --git a/src/camera-manager/utils/camera-entity-from-config.ts b/src/camera-manager/utils/camera-entity-from-config.ts index b18109cc..4e35b7a2 100644 --- a/src/camera-manager/utils/camera-entity-from-config.ts +++ b/src/camera-manager/utils/camera-entity-from-config.ts @@ -1,4 +1,4 @@ -import { CameraConfig } from '../../config/types'; +import { CameraConfig } from '../../config/schema/cameras'; export const getCameraEntityFromConfig = ( cameraConfig?: CameraConfig, diff --git a/src/camera-manager/utils/go2rtc-endpoint.ts b/src/camera-manager/utils/go2rtc-endpoint.ts index 398211b4..e8e29332 100644 --- a/src/camera-manager/utils/go2rtc-endpoint.ts +++ b/src/camera-manager/utils/go2rtc-endpoint.ts @@ -1,4 +1,4 @@ -import { CameraConfig } from '../../config/types'; +import { CameraConfig } from '../../config/schema/cameras'; import { CameraEndpoint } from '../types'; export const getDefaultGo2RTCEndpoint = ( diff --git a/src/camera-manager/utils/ptz.ts b/src/camera-manager/utils/ptz.ts index 322eb675..74ecc796 100644 --- a/src/camera-manager/utils/ptz.ts +++ b/src/camera-manager/utils/ptz.ts @@ -1,12 +1,17 @@ -import { PTZAction, PTZBaseAction } from '../../config/ptz'; -import { ActionConfig, ActionPhase, CameraConfig } from '../../config/types'; +import { + PTZAction, + PTZActionPhase, + PTZBaseAction, +} from '../../config/schema/actions/custom/ptz'; +import { ActionConfig } from '../../config/schema/actions/types'; +import { CameraConfig } from '../../config/schema/cameras'; import { PTZCapabilities, PTZMovementType } from '../../types'; export const getConfiguredPTZAction = ( cameraConfig: CameraConfig, action: PTZAction, options?: { - phase?: ActionPhase; + phase?: PTZActionPhase; preset?: string; }, ): ActionConfig | ActionConfig[] | null => { @@ -25,7 +30,7 @@ const hasConfiguredPTZAction = ( cameraConfig: CameraConfig, action: PTZBaseAction, options?: { - phase?: ActionPhase; + phase?: PTZActionPhase; preset?: string; }, ): boolean => { diff --git a/src/card-controller/actions/actions-manager.ts b/src/card-controller/actions/actions-manager.ts index 9b27e83b..9d3b8000 100644 --- a/src/card-controller/actions/actions-manager.ts +++ b/src/card-controller/actions/actions-manager.ts @@ -6,7 +6,7 @@ import { Actions, ActionsConfig, AuxillaryActionConfig, -} from '../../config/types.js'; +} from '../../config/schema/actions/types.js'; import { forwardHaptic } from '../../ha/haptic.js'; import { getActionConfigGivenAction } from '../../utils/action.js'; import { allPromises } from '../../utils/basic.js'; diff --git a/src/card-controller/actions/actions/base.ts b/src/card-controller/actions/actions/base.ts index 78e2df54..d15be46a 100644 --- a/src/card-controller/actions/actions/base.ts +++ b/src/card-controller/actions/actions/base.ts @@ -1,5 +1,8 @@ import { ActionContext } from 'action'; -import { ActionConfig, AuxillaryActionConfig } from '../../../config/types'; +import { + ActionConfig, + AuxillaryActionConfig, +} from '../../../config/schema/actions/types.js'; import { localize } from '../../../localize/localize.js'; import { isAdvancedCameraCardCustomAction } from '../../../utils/action'; import { CardActionsAPI } from '../../types'; diff --git a/src/card-controller/actions/actions/call-service.ts b/src/card-controller/actions/actions/call-service.ts index 27b6a46e..d3ee3b40 100644 --- a/src/card-controller/actions/actions/call-service.ts +++ b/src/card-controller/actions/actions/call-service.ts @@ -1,4 +1,4 @@ -import { CallServiceActionConfig } from '../../../config/types'; +import { CallServiceActionConfig } from '../../../config/schema/actions/stock/call-service'; import { CardActionsAPI } from '../../types'; import { AdvancedCameraCardAction } from './base'; diff --git a/src/card-controller/actions/actions/camera-select.ts b/src/card-controller/actions/actions/camera-select.ts index 1136bc4c..1cc3afa4 100644 --- a/src/card-controller/actions/actions/camera-select.ts +++ b/src/card-controller/actions/actions/camera-select.ts @@ -1,4 +1,4 @@ -import { CameraSelectActionConfig } from '../../../config/types'; +import { CameraSelectActionConfig } from '../../../config/schema/actions/custom/camera-select'; import { CardActionsAPI } from '../../types'; import { AdvancedCameraCardAction } from './base'; diff --git a/src/card-controller/actions/actions/camera-ui.ts b/src/card-controller/actions/actions/camera-ui.ts index 803085d6..ebaaa9c5 100644 --- a/src/card-controller/actions/actions/camera-ui.ts +++ b/src/card-controller/actions/actions/camera-ui.ts @@ -1,4 +1,4 @@ -import { GeneralActionConfig } from '../../../config/types'; +import { GeneralActionConfig } from '../../../config/schema/actions/custom/general'; import { CardActionsAPI } from '../../types'; import { AdvancedCameraCardAction } from './base'; diff --git a/src/card-controller/actions/actions/custom.ts b/src/card-controller/actions/actions/custom.ts index a17c36de..75f1871e 100644 --- a/src/card-controller/actions/actions/custom.ts +++ b/src/card-controller/actions/actions/custom.ts @@ -1,4 +1,4 @@ -import { CustomActionConfig } from '../../../config/types'; +import { CustomActionConfig } from '../../../config/schema/actions/stock/custom'; import { fireHASSEvent } from '../../../ha/fire-hass-event'; import { CardActionsAPI } from '../../types'; import { AdvancedCameraCardAction } from './base'; diff --git a/src/card-controller/actions/actions/default.ts b/src/card-controller/actions/actions/default.ts index 3a04474e..835c4122 100644 --- a/src/card-controller/actions/actions/default.ts +++ b/src/card-controller/actions/actions/default.ts @@ -1,4 +1,4 @@ -import { GeneralActionConfig } from '../../../config/types'; +import { GeneralActionConfig } from '../../../config/schema/actions/custom/general'; import { CardActionsAPI } from '../../types'; import { AdvancedCameraCardAction } from './base'; diff --git a/src/card-controller/actions/actions/display-mode-select.ts b/src/card-controller/actions/actions/display-mode-select.ts index 490b2022..0d094b82 100644 --- a/src/card-controller/actions/actions/display-mode-select.ts +++ b/src/card-controller/actions/actions/display-mode-select.ts @@ -1,4 +1,4 @@ -import { DisplayModeActionConfig } from '../../../config/types'; +import { DisplayModeActionConfig } from '../../../config/schema/actions/custom/display-mode'; import { CardActionsAPI } from '../../types'; import { AdvancedCameraCardAction } from './base'; diff --git a/src/card-controller/actions/actions/download.ts b/src/card-controller/actions/actions/download.ts index ac8c5558..1cb23fa7 100644 --- a/src/card-controller/actions/actions/download.ts +++ b/src/card-controller/actions/actions/download.ts @@ -1,4 +1,4 @@ -import { GeneralActionConfig } from '../../../config/types'; +import { GeneralActionConfig } from '../../../config/schema/actions/custom/general'; import { CardActionsAPI } from '../../types'; import { AdvancedCameraCardAction } from './base'; diff --git a/src/card-controller/actions/actions/expand.ts b/src/card-controller/actions/actions/expand.ts index 6431297d..b163baec 100644 --- a/src/card-controller/actions/actions/expand.ts +++ b/src/card-controller/actions/actions/expand.ts @@ -1,4 +1,4 @@ -import { GeneralActionConfig } from '../../../config/types'; +import { GeneralActionConfig } from '../../../config/schema/actions/custom/general'; import { CardActionsAPI } from '../../types'; import { AdvancedCameraCardAction } from './base'; diff --git a/src/card-controller/actions/actions/fullscreen.ts b/src/card-controller/actions/actions/fullscreen.ts index e2cdc03f..75e68bfd 100644 --- a/src/card-controller/actions/actions/fullscreen.ts +++ b/src/card-controller/actions/actions/fullscreen.ts @@ -1,4 +1,4 @@ -import { GeneralActionConfig } from '../../../config/types'; +import { GeneralActionConfig } from '../../../config/schema/actions/custom/general'; import { CardActionsAPI } from '../../types'; import { AdvancedCameraCardAction } from './base'; diff --git a/src/card-controller/actions/actions/internal-callback.ts b/src/card-controller/actions/actions/internal-callback.ts index 19479d40..a13f87a3 100644 --- a/src/card-controller/actions/actions/internal-callback.ts +++ b/src/card-controller/actions/actions/internal-callback.ts @@ -1,4 +1,4 @@ -import { InternalCallbackActionConfig } from '../../../config/types'; +import { InternalCallbackActionConfig } from '../../../config/schema/actions/custom/internal'; import { CardActionsAPI } from '../../types'; import { AdvancedCameraCardAction } from './base'; diff --git a/src/card-controller/actions/actions/log.ts b/src/card-controller/actions/actions/log.ts index fcea66a9..0f42f626 100644 --- a/src/card-controller/actions/actions/log.ts +++ b/src/card-controller/actions/actions/log.ts @@ -1,4 +1,4 @@ -import { LogActionConfig } from '../../../config/types'; +import { LogActionConfig } from '../../../config/schema/actions/custom/log'; import { CardActionsAPI } from '../../types'; import { AdvancedCameraCardAction } from './base'; diff --git a/src/card-controller/actions/actions/media-player.ts b/src/card-controller/actions/actions/media-player.ts index 1bb699ae..d624f9b5 100644 --- a/src/card-controller/actions/actions/media-player.ts +++ b/src/card-controller/actions/actions/media-player.ts @@ -1,4 +1,4 @@ -import { MediaPlayerActionConfig } from '../../../config/types'; +import { MediaPlayerActionConfig } from '../../../config/schema/actions/custom/media-player'; import { getStreamCameraID } from '../../../utils/substream'; import { CardActionsAPI } from '../../types'; import { AdvancedCameraCardAction } from './base'; diff --git a/src/card-controller/actions/actions/menu-toggle.ts b/src/card-controller/actions/actions/menu-toggle.ts index 53fd9cdb..c8243e66 100644 --- a/src/card-controller/actions/actions/menu-toggle.ts +++ b/src/card-controller/actions/actions/menu-toggle.ts @@ -1,4 +1,4 @@ -import { GeneralActionConfig } from '../../../config/types'; +import { GeneralActionConfig } from '../../../config/schema/actions/custom/general'; import { CardActionsAPI } from '../../types'; import { AdvancedCameraCardAction } from './base'; diff --git a/src/card-controller/actions/actions/microphone-connect.ts b/src/card-controller/actions/actions/microphone-connect.ts index 219dafd8..10313c08 100644 --- a/src/card-controller/actions/actions/microphone-connect.ts +++ b/src/card-controller/actions/actions/microphone-connect.ts @@ -1,4 +1,4 @@ -import { GeneralActionConfig } from '../../../config/types'; +import { GeneralActionConfig } from '../../../config/schema/actions/custom/general'; import { CardActionsAPI } from '../../types'; import { AdvancedCameraCardAction } from './base'; diff --git a/src/card-controller/actions/actions/microphone-disconnect.ts b/src/card-controller/actions/actions/microphone-disconnect.ts index 872d4982..2a11f0f4 100644 --- a/src/card-controller/actions/actions/microphone-disconnect.ts +++ b/src/card-controller/actions/actions/microphone-disconnect.ts @@ -1,4 +1,4 @@ -import { GeneralActionConfig } from '../../../config/types'; +import { GeneralActionConfig } from '../../../config/schema/actions/custom/general'; import { CardActionsAPI } from '../../types'; import { AdvancedCameraCardAction } from './base'; diff --git a/src/card-controller/actions/actions/microphone-mute.ts b/src/card-controller/actions/actions/microphone-mute.ts index e2a64e14..36be6ea2 100644 --- a/src/card-controller/actions/actions/microphone-mute.ts +++ b/src/card-controller/actions/actions/microphone-mute.ts @@ -1,4 +1,4 @@ -import { GeneralActionConfig } from '../../../config/types'; +import { GeneralActionConfig } from '../../../config/schema/actions/custom/general'; import { CardActionsAPI } from '../../types'; import { AdvancedCameraCardAction } from './base'; diff --git a/src/card-controller/actions/actions/microphone-unmute.ts b/src/card-controller/actions/actions/microphone-unmute.ts index 7a958a06..14cc6f32 100644 --- a/src/card-controller/actions/actions/microphone-unmute.ts +++ b/src/card-controller/actions/actions/microphone-unmute.ts @@ -1,4 +1,4 @@ -import { GeneralActionConfig } from '../../../config/types'; +import { GeneralActionConfig } from '../../../config/schema/actions/custom/general'; import { CardActionsAPI } from '../../types'; import { AdvancedCameraCardAction } from './base'; diff --git a/src/card-controller/actions/actions/more-info.ts b/src/card-controller/actions/actions/more-info.ts index 7311f8dd..3145626b 100644 --- a/src/card-controller/actions/actions/more-info.ts +++ b/src/card-controller/actions/actions/more-info.ts @@ -1,4 +1,4 @@ -import { MoreInfoActionConfig } from '../../../config/types'; +import { MoreInfoActionConfig } from '../../../config/schema/actions/stock/more-info'; import { fireHASSEvent } from '../../../ha/fire-hass-event'; import { CardActionsAPI } from '../../types'; import { AdvancedCameraCardAction } from './base'; diff --git a/src/card-controller/actions/actions/mute.ts b/src/card-controller/actions/actions/mute.ts index 062917c2..bd34f305 100644 --- a/src/card-controller/actions/actions/mute.ts +++ b/src/card-controller/actions/actions/mute.ts @@ -1,4 +1,4 @@ -import { GeneralActionConfig } from '../../../config/types'; +import { GeneralActionConfig } from '../../../config/schema/actions/custom/general'; import { CardActionsAPI } from '../../types'; import { AdvancedCameraCardAction } from './base'; diff --git a/src/card-controller/actions/actions/navigate.ts b/src/card-controller/actions/actions/navigate.ts index 0ad085de..ac9649af 100644 --- a/src/card-controller/actions/actions/navigate.ts +++ b/src/card-controller/actions/actions/navigate.ts @@ -1,4 +1,4 @@ -import { NavigateActionConfig } from '../../../config/types'; +import { NavigateActionConfig } from '../../../config/schema/actions/stock/navigate'; import { fireHASSEvent } from '../../../ha/fire-hass-event'; import { CardActionsAPI } from '../../types'; import { AdvancedCameraCardAction } from './base'; diff --git a/src/card-controller/actions/actions/none.ts b/src/card-controller/actions/actions/none.ts index 6a89d647..c07f4033 100644 --- a/src/card-controller/actions/actions/none.ts +++ b/src/card-controller/actions/actions/none.ts @@ -1,4 +1,4 @@ -import { NoneActionConfig } from '../../../config/types'; +import { NoneActionConfig } from '../../../config/schema/actions/stock/none'; import { CardActionsAPI } from '../../types'; import { AdvancedCameraCardAction } from './base'; diff --git a/src/card-controller/actions/actions/pause.ts b/src/card-controller/actions/actions/pause.ts index f3b4fdd6..e9f2368c 100644 --- a/src/card-controller/actions/actions/pause.ts +++ b/src/card-controller/actions/actions/pause.ts @@ -1,4 +1,4 @@ -import { GeneralActionConfig } from '../../../config/types'; +import { GeneralActionConfig } from '../../../config/schema/actions/custom/general'; import { CardActionsAPI } from '../../types'; import { AdvancedCameraCardAction } from './base'; diff --git a/src/card-controller/actions/actions/perform-action.ts b/src/card-controller/actions/actions/perform-action.ts index f808eb53..cc784fa8 100644 --- a/src/card-controller/actions/actions/perform-action.ts +++ b/src/card-controller/actions/actions/perform-action.ts @@ -1,4 +1,4 @@ -import { PerformActionActionConfig } from '../../../config/types'; +import { PerformActionActionConfig } from '../../../config/schema/actions/stock/perform-action'; import { CardActionsAPI } from '../../types'; import { AdvancedCameraCardAction } from './base'; diff --git a/src/card-controller/actions/actions/play.ts b/src/card-controller/actions/actions/play.ts index 93e02081..7dcdb115 100644 --- a/src/card-controller/actions/actions/play.ts +++ b/src/card-controller/actions/actions/play.ts @@ -1,4 +1,4 @@ -import { GeneralActionConfig } from '../../../config/types'; +import { GeneralActionConfig } from '../../../config/schema/actions/custom/general'; import { CardActionsAPI } from '../../types'; import { AdvancedCameraCardAction } from './base'; diff --git a/src/card-controller/actions/actions/ptz-controls.ts b/src/card-controller/actions/actions/ptz-controls.ts index 34292ab8..ef8a465a 100644 --- a/src/card-controller/actions/actions/ptz-controls.ts +++ b/src/card-controller/actions/actions/ptz-controls.ts @@ -1,4 +1,4 @@ -import { PTZControlsActionConfig } from '../../../config/types'; +import { PTZControlsActionConfig } from '../../../config/schema/actions/custom/ptz-controls'; import { CardActionsAPI } from '../../types'; import { AdvancedCameraCardAction } from './base'; diff --git a/src/card-controller/actions/actions/ptz-digital.ts b/src/card-controller/actions/actions/ptz-digital.ts index 52d6bb20..e15b8b38 100644 --- a/src/card-controller/actions/actions/ptz-digital.ts +++ b/src/card-controller/actions/actions/ptz-digital.ts @@ -6,7 +6,8 @@ import { ZOOM_DEFAULT_SCALE, } from '../../../components-lib/zoom/types'; import { generateViewContextForZoom } from '../../../components-lib/zoom/zoom-view-context'; -import { PTZDigitialActionConfig, ZOOM_MAX, ZOOM_MIN } from '../../../config/types'; +import { PTZDigitialActionConfig } from '../../../config/schema/actions/custom/ptz-digital'; +import { ZOOM_MAX, ZOOM_MIN } from '../../../config/schema/common/zoom'; import { getPTZTarget } from '../../../utils/ptz'; import { Timer } from '../../../utils/timer'; import { CardActionsAPI } from '../../types'; diff --git a/src/card-controller/actions/actions/ptz-multi.ts b/src/card-controller/actions/actions/ptz-multi.ts index 025aba0d..16f1b9b6 100644 --- a/src/card-controller/actions/actions/ptz-multi.ts +++ b/src/card-controller/actions/actions/ptz-multi.ts @@ -1,4 +1,4 @@ -import { PTZMultiActionConfig } from '../../../config/types'; +import { PTZMultiActionConfig } from '../../../config/schema/actions/custom/ptz-multi'; import { createPTZAction, createPTZDigitalAction } from '../../../utils/action'; import { PTZType, getPTZTarget, hasCameraTruePTZ } from '../../../utils/ptz'; import { CardActionsAPI } from '../../types'; diff --git a/src/card-controller/actions/actions/ptz.ts b/src/card-controller/actions/actions/ptz.ts index 349f7789..8ecf0317 100644 --- a/src/card-controller/actions/actions/ptz.ts +++ b/src/card-controller/actions/actions/ptz.ts @@ -1,4 +1,4 @@ -import { PTZActionConfig } from '../../../config/types'; +import { PTZActionConfig } from '../../../config/schema/actions/custom/ptz'; import { getPTZTarget, ptzActionToCapabilityKey } from '../../../utils/ptz'; import { Timer } from '../../../utils/timer'; import { CardActionsAPI } from '../../types'; diff --git a/src/card-controller/actions/actions/screenshot.ts b/src/card-controller/actions/actions/screenshot.ts index 6ab397c5..c7d401bf 100644 --- a/src/card-controller/actions/actions/screenshot.ts +++ b/src/card-controller/actions/actions/screenshot.ts @@ -1,4 +1,4 @@ -import { GeneralActionConfig } from '../../../config/types'; +import { GeneralActionConfig } from '../../../config/schema/actions/custom/general'; import { CardActionsAPI } from '../../types'; import { AdvancedCameraCardAction } from './base'; diff --git a/src/card-controller/actions/actions/set.ts b/src/card-controller/actions/actions/set.ts index 4db35963..dac84207 100644 --- a/src/card-controller/actions/actions/set.ts +++ b/src/card-controller/actions/actions/set.ts @@ -1,5 +1,8 @@ import { ActionContext } from 'action'; -import { ActionConfig, AuxillaryActionConfig } from '../../../config/types'; +import { + ActionConfig, + AuxillaryActionConfig, +} from '../../../config/schema/actions/types'; import { arrayify } from '../../../utils/basic'; import { CardActionsAPI } from '../../types'; import { ActionFactory } from '../factory'; diff --git a/src/card-controller/actions/actions/sleep.ts b/src/card-controller/actions/actions/sleep.ts index 9fe85fc6..69f6a9c0 100644 --- a/src/card-controller/actions/actions/sleep.ts +++ b/src/card-controller/actions/actions/sleep.ts @@ -1,4 +1,4 @@ -import { SleepActionConfig } from '../../../config/types'; +import { SleepActionConfig } from '../../../config/schema/actions/custom/sleep'; import { sleep } from '../../../utils/basic'; import { CardActionsAPI } from '../../types'; import { timeDeltaToSeconds } from '../utils/time-delta'; diff --git a/src/card-controller/actions/actions/status-bar.ts b/src/card-controller/actions/actions/status-bar.ts index c9fccea3..3d1a455e 100644 --- a/src/card-controller/actions/actions/status-bar.ts +++ b/src/card-controller/actions/actions/status-bar.ts @@ -1,4 +1,4 @@ -import { StatusBarActionConfig } from '../../../config/types'; +import { StatusBarActionConfig } from '../../../config/schema/actions/types'; import { CardActionsAPI } from '../../types'; import { AdvancedCameraCardAction } from './base'; diff --git a/src/card-controller/actions/actions/substream-off.ts b/src/card-controller/actions/actions/substream-off.ts index b3637884..f31837ac 100644 --- a/src/card-controller/actions/actions/substream-off.ts +++ b/src/card-controller/actions/actions/substream-off.ts @@ -1,4 +1,4 @@ -import { GeneralActionConfig } from '../../../config/types'; +import { GeneralActionConfig } from '../../../config/schema/actions/custom/general'; import { CardActionsAPI } from '../../types'; import { SubstreamOffViewModifier } from '../../view/modifiers/substream-off'; import { AdvancedCameraCardAction } from './base'; diff --git a/src/card-controller/actions/actions/substream-on.ts b/src/card-controller/actions/actions/substream-on.ts index 2864f1a8..8618acc1 100644 --- a/src/card-controller/actions/actions/substream-on.ts +++ b/src/card-controller/actions/actions/substream-on.ts @@ -1,4 +1,4 @@ -import { GeneralActionConfig } from '../../../config/types'; +import { GeneralActionConfig } from '../../../config/schema/actions/custom/general'; import { CardActionsAPI } from '../../types'; import { SubstreamOnViewModifier } from '../../view/modifiers/substream-on'; import { AdvancedCameraCardAction } from './base'; diff --git a/src/card-controller/actions/actions/substream-select.ts b/src/card-controller/actions/actions/substream-select.ts index efbbf57e..cb41ce82 100644 --- a/src/card-controller/actions/actions/substream-select.ts +++ b/src/card-controller/actions/actions/substream-select.ts @@ -1,4 +1,4 @@ -import { SubstreamSelectActionConfig } from '../../../config/types'; +import { SubstreamSelectActionConfig } from '../../../config/schema/actions/custom/substream-select'; import { CardActionsAPI } from '../../types'; import { SubstreamSelectViewModifier } from '../../view/modifiers/substream-select'; import { AdvancedCameraCardAction } from './base'; diff --git a/src/card-controller/actions/actions/toggle.ts b/src/card-controller/actions/actions/toggle.ts index a5276709..f41e857d 100644 --- a/src/card-controller/actions/actions/toggle.ts +++ b/src/card-controller/actions/actions/toggle.ts @@ -1,4 +1,4 @@ -import { ToggleActionConfig } from '../../../config/types'; +import { ToggleActionConfig } from '../../../config/schema/actions/stock/toggle'; import { computeDomain } from '../../../ha/compute-domain'; import { STATES_OFF } from '../../../ha/const'; import { CardActionsAPI } from '../../types'; diff --git a/src/card-controller/actions/actions/unmute.ts b/src/card-controller/actions/actions/unmute.ts index 1f5d03f5..e193a7e4 100644 --- a/src/card-controller/actions/actions/unmute.ts +++ b/src/card-controller/actions/actions/unmute.ts @@ -1,4 +1,4 @@ -import { GeneralActionConfig } from '../../../config/types'; +import { GeneralActionConfig } from '../../../config/schema/actions/custom/general'; import { CardActionsAPI } from '../../types'; import { AdvancedCameraCardAction } from './base'; diff --git a/src/card-controller/actions/actions/url.ts b/src/card-controller/actions/actions/url.ts index 5440f32b..3f09f5a5 100644 --- a/src/card-controller/actions/actions/url.ts +++ b/src/card-controller/actions/actions/url.ts @@ -1,4 +1,4 @@ -import { URLActionConfig } from '../../../config/types'; +import { URLActionConfig } from '../../../config/schema/actions/stock/url'; import { CardActionsAPI } from '../../types'; import { AdvancedCameraCardAction } from './base'; diff --git a/src/card-controller/actions/actions/view.ts b/src/card-controller/actions/actions/view.ts index 7869ae4b..f7156fe1 100644 --- a/src/card-controller/actions/actions/view.ts +++ b/src/card-controller/actions/actions/view.ts @@ -1,4 +1,4 @@ -import { ViewActionConfig } from '../../../config/types'; +import { ViewActionConfig } from '../../../config/schema/actions/custom/view'; import { CardActionsAPI } from '../../types'; import { AdvancedCameraCardAction } from './base'; diff --git a/src/card-controller/actions/factory.ts b/src/card-controller/actions/factory.ts index 76b87b8c..d9b7a716 100644 --- a/src/card-controller/actions/factory.ts +++ b/src/card-controller/actions/factory.ts @@ -1,9 +1,6 @@ import { ActionContext } from 'action'; -import { - ActionConfig, - AuxillaryActionConfig, - INTERNAL_CALLBACK_ACTION, -} from '../../config/types'; +import { INTERNAL_CALLBACK_ACTION } from '../../config/schema/actions/custom/internal'; +import { ActionConfig, AuxillaryActionConfig } from '../../config/schema/actions/types'; import { isAdvancedCameraCardCustomAction } from '../../utils/action'; import { CallServiceAction } from './actions/call-service'; import { CameraSelectAction } from './actions/camera-select'; diff --git a/src/card-controller/actions/types.ts b/src/card-controller/actions/types.ts index 5abbffaf..3fac4a09 100644 --- a/src/card-controller/actions/types.ts +++ b/src/card-controller/actions/types.ts @@ -1,5 +1,8 @@ +import { + ActionConfig, + AuxillaryActionConfig, +} from '../../config/schema/actions/types.js'; import { AdvancedCameraCardError } from '../../types.js'; -import { ActionConfig, AuxillaryActionConfig } from '../../config/types'; import { CardActionsAPI } from '../types'; export interface Action { diff --git a/src/card-controller/actions/utils/time-delta.ts b/src/card-controller/actions/utils/time-delta.ts index ac064acd..c7ffd8c8 100644 --- a/src/card-controller/actions/utils/time-delta.ts +++ b/src/card-controller/actions/utils/time-delta.ts @@ -1,4 +1,4 @@ -import { TimeDelta } from '../../../config/types'; +import { TimeDelta } from '../../../config/schema/actions/custom/sleep'; export const timeDeltaToSeconds = (timeDelta: TimeDelta): number => { return ( diff --git a/src/card-controller/automations-manager.ts b/src/card-controller/automations-manager.ts index 67635cd6..be39fab5 100644 --- a/src/card-controller/automations-manager.ts +++ b/src/card-controller/automations-manager.ts @@ -1,6 +1,6 @@ import { ConditionsManager } from '../conditions/conditions-manager.js'; import { ConditionsEvaluationResult } from '../conditions/types.js'; -import { Automation, AutomationActions } from '../config/types.js'; +import { Automation, AutomationActions } from '../config/schema/automations.js'; import { localize } from '../localize/localize.js'; import { CardAutomationsAPI, TaggedAutomation } from './types.js'; diff --git a/src/card-controller/config/config-manager.ts b/src/card-controller/config/config-manager.ts index 3d71e49d..3c0638c9 100644 --- a/src/card-controller/config/config-manager.ts +++ b/src/card-controller/config/config-manager.ts @@ -1,13 +1,13 @@ import isEqual from 'lodash-es/isEqual'; import { ConditionsManager } from '../../conditions/conditions-manager.js'; import { isConfigUpgradeable } from '../../config/management.js'; -import { setProfiles } from '../../config/profiles/index.js'; +import { setProfiles } from '../../config/profiles/set-profiles.js'; import { AdvancedCameraCardConfig, - advancedCameraCardConfigSchema, CardWideConfig, - RawAdvancedCameraCardConfig, -} from '../../config/types.js'; + advancedCameraCardConfigSchema, +} from '../../config/schema/types.js'; +import { RawAdvancedCameraCardConfig } from '../../config/types.js'; import { localize } from '../../localize/localize.js'; import { getParseErrorPaths } from '../../utils/zod.js'; import { InitializationAspect } from '../initialization-manager.js'; diff --git a/src/card-controller/config/get-overridden-config.ts b/src/card-controller/config/get-overridden-config.ts index 5ad7a0fe..0a273fb7 100644 --- a/src/card-controller/config/get-overridden-config.ts +++ b/src/card-controller/config/get-overridden-config.ts @@ -7,7 +7,8 @@ import { getConfigValue, setConfigValue, } from '../../config/management'; -import { Overrides, RawAdvancedCameraCardConfig } from '../../config/types'; +import { Overrides } from '../../config/schema/overrides'; +import { RawAdvancedCameraCardConfig } from '../../config/types'; import { localize } from '../../localize/localize'; import { AdvancedCameraCardError } from '../../types'; import { desparsifyArrays } from '../../utils/basic'; diff --git a/src/card-controller/config/load-keyboard-shortcuts.ts b/src/card-controller/config/load-keyboard-shortcuts.ts index 4be3f673..8077f2b6 100644 --- a/src/card-controller/config/load-keyboard-shortcuts.ts +++ b/src/card-controller/config/load-keyboard-shortcuts.ts @@ -1,8 +1,5 @@ -import { - KeyboardShortcuts, - PTZKeyboardShortcutName, -} from '../../config/keyboard-shortcuts'; -import { PTZAction } from '../../config/ptz'; +import { PTZAction } from '../../config/schema/actions/custom/ptz'; +import { KeyboardShortcuts, PTZKeyboardShortcutName } from '../../config/schema/view'; import { createPTZMultiAction } from '../../utils/action'; import { CardConfigLoaderAPI, TaggedAutomation } from '../types'; diff --git a/src/card-controller/controller.ts b/src/card-controller/controller.ts index 3b911163..d2efcb77 100644 --- a/src/card-controller/controller.ts +++ b/src/card-controller/controller.ts @@ -1,7 +1,7 @@ import { ReactiveController } from 'lit'; import { CameraManager } from '../camera-manager/manager'; import { ConditionStateManager } from '../conditions/state-manager'; -import { AdvancedCameraCardConfig } from '../config/types'; +import { AdvancedCameraCardConfig } from '../config/schema/types'; import { LovelaceCardEditor } from '../ha/types'; import { createDeviceRegistryCache, diff --git a/src/card-controller/default-manager.ts b/src/card-controller/default-manager.ts index 0d19880a..a56d9f93 100644 --- a/src/card-controller/default-manager.ts +++ b/src/card-controller/default-manager.ts @@ -1,5 +1,5 @@ import isEqual from 'lodash-es/isEqual'; -import { AdvancedCameraCardConfig } from '../config/types'; +import { AdvancedCameraCardConfig } from '../config/schema/types'; import { createGeneralAction } from '../utils/action'; import { isActionAllowedBasedOnInteractionState } from '../utils/interaction-mode'; import { Timer } from '../utils/timer'; diff --git a/src/card-controller/media-player-manager.ts b/src/card-controller/media-player-manager.ts index 3645f3d8..c0a6c23f 100644 --- a/src/card-controller/media-player-manager.ts +++ b/src/card-controller/media-player-manager.ts @@ -1,4 +1,5 @@ -import { AdvancedCameraCardConfig, CameraConfig } from '../config/types'; +import { CameraConfig } from '../config/schema/cameras'; +import { AdvancedCameraCardConfig } from '../config/schema/types'; import { MEDIA_PLAYER_SUPPORT_BROWSE_MEDIA, MEDIA_PLAYER_SUPPORT_STOP, diff --git a/src/card-controller/query-string-manager.ts b/src/card-controller/query-string-manager.ts index d7f846d7..fe8affa4 100644 --- a/src/card-controller/query-string-manager.ts +++ b/src/card-controller/query-string-manager.ts @@ -1,4 +1,5 @@ -import { AdvancedCameraCardCustomActionConfig, ViewActionConfig } from '../config/types'; +import { ViewActionConfig } from '../config/schema/actions/custom/view'; +import { AdvancedCameraCardCustomActionConfig } from '../config/schema/actions/types'; import { createCameraAction, createGeneralAction, diff --git a/src/card-controller/status-bar-item-manager.ts b/src/card-controller/status-bar-item-manager.ts index d2c9a0bd..4d27f016 100644 --- a/src/card-controller/status-bar-item-manager.ts +++ b/src/card-controller/status-bar-item-manager.ts @@ -1,6 +1,7 @@ import isEqual from 'lodash-es/isEqual'; import { CameraManager } from '../camera-manager/manager'; -import { StatusBarConfig, StatusBarItem } from '../config/types'; +import { StatusBarItem } from '../config/schema/actions/types'; +import { StatusBarConfig } from '../config/schema/status-bar'; import { MediaLoadedInfo } from '../types'; import { View } from '../view/view'; import { CardStatusBarAPI } from './types'; diff --git a/src/card-controller/style-manager.ts b/src/card-controller/style-manager.ts index fcba0e3c..8df563b4 100644 --- a/src/card-controller/style-manager.ts +++ b/src/card-controller/style-manager.ts @@ -1,10 +1,6 @@ import { StyleInfo } from 'lit/directives/style-map'; -import { - AdvancedCameraCardConfig, - configDefaults, - ThemeConfig, - ThemeName, -} from '../config/types'; +import { AdvancedCameraCardConfig, configDefaults } from '../config/schema/types'; +import { ThemeConfig, ThemeName } from '../config/schema/view'; import { aspectRatioToStyle, setOrRemoveAttribute } from '../utils/basic'; import { View } from '../view/view'; import { CardStyleAPI } from './types'; diff --git a/src/card-controller/types.ts b/src/card-controller/types.ts index 0e17b62e..712f19a8 100644 --- a/src/card-controller/types.ts +++ b/src/card-controller/types.ts @@ -1,6 +1,6 @@ import type { CameraManager } from '../camera-manager/manager'; import type { ConditionStateManager } from '../conditions/state-manager'; -import type { Automation } from '../config/types'; +import type { Automation } from '../config/schema/automations'; import type { EntityRegistryManager } from '../utils/ha/registry/entity'; import type { ResolvedMediaCache } from '../utils/ha/resolved-media'; import type { ActionsManager } from './actions/actions-manager'; diff --git a/src/card-controller/view/factory.ts b/src/card-controller/view/factory.ts index 828d2044..962f0eed 100644 --- a/src/card-controller/view/factory.ts +++ b/src/card-controller/view/factory.ts @@ -1,9 +1,6 @@ -import { - VIEW_DEFAULT, - AdvancedCameraCardConfig, - AdvancedCameraCardView, - ViewDisplayMode, -} from '../../config/types'; +import { AdvancedCameraCardView, VIEW_DEFAULT } from '../../config/schema/common/const'; +import { ViewDisplayMode } from '../../config/schema/common/display'; +import { AdvancedCameraCardConfig } from '../../config/schema/types'; import { localize } from '../../localize/localize'; import { View, ViewParameters } from '../../view/view'; import { getCameraIDsForViewName } from '../../view/view-to-cameras'; diff --git a/src/card-controller/view/types.ts b/src/card-controller/view/types.ts index b9e449ea..174e663c 100644 --- a/src/card-controller/view/types.ts +++ b/src/card-controller/view/types.ts @@ -1,5 +1,5 @@ import { ViewContext } from 'view'; -import { AdvancedCameraCardView } from '../../config/types.js'; +import { AdvancedCameraCardView } from '../../config/schema/common/const.js'; import { AdvancedCameraCardError } from '../../types.js'; import { MediaQueriesResults } from '../../view/media-queries-results.js'; import { MediaQueries } from '../../view/media-queries.js'; diff --git a/src/card-controller/view/view-manager.ts b/src/card-controller/view/view-manager.ts index cfc8c881..64b048c6 100644 --- a/src/card-controller/view/view-manager.ts +++ b/src/card-controller/view/view-manager.ts @@ -1,5 +1,5 @@ import { ViewContext } from 'view'; -import { AdvancedCameraCardView } from '../../config/types'; +import { AdvancedCameraCardView } from '../../config/schema/common/const'; import { log } from '../../utils/debug'; import { getStreamCameraID } from '../../utils/substream'; import { MediaQueriesClassifier } from '../../view/media-queries-classifier'; @@ -7,6 +7,7 @@ import { View } from '../../view/view'; import { getCameraIDsForViewName } from '../../view/view-to-cameras'; import { CardViewAPI } from '../types'; import { ViewFactory } from './factory'; +import { applyViewModifiers } from './modifiers'; import { QueryExecutorOptions, ViewFactoryOptions, @@ -15,7 +16,6 @@ import { ViewModifier, } from './types'; import { ViewQueryExecutor } from './view-query-executor'; -import { applyViewModifiers } from './modifiers'; export class ViewManager implements ViewManagerInterface { protected _view: View | null = null; diff --git a/src/card.ts b/src/card.ts index ce2531ff..f4e67299 100644 --- a/src/card.ts +++ b/src/card.ts @@ -22,12 +22,10 @@ import './components/thumbnail-carousel.js'; import './components/views.js'; import { AdvancedCameraCardViews } from './components/views.js'; import { ConditionStateManagerGetEvent } from './conditions/state-manager-via-event.js'; -import { - AdvancedCameraCardConfig, - MenuItem, - RawAdvancedCameraCardConfig, - StatusBarItem, -} from './config/types'; +import { StatusBarItem } from './config/schema/actions/types.js'; +import { MenuItem } from './config/schema/elements/custom/menu/types.js'; +import { AdvancedCameraCardConfig } from './config/schema/types.js'; +import { RawAdvancedCameraCardConfig } from './config/types.js'; import { REPO_URL } from './const.js'; import { HomeAssistant, LovelaceCardEditor } from './ha/types.js'; import { localize } from './localize/localize.js'; diff --git a/src/components-lib/key-assigner-controller.ts b/src/components-lib/key-assigner-controller.ts index c9e628cb..e6ac4d44 100644 --- a/src/components-lib/key-assigner-controller.ts +++ b/src/components-lib/key-assigner-controller.ts @@ -1,6 +1,6 @@ import { LitElement, ReactiveController } from 'lit'; import isEqual from 'lodash-es/isEqual'; -import { KeyboardShortcut } from '../config/keyboard-shortcuts'; +import { KeyboardShortcut } from '../config/schema/view'; import { setOrRemoveAttribute } from '../utils/basic'; export class KeyAssignerController implements ReactiveController { diff --git a/src/components-lib/media-actions-controller.ts b/src/components-lib/media-actions-controller.ts index 1d94c037..5a913a2d 100644 --- a/src/components-lib/media-actions-controller.ts +++ b/src/components-lib/media-actions-controller.ts @@ -4,7 +4,7 @@ import { AutoPauseCondition, AutoPlayCondition, AutoUnmuteCondition, -} from '../config/types.js'; +} from '../config/schema/common/media-actions.js'; import { MediaPlayerElement } from '../types.js'; import { AdvancedCameraCardMediaLoadedEventTarget } from '../utils/media-info.js'; import { Timer } from '../utils/timer.js'; diff --git a/src/components-lib/media-filter-controller.ts b/src/components-lib/media-filter-controller.ts index a92b6321..700bc1a1 100644 --- a/src/components-lib/media-filter-controller.ts +++ b/src/components-lib/media-filter-controller.ts @@ -1,12 +1,12 @@ import { endOfDay, endOfMonth, - endOfYesterday, endOfToday, - startOfToday, + endOfYesterday, format, parse, startOfDay, + startOfToday, startOfYesterday, sub, } from 'date-fns'; @@ -17,13 +17,13 @@ import uniqWith from 'lodash-es/uniqWith'; import { CameraManager } from '../camera-manager/manager'; import { DateRange, PartialDateRange } from '../camera-manager/range'; import { DataQuery, MediaMetadata, QueryType } from '../camera-manager/types'; +import { ViewManagerInterface } from '../card-controller/view/types'; import { SelectOption, SelectValues } from '../components/select'; -import { CardWideConfig } from '../config/types'; +import { CardWideConfig } from '../config/schema/types'; import { localize } from '../localize/localize'; import { errorToConsole, formatDate, prettifyTitle } from '../utils/basic'; import { EventMediaQueries, RecordingMediaQueries } from '../view/media-queries'; import { MediaQueriesClassifier } from '../view/media-queries-classifier'; -import { ViewManagerInterface } from '../card-controller/view/types'; interface MediaFilterControls { events: boolean; diff --git a/src/components-lib/media-grid-controller.ts b/src/components-lib/media-grid-controller.ts index 5aa58e95..43ff28a4 100644 --- a/src/components-lib/media-grid-controller.ts +++ b/src/components-lib/media-grid-controller.ts @@ -1,7 +1,7 @@ import isEqual from 'lodash-es/isEqual'; import throttle from 'lodash-es/throttle'; import Masonry from 'masonry-layout'; -import { ViewDisplayConfig } from '../config/types'; +import { ViewDisplayConfig } from '../config/schema/common/display'; import { MediaLoadedInfo } from '../types'; import { getChildrenFromElement, setOrRemoveAttribute } from '../utils/basic'; import { fireAdvancedCameraCardEvent } from '../utils/fire-advanced-camera-card-event'; diff --git a/src/components-lib/menu-button-controller.ts b/src/components-lib/menu-button-controller.ts index 2199cb9b..5e5d9773 100644 --- a/src/components-lib/menu-button-controller.ts +++ b/src/components-lib/menu-button-controller.ts @@ -4,11 +4,9 @@ import { FullscreenManager } from '../card-controller/fullscreen/fullscreen-mana import { MediaPlayerManager } from '../card-controller/media-player-manager'; import { MicrophoneManager } from '../card-controller/microphone-manager'; import { ViewManager } from '../card-controller/view/view-manager'; -import { - AdvancedCameraCardConfig, - MenuItem, - VIEWS_USER_SPECIFIED, -} from '../config/types'; +import { VIEWS_USER_SPECIFIED } from '../config/schema/common/const'; +import { MenuItem } from '../config/schema/elements/custom/menu/types'; +import { AdvancedCameraCardConfig } from '../config/schema/types'; import { HomeAssistant } from '../ha/types'; import { localize } from '../localize/localize.js'; import { MediaLoadedInfo } from '../types'; diff --git a/src/components-lib/menu-controller.ts b/src/components-lib/menu-controller.ts index 8ea66bb4..ed406121 100644 --- a/src/components-lib/menu-controller.ts +++ b/src/components-lib/menu-controller.ts @@ -2,13 +2,10 @@ import { LitElement } from 'lit'; import { orderBy } from 'lodash-es'; import { dispatchActionExecutionRequest } from '../card-controller/actions/utils/execution-request.js'; import { SubmenuInteraction } from '../components/submenu/types.js'; -import { - ActionConfig, - MENU_PRIORITY_MAX, - type ActionsConfig, - type MenuConfig, - type MenuItem, -} from '../config/types.js'; +import { ActionConfig, ActionsConfig } from '../config/schema/actions/types.js'; +import { MENU_PRIORITY_MAX } from '../config/schema/common/const.js'; +import { MenuItem } from '../config/schema/elements/custom/menu/types.js'; +import { MenuConfig } from '../config/schema/menu.js'; import { Interaction } from '../types.js'; import { getActionConfigGivenAction } from '../utils/action'; import { arrayify, isTruthy, setOrRemoveAttribute } from '../utils/basic.js'; diff --git a/src/components-lib/ptz/ptz-controller.ts b/src/components-lib/ptz/ptz-controller.ts index 9539cac8..0402f99c 100644 --- a/src/components-lib/ptz/ptz-controller.ts +++ b/src/components-lib/ptz/ptz-controller.ts @@ -1,8 +1,9 @@ import { CameraManager } from '../../camera-manager/manager.js'; import { dispatchActionExecutionRequest } from '../../card-controller/actions/utils/execution-request.js'; import { SubmenuInteraction } from '../../components/submenu/types.js'; -import { PTZAction } from '../../config/ptz.js'; -import { Actions, ActionsConfig, PTZControlsConfig } from '../../config/types.js'; +import { PTZAction } from '../../config/schema/actions/custom/ptz.js'; +import { Actions, ActionsConfig } from '../../config/schema/actions/types.js'; +import { PTZControlsConfig } from '../../config/schema/common/controls/ptz.js'; import { HomeAssistant } from '../../ha/types.js'; import { Interaction } from '../../types.js'; import { createPTZMultiAction, getActionConfigGivenAction } from '../../utils/action.js'; diff --git a/src/components-lib/ptz/types.ts b/src/components-lib/ptz/types.ts index b8bdd88d..910798ab 100644 --- a/src/components-lib/ptz/types.ts +++ b/src/components-lib/ptz/types.ts @@ -1,5 +1,5 @@ -import { PTZControlAction } from '../../config/ptz'; -import { Actions } from '../../config/types'; +import { PTZControlAction } from '../../config/schema/actions/custom/ptz'; +import { Actions } from '../../config/schema/actions/types'; interface PTZControlsViewContext { enabled?: boolean; diff --git a/src/components-lib/status-bar-controller.ts b/src/components-lib/status-bar-controller.ts index b2775529..0e84ba98 100644 --- a/src/components-lib/status-bar-controller.ts +++ b/src/components-lib/status-bar-controller.ts @@ -2,12 +2,9 @@ import { LitElement } from 'lit'; import isEqual from 'lodash-es/isEqual'; import orderBy from 'lodash-es/orderBy'; import { dispatchActionExecutionRequest } from '../card-controller/actions/utils/execution-request'; -import { - ActionsConfig, - STATUS_BAR_PRIORITY_DEFAULT, - StatusBarConfig, - StatusBarItem, -} from '../config/types'; +import { ActionsConfig, StatusBarItem } from '../config/schema/actions/types'; +import { STATUS_BAR_PRIORITY_DEFAULT } from '../config/schema/common/const'; +import { StatusBarConfig } from '../config/schema/status-bar'; import { getActionConfigGivenAction } from '../utils/action'; import { arrayify, setOrRemoveAttribute } from '../utils/basic'; import { Timer } from '../utils/timer'; diff --git a/src/components/carousel.ts b/src/components/carousel.ts index 7545cd1f..915ff635 100644 --- a/src/components/carousel.ts +++ b/src/components/carousel.ts @@ -9,7 +9,7 @@ import { } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { Ref, createRef, ref } from 'lit/directives/ref.js'; -import { TransitionEffect } from '../config/types'; +import { TransitionEffect } from '../config/schema/common/transition-effect'; import carouselStyle from '../scss/carousel.scss'; import { CarouselController, diff --git a/src/components/elements.ts b/src/components/elements.ts index dedc8de1..80e69dab 100644 --- a/src/components/elements.ts +++ b/src/components/elements.ts @@ -11,18 +11,20 @@ import { dispatchAdvancedCameraCardErrorEvent } from '../components-lib/message/ import { ConditionsManager } from '../conditions/conditions-manager.js'; import { getConditionStateManagerViaEvent } from '../conditions/state-manager-via-event.js'; import { - AdvancedCameraCardConditional, - MenuIcon, - MenuItem, - MenuStateIcon, - MenuSubmenu, - MenuSubmenuSelect, - PictureElements, StatusBarIcon, StatusBarImage, StatusBarItem, StatusBarString, -} from '../config/types.js'; +} from '../config/schema/actions/types.js'; +import { MenuIcon } from '../config/schema/elements/custom/menu/icon.js'; +import { MenuStateIcon } from '../config/schema/elements/custom/menu/state-icon.js'; +import { MenuSubmenuSelect } from '../config/schema/elements/custom/menu/submenu-select.js'; +import { MenuSubmenu } from '../config/schema/elements/custom/menu/submenu.js'; +import { MenuItem } from '../config/schema/elements/custom/menu/types.js'; +import { + AdvancedCameraCardConditional, + PictureElements, +} from '../config/schema/elements/types.js'; import { HomeAssistant } from '../ha/types.js'; import { localize } from '../localize/localize.js'; import elementsStyle from '../scss/elements.scss'; diff --git a/src/components/gallery.ts b/src/components/gallery.ts index 0563f86a..d4a4df45 100644 --- a/src/components/gallery.ts +++ b/src/components/gallery.ts @@ -13,7 +13,8 @@ import throttle from 'lodash-es/throttle'; import { CameraManager, ExtendedMediaQueryResult } from '../camera-manager/manager.js'; import { EventQuery, MediaQuery, RecordingQuery } from '../camera-manager/types'; import { ViewManagerEpoch } from '../card-controller/view/types.js'; -import { CardWideConfig, configDefaults, GalleryConfig } from '../config/types'; +import { GalleryConfig } from '../config/schema/gallery.js'; +import { CardWideConfig, configDefaults } from '../config/schema/types.js'; import { HomeAssistant } from '../ha/types.js'; import { localize } from '../localize/localize'; import galleryCoreStyle from '../scss/gallery-core.scss'; diff --git a/src/components/image-updating-player.ts b/src/components/image-updating-player.ts index b0ce645f..5dd5a2fc 100644 --- a/src/components/image-updating-player.ts +++ b/src/components/image-updating-player.ts @@ -15,7 +15,9 @@ import { CameraManager } from '../camera-manager/manager.js'; import { getCameraEntityFromConfig } from '../camera-manager/utils/camera-entity-from-config.js'; import { CachedValueController } from '../components-lib/cached-value-controller.js'; import { UpdatingImageMediaPlayerController } from '../components-lib/media-player/updating-image.js'; -import { CameraConfig, ImageMode, ImageViewConfig } from '../config/types.js'; +import { CameraConfig } from '../config/schema/cameras.js'; +import { ImageMode } from '../config/schema/common/image.js'; +import { ImageViewConfig } from '../config/schema/image.js'; import { HomeAssistant } from '../ha/types.js'; import defaultImage from '../images/iris-screensaver.jpg'; import { localize } from '../localize/localize.js'; diff --git a/src/components/image.ts b/src/components/image.ts index b2281fac..9d16438e 100644 --- a/src/components/image.ts +++ b/src/components/image.ts @@ -13,8 +13,10 @@ import { CameraManager } from '../camera-manager/manager'; import { ViewManagerEpoch } from '../card-controller/view/types'; import { ZoomSettingsObserved } from '../components-lib/zoom/types'; import { handleZoomSettingsObservedEvent } from '../components-lib/zoom/zoom-view-context'; -import { CameraConfig, ImageViewConfig } from '../config/types'; +import { CameraConfig } from '../config/schema/cameras'; +import { ImageViewConfig } from '../config/schema/image'; import { IMAGE_VIEW_ZOOM_TARGET_SENTINEL } from '../const'; +import { HomeAssistant } from '../ha/types'; import imageStyle from '../scss/image.scss'; import { MediaPlayer, MediaPlayerController, MediaPlayerElement } from '../types.js'; import { aspectRatioToString } from '../utils/basic'; @@ -22,7 +24,6 @@ import { updateElementStyleFromMediaLayoutConfig } from '../utils/media-layout.j import './image-updating-player'; import { resolveImageMode } from './image-updating-player'; import './zoomer.js'; -import { HomeAssistant } from '../ha/types'; @customElement('advanced-camera-card-image') export class AdvancedCameraCardImage extends LitElement implements MediaPlayer { diff --git a/src/components/key-assigner.ts b/src/components/key-assigner.ts index e4d20c65..29275157 100644 --- a/src/components/key-assigner.ts +++ b/src/components/key-assigner.ts @@ -9,9 +9,9 @@ import { import { customElement, property } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import { KeyAssignerController } from '../components-lib/key-assigner-controller'; -import { KeyboardShortcut } from '../config/keyboard-shortcuts'; -import keyAssignerStyle from '../scss/key-assigner.scss'; +import { KeyboardShortcut } from '../config/schema/view'; import { localize } from '../localize/localize'; +import keyAssignerStyle from '../scss/key-assigner.scss'; import './icon'; @customElement('advanced-camera-card-key-assigner') diff --git a/src/components/live/carousel.ts b/src/components/live/carousel.ts index d37de101..185a933e 100644 --- a/src/components/live/carousel.ts +++ b/src/components/live/carousel.ts @@ -16,13 +16,10 @@ import { ViewManagerEpoch } from '../../card-controller/view/types.js'; import { MediaActionsController } from '../../components-lib/media-actions-controller.js'; import { ZoomSettingsObserved } from '../../components-lib/zoom/types.js'; import { handleZoomSettingsObservedEvent } from '../../components-lib/zoom/zoom-view-context.js'; -import { - CameraConfig, - CardWideConfig, - configDefaults, - LiveConfig, - TransitionEffect, -} from '../../config/types.js'; +import { CameraConfig } from '../../config/schema/cameras.js'; +import { TransitionEffect } from '../../config/schema/common/transition-effect.js'; +import { LiveConfig } from '../../config/schema/live.js'; +import { CardWideConfig, configDefaults } from '../../config/schema/types.js'; import { HomeAssistant } from '../../ha/types.js'; import liveCarouselStyle from '../../scss/live-carousel.scss'; import { stopEventFromActivatingCardWideActions } from '../../utils/action.js'; diff --git a/src/components/live/grid.ts b/src/components/live/grid.ts index 197acba0..5cbe1c8a 100644 --- a/src/components/live/grid.ts +++ b/src/components/live/grid.ts @@ -12,7 +12,8 @@ import { CameraManager } from '../../camera-manager/manager.js'; import { MicrophoneState } from '../../card-controller/types.js'; import { ViewManagerEpoch } from '../../card-controller/view/types.js'; import { MediaGridSelected } from '../../components-lib/media-grid-controller.js'; -import { CardWideConfig, LiveConfig } from '../../config/types.js'; +import { LiveConfig } from '../../config/schema/live.js'; +import { CardWideConfig } from '../../config/schema/types.js'; import { HomeAssistant } from '../../ha/types.js'; import liveGridStyle from '../../scss/live-grid.scss'; import './carousel.js'; diff --git a/src/components/live/index.ts b/src/components/live/index.ts index 8fa0480e..6887e60d 100644 --- a/src/components/live/index.ts +++ b/src/components/live/index.ts @@ -4,7 +4,8 @@ import { CameraManager } from '../../camera-manager/manager.js'; import { MicrophoneState } from '../../card-controller/types.js'; import { ViewManagerEpoch } from '../../card-controller/view/types.js'; import { LiveController } from '../../components-lib/live/live-controller.js'; -import { CardWideConfig, LiveConfig } from '../../config/types.js'; +import { LiveConfig } from '../../config/schema/live.js'; +import { CardWideConfig } from '../../config/schema/types.js'; import { HomeAssistant } from '../../ha/types.js'; import basicBlockStyle from '../../scss/basic-block.scss'; import './grid.js'; diff --git a/src/components/live/provider.ts b/src/components/live/provider.ts index 3d8fbd20..13293746 100644 --- a/src/components/live/provider.ts +++ b/src/components/live/provider.ts @@ -14,13 +14,9 @@ import { CameraEndpoints } from '../../camera-manager/types.js'; import { MicrophoneState } from '../../card-controller/types.js'; import { dispatchLiveErrorEvent } from '../../components-lib/live/utils/dispatch-live-error.js'; import { PartialZoomSettings } from '../../components-lib/zoom/types.js'; -import { - CameraConfig, - CardWideConfig, - configDefaults, - LiveConfig, - LiveProvider, -} from '../../config/types.js'; +import { CameraConfig, LiveProvider } from '../../config/schema/cameras.js'; +import { LiveConfig } from '../../config/schema/live.js'; +import { CardWideConfig, configDefaults } from '../../config/schema/types.js'; import { STREAM_TROUBLESHOOTING_URL } from '../../const.js'; import { HomeAssistant } from '../../ha/types.js'; import { localize } from '../../localize/localize.js'; diff --git a/src/components/live/providers/go2rtc/index.ts b/src/components/live/providers/go2rtc/index.ts index d66b002b..7d5d6c3c 100644 --- a/src/components/live/providers/go2rtc/index.ts +++ b/src/components/live/providers/go2rtc/index.ts @@ -11,7 +11,8 @@ import { CameraEndpoints } from '../../../../camera-manager/types.js'; import { MicrophoneState } from '../../../../card-controller/types.js'; import { dispatchLiveErrorEvent } from '../../../../components-lib/live/utils/dispatch-live-error.js'; import { VideoMediaPlayerController } from '../../../../components-lib/media-player/video.js'; -import { CameraConfig, MicrophoneConfig } from '../../../../config/types.js'; +import { CameraConfig } from '../../../../config/schema/cameras.js'; +import { MicrophoneConfig } from '../../../../config/schema/live.js'; import { HomeAssistant } from '../../../../ha/types.js'; import { localize } from '../../../../localize/localize.js'; import liveGo2RTCStyle from '../../../../scss/live-go2rtc.scss'; diff --git a/src/components/live/providers/ha.ts b/src/components/live/providers/ha.ts index e24417bc..3ef62b13 100644 --- a/src/components/live/providers/ha.ts +++ b/src/components/live/providers/ha.ts @@ -1,7 +1,7 @@ import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { createRef, Ref, ref } from 'lit/directives/ref.js'; -import { CameraConfig } from '../../../config/types'; +import { CameraConfig } from '../../../config/schema/cameras'; import { HomeAssistant } from '../../../ha/types'; import '../../../patches/ha-camera-stream'; import '../../../patches/ha-hls-player.js'; diff --git a/src/components/live/providers/image.ts b/src/components/live/providers/image.ts index 0d01a7b6..653f44eb 100644 --- a/src/components/live/providers/image.ts +++ b/src/components/live/providers/image.ts @@ -1,7 +1,7 @@ import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { createRef, ref, Ref } from 'lit/directives/ref.js'; -import { CameraConfig } from '../../../config/types'; +import { CameraConfig } from '../../../config/schema/cameras'; import { HomeAssistant } from '../../../ha/types'; import basicBlockStyle from '../../../scss/basic-block.scss'; import { diff --git a/src/components/live/providers/jsmpeg.ts b/src/components/live/providers/jsmpeg.ts index e0cc3017..e25a266b 100644 --- a/src/components/live/providers/jsmpeg.ts +++ b/src/components/live/providers/jsmpeg.ts @@ -12,7 +12,8 @@ import { until } from 'lit/directives/until.js'; import { CameraEndpoints } from '../../../camera-manager/types.js'; import { dispatchLiveErrorEvent } from '../../../components-lib/live/utils/dispatch-live-error.js'; import { JSMPEGMediaPlayerController } from '../../../components-lib/media-player/jsmpeg.js'; -import { CameraConfig, CardWideConfig } from '../../../config/types.js'; +import { CameraConfig } from '../../../config/schema/cameras.js'; +import { CardWideConfig } from '../../../config/schema/types.js'; import { HomeAssistant } from '../../../ha/types.js'; import { localize } from '../../../localize/localize.js'; import liveJSMPEGStyle from '../../../scss/live-jsmpeg.scss'; diff --git a/src/components/live/providers/webrtc-card.ts b/src/components/live/providers/webrtc-card.ts index 3d5afe8e..86679213 100644 --- a/src/components/live/providers/webrtc-card.ts +++ b/src/components/live/providers/webrtc-card.ts @@ -12,7 +12,8 @@ import { CameraEndpoints } from '../../../camera-manager/types.js'; import { dispatchLiveErrorEvent } from '../../../components-lib/live/utils/dispatch-live-error.js'; import { getTechnologyForVideoRTC } from '../../../components-lib/live/utils/get-technology-for-video-rtc.js'; import { VideoMediaPlayerController } from '../../../components-lib/media-player/video.js'; -import { CameraConfig, CardWideConfig } from '../../../config/types.js'; +import { CameraConfig } from '../../../config/schema/cameras.js'; +import { CardWideConfig } from '../../../config/schema/types.js'; import { HomeAssistant } from '../../../ha/types.js'; import { localize } from '../../../localize/localize.js'; import liveWebRTCCardStyle from '../../../scss/live-webrtc-card.scss'; diff --git a/src/components/media-filter.ts b/src/components/media-filter.ts index 8d5194c1..cdb80ae9 100644 --- a/src/components/media-filter.ts +++ b/src/components/media-filter.ts @@ -18,7 +18,7 @@ import { MediaFilterCoreWhen, MediaFilterMediaType, } from '../components-lib/media-filter-controller'; -import { CardWideConfig } from '../config/types'; +import { CardWideConfig } from '../config/schema/types'; import { HomeAssistant } from '../ha/types'; import { localize } from '../localize/localize'; import mediaFilterStyle from '../scss/media-filter.scss'; diff --git a/src/components/media-grid.ts b/src/components/media-grid.ts index fe3f7c11..8ccfdae7 100644 --- a/src/components/media-grid.ts +++ b/src/components/media-grid.ts @@ -9,7 +9,7 @@ import { import { customElement, property } from 'lit/decorators.js'; import { createRef, ref, Ref } from 'lit/directives/ref.js'; import { MediaGridController } from '../components-lib/media-grid-controller.js'; -import { ViewDisplayConfig } from '../config/types'; +import { ViewDisplayConfig } from '../config/schema/common/display.js'; import mediaGridStyle from '../scss/media-grid.scss'; @customElement('advanced-camera-card-media-grid') diff --git a/src/components/menu.ts b/src/components/menu.ts index d7269f41..2916d1c3 100644 --- a/src/components/menu.ts +++ b/src/components/menu.ts @@ -3,7 +3,8 @@ import { customElement, property } from 'lit/decorators.js'; import { styleMap } from 'lit/directives/style-map.js'; import { actionHandler } from '../action-handler-directive.js'; import { MenuController } from '../components-lib/menu-controller.js'; -import type { MenuConfig, MenuItem } from '../config/types.js'; +import { MenuItem } from '../config/schema/elements/custom/menu/types.js'; +import { MenuConfig } from '../config/schema/menu.js'; import { HomeAssistant } from '../ha/types.js'; import menuStyle from '../scss/menu.scss'; import { hasAction } from '../utils/action.js'; diff --git a/src/components/next-prev-control.ts b/src/components/next-prev-control.ts index c8d7e135..5687e218 100644 --- a/src/components/next-prev-control.ts +++ b/src/components/next-prev-control.ts @@ -1,7 +1,7 @@ import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; -import { NextPreviousControlConfig } from '../config/types.js'; +import { NextPreviousControlConfig } from '../config/schema/common/controls/next-previous.js'; import { HomeAssistant } from '../ha/types.js'; import controlStyle from '../scss/next-previous-control.scss'; import { Icon } from '../types.js'; diff --git a/src/components/progress-indicator.ts b/src/components/progress-indicator.ts index 30e64eaf..b2abd3f2 100644 --- a/src/components/progress-indicator.ts +++ b/src/components/progress-indicator.ts @@ -2,7 +2,7 @@ import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit import { customElement, property } from 'lit/decorators.js'; import { ClassInfo, classMap } from 'lit/directives/class-map.js'; import { ref, Ref } from 'lit/directives/ref.js'; -import { CardWideConfig } from '../config/types.js'; +import { CardWideConfig } from '../config/schema/types'; import messageStyle from '../scss/message.scss'; import './icon.js'; diff --git a/src/components/ptz.ts b/src/components/ptz.ts index 05d5ef94..2fd93d69 100644 --- a/src/components/ptz.ts +++ b/src/components/ptz.ts @@ -12,7 +12,8 @@ import { actionHandler } from '../action-handler-directive.js'; import { CameraManager } from '../camera-manager/manager.js'; import { PTZController } from '../components-lib/ptz/ptz-controller.js'; import { PTZControllerActions } from '../components-lib/ptz/types.js'; -import { Actions, PTZControlsConfig } from '../config/types.js'; +import { Actions } from '../config/schema/actions/types.js'; +import { PTZControlsConfig } from '../config/schema/common/controls/ptz.js'; import { HomeAssistant } from '../ha/types.js'; import { localize } from '../localize/localize.js'; import ptzStyle from '../scss/ptz.scss'; diff --git a/src/components/status-bar.ts b/src/components/status-bar.ts index d6617e83..53384e99 100644 --- a/src/components/status-bar.ts +++ b/src/components/status-bar.ts @@ -10,7 +10,8 @@ import { customElement, property } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import { actionHandler } from '../action-handler-directive.js'; import { StatusBarController } from '../components-lib/status-bar-controller'; -import { StatusBarConfig, StatusBarItem } from '../config/types'; +import { StatusBarItem } from '../config/schema/actions/types.js'; +import { StatusBarConfig } from '../config/schema/status-bar.js'; import statusStyle from '../scss/status.scss'; import { hasAction } from '../utils/action'; import './icon.js'; diff --git a/src/components/submenu/select-button.ts b/src/components/submenu/select-button.ts index a541d207..efa7a6ed 100644 --- a/src/components/submenu/select-button.ts +++ b/src/components/submenu/select-button.ts @@ -8,7 +8,8 @@ import { } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { styleMap } from 'lit/directives/style-map.js'; -import { MenuSubmenuItem, MenuSubmenuSelect } from '../../config/types.js'; +import { MenuSubmenuSelect } from '../../config/schema/elements/custom/menu/submenu-select.js'; +import { MenuSubmenuItem } from '../../config/schema/elements/custom/menu/submenu.js'; import { HomeAssistant } from '../../ha/types.js'; import menuButtonStyle from '../../scss/menu-button.scss'; import { Icon } from '../../types.js'; diff --git a/src/components/submenu/submenu-button.ts b/src/components/submenu/submenu-button.ts index 27f2db8f..a98a5df1 100644 --- a/src/components/submenu/submenu-button.ts +++ b/src/components/submenu/submenu-button.ts @@ -2,7 +2,7 @@ import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit import { customElement, property } from 'lit/decorators.js'; import { styleMap } from 'lit/directives/style-map.js'; import { actionHandler } from '../../action-handler-directive.js'; -import { MenuSubmenu } from '../../config/types.js'; +import { MenuSubmenu } from '../../config/schema/elements/custom/menu/submenu.js'; import { HomeAssistant } from '../../ha/types.js'; import menuButtonStyle from '../../scss/menu-button.scss'; import { hasAction } from '../../utils/action.js'; diff --git a/src/components/submenu/types.ts b/src/components/submenu/types.ts index d5c83559..32bbfa14 100644 --- a/src/components/submenu/types.ts +++ b/src/components/submenu/types.ts @@ -1,4 +1,4 @@ -import { Actions } from '../../config/types'; +import { Actions } from '../../config/schema/actions/types'; import { Interaction } from '../../types'; export interface SubmenuItem extends Actions { diff --git a/src/components/surround.ts b/src/components/surround.ts index 90f07dcd..329933b6 100644 --- a/src/components/surround.ts +++ b/src/components/surround.ts @@ -10,11 +10,9 @@ import { customElement, property } from 'lit/decorators.js'; import { CameraManager } from '../camera-manager/manager.js'; import { RemoveContextViewModifier } from '../card-controller/view/modifiers/remove-context.js'; import { ViewManagerEpoch } from '../card-controller/view/types.js'; -import { - CardWideConfig, - MiniTimelineControlConfig, - ThumbnailsControlConfig, -} from '../config/types.js'; +import { ThumbnailsControlConfig } from '../config/schema/common/controls/thumbnails.js'; +import { MiniTimelineControlConfig } from '../config/schema/common/controls/timeline.js'; +import { CardWideConfig } from '../config/schema/types.js'; import { HomeAssistant } from '../ha/types.js'; import basicBlockStyle from '../scss/basic-block.scss'; import { contentsChanged } from '../utils/basic.js'; diff --git a/src/components/thumbnail-carousel.ts b/src/components/thumbnail-carousel.ts index 508971b5..833b9bb0 100644 --- a/src/components/thumbnail-carousel.ts +++ b/src/components/thumbnail-carousel.ts @@ -10,7 +10,7 @@ import { customElement, property } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import { CameraManager } from '../camera-manager/manager.js'; import { ViewManagerEpoch } from '../card-controller/view/types.js'; -import { ThumbnailsControlConfig } from '../config/types.js'; +import { ThumbnailsControlConfig } from '../config/schema/common/controls/thumbnails.js'; import { HomeAssistant } from '../ha/types.js'; import thumbnailCarouselStyle from '../scss/thumbnail-carousel.scss'; import { stopEventFromActivatingCardWideActions } from '../utils/action.js'; diff --git a/src/components/timeline-core.ts b/src/components/timeline-core.ts index 386c98ca..196b4854 100644 --- a/src/components/timeline-core.ts +++ b/src/components/timeline-core.ts @@ -37,15 +37,14 @@ import { AdvancedCameraCardTimelineItem, TimelineDataSource, } from '../components-lib/timeline-source'; +import { CameraConfig } from '../config/schema/cameras'; +import { AdvancedCameraCardView } from '../config/schema/common/const'; +import { ThumbnailsControlBaseConfig } from '../config/schema/common/controls/thumbnails'; import { - AdvancedCameraCardView, - CameraConfig, - CardWideConfig, - ThumbnailsControlBaseConfig, TimelineCoreConfig, TimelinePanMode, - configDefaults, -} from '../config/types'; +} from '../config/schema/common/controls/timeline'; +import { CardWideConfig, configDefaults } from '../config/schema/types'; import { HomeAssistant } from '../ha/types'; import { localize } from '../localize/localize'; import timelineCoreStyle from '../scss/timeline-core.scss'; diff --git a/src/components/timeline.ts b/src/components/timeline.ts index fc60a98a..8b691774 100644 --- a/src/components/timeline.ts +++ b/src/components/timeline.ts @@ -2,7 +2,8 @@ import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit import { customElement, property } from 'lit/decorators.js'; import { CameraManager } from '../camera-manager/manager'; import { ViewManagerEpoch } from '../card-controller/view/types'; -import { CardWideConfig, TimelineConfig } from '../config/types'; +import { TimelineConfig } from '../config/schema/timeline'; +import { CardWideConfig } from '../config/schema/types'; import { HomeAssistant } from '../ha/types'; import basicBlockStyle from '../scss/basic-block.scss'; import './surround.js'; diff --git a/src/components/viewer/carousel.ts b/src/components/viewer/carousel.ts index 01371a53..60cbd011 100644 --- a/src/components/viewer/carousel.ts +++ b/src/components/viewer/carousel.ts @@ -13,12 +13,9 @@ import { CameraManager } from '../../camera-manager/manager.js'; import { RemoveContextPropertyViewModifier } from '../../card-controller/view/modifiers/remove-context-property.js'; import { ViewManagerEpoch } from '../../card-controller/view/types.js'; import { MediaActionsController } from '../../components-lib/media-actions-controller.js'; -import { - CardWideConfig, - configDefaults, - TransitionEffect, - ViewerConfig, -} from '../../config/types.js'; +import { TransitionEffect } from '../../config/schema/common/transition-effect.js'; +import { CardWideConfig, configDefaults } from '../../config/schema/types.js'; +import { ViewerConfig } from '../../config/schema/viewer.js'; import { HomeAssistant } from '../../ha/types.js'; import { localize } from '../../localize/localize.js'; import '../../patches/ha-hls-player.js'; diff --git a/src/components/viewer/grid.ts b/src/components/viewer/grid.ts index 4eaae02b..e36922e0 100644 --- a/src/components/viewer/grid.ts +++ b/src/components/viewer/grid.ts @@ -11,7 +11,8 @@ import { ifDefined } from 'lit/directives/if-defined.js'; import { CameraManager } from '../../camera-manager/manager.js'; import { ViewManagerEpoch } from '../../card-controller/view/types.js'; import { MediaGridSelected } from '../../components-lib/media-grid-controller.js'; -import { CardWideConfig, ViewerConfig } from '../../config/types.js'; +import { CardWideConfig } from '../../config/schema/types.js'; +import { ViewerConfig } from '../../config/schema/viewer.js'; import { HomeAssistant } from '../../ha/types.js'; import '../../patches/ha-hls-player.js'; import basicBlockStyle from '../../scss/basic-block.scss'; diff --git a/src/components/viewer/index.ts b/src/components/viewer/index.ts index 381c9bbf..fbb56140 100644 --- a/src/components/viewer/index.ts +++ b/src/components/viewer/index.ts @@ -2,7 +2,8 @@ import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit import { customElement, property } from 'lit/decorators.js'; import { CameraManager } from '../../camera-manager/manager.js'; import { ViewManagerEpoch } from '../../card-controller/view/types.js'; -import { CardWideConfig, ViewerConfig } from '../../config/types.js'; +import { CardWideConfig } from '../../config/schema/types.js'; +import { ViewerConfig } from '../../config/schema/viewer.js'; import { HomeAssistant } from '../../ha/types.js'; import { localize } from '../../localize/localize.js'; import '../../patches/ha-hls-player.js'; diff --git a/src/components/viewer/provider.ts b/src/components/viewer/provider.ts index e36bf372..d6056077 100644 --- a/src/components/viewer/provider.ts +++ b/src/components/viewer/provider.ts @@ -13,7 +13,8 @@ import { CameraManager } from '../../camera-manager/manager.js'; import { ViewManagerEpoch } from '../../card-controller/view/types.js'; import { ZoomSettingsObserved } from '../../components-lib/zoom/types.js'; import { handleZoomSettingsObservedEvent } from '../../components-lib/zoom/zoom-view-context.js'; -import { CardWideConfig, ViewerConfig } from '../../config/types.js'; +import { CardWideConfig } from '../../config/schema/types.js'; +import { ViewerConfig } from '../../config/schema/viewer.js'; import { HomeAssistant } from '../../ha/types.js'; import '../../patches/ha-hls-player.js'; import viewerProviderStyle from '../../scss/viewer-provider.scss'; diff --git a/src/components/views.ts b/src/components/views.ts index 1872541f..49676811 100644 --- a/src/components/views.ts +++ b/src/components/views.ts @@ -11,11 +11,8 @@ import { classMap } from 'lit/directives/class-map.js'; import { CameraManager } from '../camera-manager/manager.js'; import { MicrophoneState } from '../card-controller/types.js'; import { ViewManagerEpoch } from '../card-controller/view/types.js'; -import { - AdvancedCameraCardConfig, - CardWideConfig, - RawAdvancedCameraCardConfig, -} from '../config/types.js'; +import { AdvancedCameraCardConfig, CardWideConfig } from '../config/schema/types.js'; +import { RawAdvancedCameraCardConfig } from '../config/types.js'; import { HomeAssistant } from '../ha/types.js'; import viewsStyle from '../scss/views.scss'; import { DeviceRegistryManager } from '../utils/ha/registry/device/index.js'; diff --git a/src/conditions/conditions-manager.ts b/src/conditions/conditions-manager.ts index 487debee..e0dd2bf0 100644 --- a/src/conditions/conditions-manager.ts +++ b/src/conditions/conditions-manager.ts @@ -1,5 +1,5 @@ import { getConfigValue } from '../config/management'; -import { AdvancedCameraCardCondition } from '../config/types'; +import { AdvancedCameraCardCondition } from '../config/schema/conditions/types'; import { isCompanionApp } from '../utils/companion'; import { ConditionsEvaluationResult, diff --git a/src/conditions/types.ts b/src/conditions/types.ts index 45c43027..e5375d70 100644 --- a/src/conditions/types.ts +++ b/src/conditions/types.ts @@ -1,6 +1,7 @@ import { HassEntities } from 'home-assistant-js-websocket'; import { KeysState, MicrophoneState } from '../card-controller/types'; -import { AdvancedCameraCardConfig, ViewDisplayMode } from '../config/types'; +import { ViewDisplayMode } from '../config/schema/common/display'; +import { AdvancedCameraCardConfig } from '../config/schema/types'; import { CurrentUser } from '../ha/types'; import { MediaLoadedInfo } from '../types'; diff --git a/src/config/keyboard-shortcuts.ts b/src/config/keyboard-shortcuts.ts deleted file mode 100644 index 19ed696f..00000000 --- a/src/config/keyboard-shortcuts.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { z } from 'zod'; - -const keyboardShortcut = z.object({ - key: z.string(), - ctrl: z.boolean().optional(), - shift: z.boolean().optional(), - alt: z.boolean().optional(), - meta: z.boolean().optional(), -}); -export type KeyboardShortcut = z.infer; - -export const keyboardShortcutsDefault = { - enabled: true, - ptz_left: { key: 'ArrowLeft' }, - ptz_right: { key: 'ArrowRight' }, - ptz_up: { key: 'ArrowUp' }, - ptz_down: { key: 'ArrowDown' }, - ptz_zoom_in: { key: '+' }, - ptz_zoom_out: { key: '-' }, - ptz_home: { key: 'h' }, -}; - -export const keyboardShortcutsSchema = z.object({ - enabled: z.boolean().default(keyboardShortcutsDefault.enabled), - ptz_left: keyboardShortcut.nullable().default(keyboardShortcutsDefault.ptz_left), - ptz_right: keyboardShortcut.nullable().default(keyboardShortcutsDefault.ptz_right), - ptz_up: keyboardShortcut.nullable().default(keyboardShortcutsDefault.ptz_up), - ptz_down: keyboardShortcut.nullable().default(keyboardShortcutsDefault.ptz_down), - ptz_zoom_in: keyboardShortcut.nullable().default(keyboardShortcutsDefault.ptz_zoom_in), - ptz_zoom_out: keyboardShortcut - .nullable() - .default(keyboardShortcutsDefault.ptz_zoom_out), - ptz_home: keyboardShortcut.nullable().default(keyboardShortcutsDefault.ptz_home), -}); -export type KeyboardShortcuts = z.infer; - -const KEYBOARD_SHORTCUT_PTZ_NAMES = [ - 'ptz_down', - 'ptz_home', - 'ptz_left', - 'ptz_right', - 'ptz_up', - 'ptz_zoom_in', - 'ptz_zoom_out', -] as const; -export type PTZKeyboardShortcutName = (typeof KEYBOARD_SHORTCUT_PTZ_NAMES)[number]; diff --git a/src/config/management.ts b/src/config/management.ts index cd997c23..6bd57d9a 100644 --- a/src/config/management.ts +++ b/src/config/management.ts @@ -29,11 +29,8 @@ import { CONF_VIEW_TRIGGERS_FILTER_SELECTED_CAMERA, } from '../const'; import { arrayify } from '../utils/basic'; -import { - AdvancedCameraCardCondition, - RawAdvancedCameraCardConfig, - RawAdvancedCameraCardConfigArray, -} from './types'; +import { AdvancedCameraCardCondition } from './schema/conditions/types'; +import { RawAdvancedCameraCardConfig, RawAdvancedCameraCardConfigArray } from './types'; // ************************************************************************* // General Config Management Functions diff --git a/src/config/profiles/index.ts b/src/config/profiles/set-profiles.ts similarity index 89% rename from src/config/profiles/index.ts rename to src/config/profiles/set-profiles.ts index bd7f69ba..5243fb90 100644 --- a/src/config/profiles/index.ts +++ b/src/config/profiles/set-profiles.ts @@ -1,10 +1,8 @@ import { deepRemoveDefaults } from '../../utils/zod.js'; import { getConfigValue, setConfigValue } from '../management.js'; -import { - ProfileType, - RawAdvancedCameraCardConfig, - advancedCameraCardConfigSchema, -} from '../types.js'; +import { ProfileType } from '../schema/profiles.js'; +import { advancedCameraCardConfigSchema } from '../schema/types.js'; +import { RawAdvancedCameraCardConfig } from '../types.js'; import { CASTING_PROFILE } from './casting.js'; import { LOW_PERFORMANCE_PROFILE } from './low-performance.js'; import { SCRUBBING_PROFILE } from './scrubbing.js'; diff --git a/src/config/schema/actions/base.ts b/src/config/schema/actions/base.ts new file mode 100644 index 00000000..8cce40c0 --- /dev/null +++ b/src/config/schema/actions/base.ts @@ -0,0 +1,25 @@ +import { z } from 'zod'; +import { cardIDRegex } from '../common/const'; + +// https://www.home-assistant.io/dashboards/actions/#options-for-confirmation +export const actionBaseSchema = z.object({ + confirmation: z + .boolean() + .or( + z.object({ + text: z.string().optional(), + exemptions: z + .object({ + user: z.string(), + }) + .array() + .optional(), + }), + ) + .optional(), + + card_id: z + .string() + .regex(cardIDRegex, 'card_id parameter can only contain [a-z][A-Z][0-9_]-') + .optional(), +}); diff --git a/src/config/schema/actions/custom/base.ts b/src/config/schema/actions/custom/base.ts new file mode 100644 index 00000000..9c40f874 --- /dev/null +++ b/src/config/schema/actions/custom/base.ts @@ -0,0 +1,12 @@ +import { z } from 'zod'; +import { actionBaseSchema } from '../base'; + +export const advancedCameraCardCustomActionsBaseSchema = actionBaseSchema.extend({ + action: z + .literal('fire-dom-event') + .or( + z + .literal('custom:advanced-camera-card-action') + .transform((): 'fire-dom-event' => 'fire-dom-event'), + ), +}); diff --git a/src/config/schema/actions/custom/camera-select.ts b/src/config/schema/actions/custom/camera-select.ts new file mode 100644 index 00000000..43a23c27 --- /dev/null +++ b/src/config/schema/actions/custom/camera-select.ts @@ -0,0 +1,10 @@ +import { z } from 'zod'; +import { advancedCameraCardCustomActionsBaseSchema } from './base'; + +export const cameraSelectActionConfigSchema = + advancedCameraCardCustomActionsBaseSchema.extend({ + advanced_camera_card_action: z.literal('camera_select'), + camera: z.string().optional(), + triggered: z.boolean().optional(), + }); +export type CameraSelectActionConfig = z.infer; diff --git a/src/config/schema/actions/custom/display-mode.ts b/src/config/schema/actions/custom/display-mode.ts new file mode 100644 index 00000000..2610b90b --- /dev/null +++ b/src/config/schema/actions/custom/display-mode.ts @@ -0,0 +1,10 @@ +import { z } from 'zod'; +import { viewDisplayModeSchema } from '../../common/display'; +import { advancedCameraCardCustomActionsBaseSchema } from './base'; + +export const viewDisplayModeActionConfigSchema = + advancedCameraCardCustomActionsBaseSchema.extend({ + advanced_camera_card_action: z.literal('display_mode_select'), + display_mode: viewDisplayModeSchema, + }); +export type DisplayModeActionConfig = z.infer; diff --git a/src/config/schema/actions/custom/general.ts b/src/config/schema/actions/custom/general.ts new file mode 100644 index 00000000..1cf9b914 --- /dev/null +++ b/src/config/schema/actions/custom/general.ts @@ -0,0 +1,29 @@ +import { z } from 'zod'; +import { advancedCameraCardCustomActionsBaseSchema } from './base'; + +const GENERAL_ACTIONS = [ + 'camera_ui', + 'default', + 'download', + 'expand', + 'fullscreen', + 'live_substream_off', + 'live_substream_on', + 'menu_toggle', + 'microphone_connect', + 'microphone_disconnect', + 'microphone_mute', + 'microphone_unmute', + 'mute', + 'pause', + 'play', + 'screenshot', + 'unmute', +] as const; +export type AdvancedCameraCardGeneralAction = (typeof GENERAL_ACTIONS)[number]; + +export const generalActionConfigSchema = + advancedCameraCardCustomActionsBaseSchema.extend({ + advanced_camera_card_action: z.enum(GENERAL_ACTIONS), + }); +export type GeneralActionConfig = z.infer; diff --git a/src/config/schema/actions/custom/internal.ts b/src/config/schema/actions/custom/internal.ts new file mode 100644 index 00000000..b4767961 --- /dev/null +++ b/src/config/schema/actions/custom/internal.ts @@ -0,0 +1,16 @@ +import { z } from 'zod'; +import { advancedCameraCardCustomActionsBaseSchema } from './base'; + +// An action that can be used internally to call a callback (it is not possible +// for the user to pass this through via the configuration). +export const INTERNAL_CALLBACK_ACTION = '__INTERNAL_CALLBACK_ACTION__'; +export const internalCallbackActionConfigSchema = + advancedCameraCardCustomActionsBaseSchema.extend({ + advanced_camera_card_action: z.literal(INTERNAL_CALLBACK_ACTION), + + // The callback is expected to be called with a CardController API object. + callback: z.function().args(z.any()).returns(z.promise(z.void())), + }); +export type InternalCallbackActionConfig = z.infer< + typeof internalCallbackActionConfigSchema +>; diff --git a/src/config/schema/actions/custom/log.ts b/src/config/schema/actions/custom/log.ts new file mode 100644 index 00000000..8438ba2e --- /dev/null +++ b/src/config/schema/actions/custom/log.ts @@ -0,0 +1,12 @@ +import { z } from 'zod'; +import { advancedCameraCardCustomActionsBaseSchema } from './base'; + +const LOG_ACTIONS_LEVELS = ['debug', 'info', 'warn', 'error'] as const; +export type LogActionLevel = (typeof LOG_ACTIONS_LEVELS)[number]; + +export const logActionConfigSchema = advancedCameraCardCustomActionsBaseSchema.extend({ + advanced_camera_card_action: z.literal('log'), + message: z.string(), + level: z.enum(LOG_ACTIONS_LEVELS).default('info'), +}); +export type LogActionConfig = z.infer; diff --git a/src/config/schema/actions/custom/media-player.ts b/src/config/schema/actions/custom/media-player.ts new file mode 100644 index 00000000..4264b0d8 --- /dev/null +++ b/src/config/schema/actions/custom/media-player.ts @@ -0,0 +1,10 @@ +import { z } from 'zod'; +import { advancedCameraCardCustomActionsBaseSchema } from './base'; + +export const mediaPlayerActionConfigSchema = + advancedCameraCardCustomActionsBaseSchema.extend({ + advanced_camera_card_action: z.literal('media_player'), + media_player: z.string(), + media_player_action: z.enum(['play', 'stop']), + }); +export type MediaPlayerActionConfig = z.infer; diff --git a/src/config/schema/actions/custom/ptz-controls.ts b/src/config/schema/actions/custom/ptz-controls.ts new file mode 100644 index 00000000..227100a7 --- /dev/null +++ b/src/config/schema/actions/custom/ptz-controls.ts @@ -0,0 +1,9 @@ +import { z } from 'zod'; +import { advancedCameraCardCustomActionsBaseSchema } from './base'; + +export const ptzControlsActionConfigSchema = + advancedCameraCardCustomActionsBaseSchema.extend({ + advanced_camera_card_action: z.literal('ptz_controls'), + enabled: z.boolean(), + }); +export type PTZControlsActionConfig = z.infer; diff --git a/src/config/schema/actions/custom/ptz-digital.ts b/src/config/schema/actions/custom/ptz-digital.ts new file mode 100644 index 00000000..c4413e19 --- /dev/null +++ b/src/config/schema/actions/custom/ptz-digital.ts @@ -0,0 +1,20 @@ +import { z } from 'zod'; +import { panSchema } from '../../common/pan'; +import { zoomSchema } from '../../common/zoom'; +import { advancedCameraCardCustomActionsBaseSchema } from './base'; +import { PTZ_ACTION_PHASES, PTZ_ACTIONS } from './ptz'; + +export const ptzDigitalActionConfigSchema = + advancedCameraCardCustomActionsBaseSchema.extend({ + advanced_camera_card_action: z.literal('ptz_digital'), + target_id: z.string().optional(), + absolute: z + .object({ + zoom: zoomSchema.optional(), + pan: panSchema.optional(), + }) + .optional(), + ptz_action: z.enum(PTZ_ACTIONS).optional(), + ptz_phase: z.enum(PTZ_ACTION_PHASES).optional(), + }); +export type PTZDigitialActionConfig = z.infer; diff --git a/src/config/schema/actions/custom/ptz-multi.ts b/src/config/schema/actions/custom/ptz-multi.ts new file mode 100644 index 00000000..082a9629 --- /dev/null +++ b/src/config/schema/actions/custom/ptz-multi.ts @@ -0,0 +1,13 @@ +import { z } from 'zod'; +import { advancedCameraCardCustomActionsBaseSchema } from './base'; +import { PTZ_ACTION_PHASES, PTZ_ACTIONS } from './ptz'; + +export const ptzMultiActionSchema = advancedCameraCardCustomActionsBaseSchema.extend({ + advanced_camera_card_action: z.literal('ptz_multi'), + target_id: z.string().optional(), + + ptz_action: z.enum(PTZ_ACTIONS).optional(), + ptz_phase: z.enum(PTZ_ACTION_PHASES).optional(), + ptz_preset: z.string().optional(), +}); +export type PTZMultiActionConfig = z.infer; diff --git a/src/config/ptz.ts b/src/config/schema/actions/custom/ptz.ts similarity index 53% rename from src/config/ptz.ts rename to src/config/schema/actions/custom/ptz.ts index 4507e2bf..c0b8b121 100644 --- a/src/config/ptz.ts +++ b/src/config/schema/actions/custom/ptz.ts @@ -1,3 +1,6 @@ +import { z } from 'zod'; +import { advancedCameraCardCustomActionsBaseSchema } from './base'; + const PTZ_PAN_TILT_ACTIONS = ['left', 'right', 'up', 'down'] as const; const PTZ_ZOOM_ACTIONS = ['zoom_in', 'zoom_out'] as const; const PTZ_BASE_ACTIONS = [...PTZ_PAN_TILT_ACTIONS, ...PTZ_ZOOM_ACTIONS] as const; @@ -10,3 +13,15 @@ export type PTZControlAction = (typeof PTZ_CONTROL_ACTIONS)[number]; // PTZ actions as used by the camera manager (includes generic presets). export const PTZ_ACTIONS = [...PTZ_BASE_ACTIONS, 'preset'] as const; export type PTZAction = (typeof PTZ_ACTIONS)[number]; + +export const PTZ_ACTION_PHASES = ['start', 'stop'] as const; +export type PTZActionPhase = (typeof PTZ_ACTION_PHASES)[number]; + +export const ptzActionConfigSchema = advancedCameraCardCustomActionsBaseSchema.extend({ + advanced_camera_card_action: z.literal('ptz'), + camera: z.string().optional(), + ptz_action: z.enum(PTZ_ACTIONS).optional(), + ptz_phase: z.enum(PTZ_ACTION_PHASES).optional(), + ptz_preset: z.string().optional(), +}); +export type PTZActionConfig = z.infer; diff --git a/src/config/schema/actions/custom/sleep.ts b/src/config/schema/actions/custom/sleep.ts new file mode 100644 index 00000000..e0f252cb --- /dev/null +++ b/src/config/schema/actions/custom/sleep.ts @@ -0,0 +1,16 @@ +import { z } from 'zod'; +import { advancedCameraCardCustomActionsBaseSchema } from './base'; + +const timeDeltaSchema = z.object({ + ms: z.number().optional(), + s: z.number().optional(), + m: z.number().optional(), + h: z.number().optional(), +}); +export type TimeDelta = z.infer; + +export const sleepActionConfigSchema = advancedCameraCardCustomActionsBaseSchema.extend({ + advanced_camera_card_action: z.literal('sleep'), + duration: timeDeltaSchema.optional().default({ s: 1 }), +}); +export type SleepActionConfig = z.infer; diff --git a/src/config/schema/actions/custom/substream-select.ts b/src/config/schema/actions/custom/substream-select.ts new file mode 100644 index 00000000..7b83d654 --- /dev/null +++ b/src/config/schema/actions/custom/substream-select.ts @@ -0,0 +1,11 @@ +import { z } from 'zod'; +import { advancedCameraCardCustomActionsBaseSchema } from './base'; + +export const substreamSelectActionConfigSchema = + advancedCameraCardCustomActionsBaseSchema.extend({ + advanced_camera_card_action: z.literal('live_substream_select'), + camera: z.string(), + }); +export type SubstreamSelectActionConfig = z.infer< + typeof substreamSelectActionConfigSchema +>; diff --git a/src/config/schema/actions/custom/view.ts b/src/config/schema/actions/custom/view.ts new file mode 100644 index 00000000..621904e3 --- /dev/null +++ b/src/config/schema/actions/custom/view.ts @@ -0,0 +1,8 @@ +import { z } from 'zod'; +import { VIEWS_USER_SPECIFIED } from '../../common/const'; +import { advancedCameraCardCustomActionsBaseSchema } from './base'; + +export const viewActionConfigSchema = advancedCameraCardCustomActionsBaseSchema.extend({ + advanced_camera_card_action: z.enum(VIEWS_USER_SPECIFIED), +}); +export type ViewActionConfig = z.infer; diff --git a/src/config/schema/actions/stock/call-service.ts b/src/config/schema/actions/stock/call-service.ts new file mode 100644 index 00000000..c2644df4 --- /dev/null +++ b/src/config/schema/actions/stock/call-service.ts @@ -0,0 +1,15 @@ +import { z } from 'zod'; +import { actionBaseSchema } from '../base'; +import { targetSchema } from './target'; + +// Note: call-service is deprecated and will eventually go away. Please use +// perform-action instead. +// See: https://www.home-assistant.io/blog/2024/08/07/release-20248/#goodbye-service-calls-hello-actions- + +export const callServiceActionSchema = actionBaseSchema.extend({ + action: z.literal('call-service'), + service: z.string(), + data: z.object({}).passthrough().optional(), + target: targetSchema.optional(), +}); +export type CallServiceActionConfig = z.infer; diff --git a/src/config/schema/actions/stock/custom.ts b/src/config/schema/actions/stock/custom.ts new file mode 100644 index 00000000..afd05d62 --- /dev/null +++ b/src/config/schema/actions/stock/custom.ts @@ -0,0 +1,9 @@ +import { z } from 'zod'; +import { actionBaseSchema } from '../base'; + +export const customActionSchema = actionBaseSchema + .extend({ + action: z.literal('fire-dom-event'), + }) + .passthrough(); +export type CustomActionConfig = z.infer; diff --git a/src/config/schema/actions/stock/more-info.ts b/src/config/schema/actions/stock/more-info.ts new file mode 100644 index 00000000..feafae92 --- /dev/null +++ b/src/config/schema/actions/stock/more-info.ts @@ -0,0 +1,8 @@ +import { z } from 'zod'; +import { actionBaseSchema } from '../base'; + +export const moreInfoActionSchema = actionBaseSchema.extend({ + action: z.literal('more-info'), + entity: z.string().optional(), +}); +export type MoreInfoActionConfig = z.infer; diff --git a/src/config/schema/actions/stock/navigate.ts b/src/config/schema/actions/stock/navigate.ts new file mode 100644 index 00000000..b7992cf6 --- /dev/null +++ b/src/config/schema/actions/stock/navigate.ts @@ -0,0 +1,9 @@ +import { z } from 'zod'; +import { actionBaseSchema } from '../base'; + +export const navigateActionSchema = actionBaseSchema.extend({ + action: z.literal('navigate'), + navigation_path: z.string(), + navigation_replace: z.boolean().optional(), +}); +export type NavigateActionConfig = z.infer; diff --git a/src/config/schema/actions/stock/none.ts b/src/config/schema/actions/stock/none.ts new file mode 100644 index 00000000..e83d8d74 --- /dev/null +++ b/src/config/schema/actions/stock/none.ts @@ -0,0 +1,7 @@ +import { z } from 'zod'; +import { actionBaseSchema } from '../base'; + +export const noneActionSchema = actionBaseSchema.extend({ + action: z.literal('none'), +}); +export type NoneActionConfig = z.infer; diff --git a/src/config/schema/actions/stock/perform-action.ts b/src/config/schema/actions/stock/perform-action.ts new file mode 100644 index 00000000..1e6deb81 --- /dev/null +++ b/src/config/schema/actions/stock/perform-action.ts @@ -0,0 +1,11 @@ +import { z } from 'zod'; +import { actionBaseSchema } from '../base'; +import { targetSchema } from './target'; + +export const performActionActionSchema = actionBaseSchema.extend({ + action: z.literal('perform-action'), + perform_action: z.string(), + data: z.object({}).passthrough().optional(), + target: targetSchema.optional(), +}); +export type PerformActionActionConfig = z.infer; diff --git a/src/config/schema/actions/stock/target.ts b/src/config/schema/actions/stock/target.ts new file mode 100644 index 00000000..ab1e0704 --- /dev/null +++ b/src/config/schema/actions/stock/target.ts @@ -0,0 +1,10 @@ +import { HassServiceTarget } from 'home-assistant-js-websocket'; +import { z } from 'zod'; + +export const targetSchema: z.ZodSchema = z.object({ + entity_id: z.string().or(z.string().array()).optional(), + device_id: z.string().or(z.string().array()).optional(), + area_id: z.string().or(z.string().array()).optional(), + floor_id: z.string().or(z.string().array()).optional(), + label_id: z.string().or(z.string().array()).optional(), +}); diff --git a/src/config/schema/actions/stock/toggle.ts b/src/config/schema/actions/stock/toggle.ts new file mode 100644 index 00000000..8e1af008 --- /dev/null +++ b/src/config/schema/actions/stock/toggle.ts @@ -0,0 +1,7 @@ +import { z } from 'zod'; +import { actionBaseSchema } from '../base'; + +export const toggleActionSchema = actionBaseSchema.extend({ + action: z.literal('toggle'), +}); +export type ToggleActionConfig = z.infer; diff --git a/src/config/schema/actions/stock/types.ts b/src/config/schema/actions/stock/types.ts new file mode 100644 index 00000000..615f07b1 --- /dev/null +++ b/src/config/schema/actions/stock/types.ts @@ -0,0 +1,20 @@ +import { z } from 'zod'; +import { callServiceActionSchema } from './call-service'; +import { customActionSchema } from './custom'; +import { moreInfoActionSchema } from './more-info'; +import { navigateActionSchema } from './navigate'; +import { noneActionSchema } from './none'; +import { performActionActionSchema } from './perform-action'; +import { toggleActionSchema } from './toggle'; +import { urlActionSchema } from './url'; + +export const stockActionSchema = z.union([ + callServiceActionSchema, + customActionSchema, + moreInfoActionSchema, + navigateActionSchema, + noneActionSchema, + performActionActionSchema, + toggleActionSchema, + urlActionSchema, +]); diff --git a/src/config/schema/actions/stock/url.ts b/src/config/schema/actions/stock/url.ts new file mode 100644 index 00000000..e371a85e --- /dev/null +++ b/src/config/schema/actions/stock/url.ts @@ -0,0 +1,8 @@ +import { z } from 'zod'; +import { actionBaseSchema } from '../base'; + +export const urlActionSchema = actionBaseSchema.extend({ + action: z.literal('url'), + url_path: z.string(), +}); +export type URLActionConfig = z.infer; diff --git a/src/config/schema/actions/types.ts b/src/config/schema/actions/types.ts new file mode 100644 index 00000000..5ac4464d --- /dev/null +++ b/src/config/schema/actions/types.ts @@ -0,0 +1,133 @@ +import { z } from 'zod'; +import { statusBarItemBaseSchema } from '../common/status-bar'; +import { advancedCameraCardCustomActionsBaseSchema } from './custom/base'; +import { cameraSelectActionConfigSchema } from './custom/camera-select'; +import { viewDisplayModeActionConfigSchema } from './custom/display-mode'; +import { generalActionConfigSchema } from './custom/general'; +import { internalCallbackActionConfigSchema } from './custom/internal'; +import { logActionConfigSchema } from './custom/log'; +import { mediaPlayerActionConfigSchema } from './custom/media-player'; +import { ptzActionConfigSchema } from './custom/ptz'; +import { ptzControlsActionConfigSchema } from './custom/ptz-controls'; +import { ptzDigitalActionConfigSchema } from './custom/ptz-digital'; +import { ptzMultiActionSchema } from './custom/ptz-multi'; +import { sleepActionConfigSchema } from './custom/sleep'; +import { substreamSelectActionConfigSchema } from './custom/substream-select'; +import { viewActionConfigSchema } from './custom/view'; +import { stockActionSchema } from './stock/types'; + +// Provide a manual type definition to avoid the `any` that would be created by +// the lazy() evaluation below. +// See: https://zod.dev/?id=recursive-types +export type StatusBarActionConfig = z.infer< + typeof advancedCameraCardCustomActionsBaseSchema +> & { + advanced_camera_card_action: 'status_bar'; + status_bar_action: 'add' | 'remove' | 'reset'; + items?: StatusBarItem[]; +}; +export const statusBarActionConfigSchema: z.ZodSchema< + StatusBarActionConfig, + z.ZodTypeDef, + unknown +> = advancedCameraCardCustomActionsBaseSchema.extend({ + advanced_camera_card_action: z.literal('status_bar'), + status_bar_action: z.enum(['add', 'remove', 'reset']), + items: z + .lazy(() => statusBarItemSchema) + .array() + .optional(), +}); + +const advancedCameraCardCustomActionSchema = z.union([ + cameraSelectActionConfigSchema, + generalActionConfigSchema, + internalCallbackActionConfigSchema, + logActionConfigSchema, + mediaPlayerActionConfigSchema, + ptzActionConfigSchema, + ptzControlsActionConfigSchema, + ptzDigitalActionConfigSchema, + ptzMultiActionSchema, + sleepActionConfigSchema, + statusBarActionConfigSchema, + substreamSelectActionConfigSchema, + viewActionConfigSchema, + viewDisplayModeActionConfigSchema, +]); +export type AdvancedCameraCardCustomActionConfig = z.infer< + typeof advancedCameraCardCustomActionSchema +>; + +export const actionConfigSchema = z.union([ + stockActionSchema, + advancedCameraCardCustomActionSchema, +]); +export type ActionConfig = z.infer; + +export const actionsBaseSchema = z + .object({ + tap_action: actionConfigSchema.or(actionConfigSchema.array()).optional(), + hold_action: actionConfigSchema.or(actionConfigSchema.array()).optional(), + double_tap_action: actionConfigSchema.or(actionConfigSchema.array()).optional(), + start_tap_action: actionConfigSchema.or(actionConfigSchema.array()).optional(), + end_tap_action: actionConfigSchema.or(actionConfigSchema.array()).optional(), + }) + // Passthrough to allow (at least) entity/camera_image to go through. This + // card doesn't need these attributes, but handleAction() in + // custom_card_helpers may depending on how the action is configured. + .passthrough(); +export type Actions = z.infer; + +export interface AuxillaryActionConfig { + entity?: string; +} + +export type ActionsConfig = Actions & AuxillaryActionConfig; + +export const actionsSchema = z.object({ + actions: actionsBaseSchema.optional(), +}); + +// ============================================================================ +// Status Bar Elements +// +// Note: Status Bar action & elements are included in this file, since this is +// the only action that may include content that refers to *other* actions (e.g. +// a status bar action, compromises of status bar items, which themselves may +// have actions). This circular relationship means the components must be +// together in a file, or they will generate typescript circular dependency +// errors. +// ============================================================================ + +const statusBarItemElementsBaseSchema = statusBarItemBaseSchema.extend({ + sufficient: z.boolean().default(false).optional(), + exclusive: z.boolean().default(false).optional(), + expand: z.boolean().default(false).optional(), + actions: actionsBaseSchema.optional(), +}); + +export const statusBarIconItemSchema = statusBarItemElementsBaseSchema.extend({ + type: z.literal('custom:advanced-camera-card-status-bar-icon'), + icon: z.string(), +}); +export type StatusBarIcon = z.infer; + +export const statusBarImageItemSchema = statusBarItemElementsBaseSchema.extend({ + type: z.literal('custom:advanced-camera-card-status-bar-image'), + image: z.string(), +}); +export type StatusBarImage = z.infer; + +export const statusBarStringItemSchema = statusBarItemElementsBaseSchema.extend({ + type: z.literal('custom:advanced-camera-card-status-bar-string'), + string: z.string(), +}); +export type StatusBarString = z.infer; + +const statusBarItemSchema = z.union([ + statusBarIconItemSchema, + statusBarImageItemSchema, + statusBarStringItemSchema, +]); +export type StatusBarItem = z.infer; diff --git a/src/config/schema/automations.ts b/src/config/schema/automations.ts new file mode 100644 index 00000000..62c612a3 --- /dev/null +++ b/src/config/schema/automations.ts @@ -0,0 +1,20 @@ +import { z } from 'zod'; +import { actionConfigSchema } from './actions/types'; +import { advancedCameraCardConditionSchema } from './conditions/types'; + +const automationActionsSchema = actionConfigSchema.array(); +export type AutomationActions = z.infer; + +const automationSchema = z + .object({ + conditions: advancedCameraCardConditionSchema.array(), + actions: automationActionsSchema.optional(), + actions_not: automationActionsSchema.optional(), + }) + .refine( + (data) => data.actions?.length || data.actions_not?.length, + 'Automations must include at least one action', + ); +export type Automation = z.infer; + +export const automationsSchema = automationSchema.array(); diff --git a/src/config/schema/camera/media-layout.ts b/src/config/schema/camera/media-layout.ts new file mode 100644 index 00000000..26b19780 --- /dev/null +++ b/src/config/schema/camera/media-layout.ts @@ -0,0 +1,24 @@ +import { z } from 'zod'; +import { panSchema } from '../common/pan'; +import { zoomSchema } from '../common/zoom'; + +export const mediaLayoutConfigSchema = z.object({ + fit: z.enum(['contain', 'cover', 'fill']).optional(), + position: z + .object({ + x: z.number().min(0).max(100).optional(), + y: z.number().min(0).max(100).optional(), + }) + .optional(), + view_box: z + .object({ + bottom: z.number().min(0).max(100).optional().default(0), + left: z.number().min(0).max(100).optional().default(0), + right: z.number().min(0).max(100).optional().default(0), + top: z.number().min(0).max(100).optional().default(0), + }) + .optional(), + pan: panSchema.optional(), + zoom: zoomSchema.optional(), +}); +export type MediaLayoutConfig = z.infer; diff --git a/src/config/schema/camera/ptz.ts b/src/config/schema/camera/ptz.ts new file mode 100644 index 00000000..8c963aaa --- /dev/null +++ b/src/config/schema/camera/ptz.ts @@ -0,0 +1,93 @@ +import { z } from 'zod'; +import { performActionActionSchema } from '../actions/stock/perform-action'; + +export const ptzCameraConfigDefaults = { + r2c_delay_between_calls_seconds: 0.5, + c2r_delay_between_calls_seconds: 0.2, +}; + +// To avoid lots of YAML duplication, provide an easy way to just specify the +// service data as actions for each PTZ action, and it will be preprocessed +// into the full form. This also provides compatability with the AlexIT/WebRTC +// PTZ configuration. +const dataPTZFormatToFullFormat = function (suffix: string): (data: unknown) => unknown { + return (data) => { + if (!data || typeof data !== 'object' || !data['service']) { + return data; + } + const out = { ...data }; + Object.keys(data).forEach((key) => { + const match = key.match(/^data_(.+)$/); + const name = match?.[1]; + if (name && !(`${suffix}${name}` in data)) { + out[`${suffix}${name}`] = { + action: 'perform-action', + perform_action: data['service'], + data: data[key], + }; + delete out[key]; + delete out['service']; + } + }); + return out; + }; +}; + +export const ptzCameraConfigSchema = z.preprocess( + dataPTZFormatToFullFormat('actions_'), + z + .object({ + actions_left: performActionActionSchema.optional(), + actions_left_start: performActionActionSchema.optional(), + actions_left_stop: performActionActionSchema.optional(), + + actions_right: performActionActionSchema.optional(), + actions_right_start: performActionActionSchema.optional(), + actions_right_stop: performActionActionSchema.optional(), + + actions_up: performActionActionSchema.optional(), + actions_up_start: performActionActionSchema.optional(), + actions_up_stop: performActionActionSchema.optional(), + + actions_down: performActionActionSchema.optional(), + actions_down_start: performActionActionSchema.optional(), + actions_down_stop: performActionActionSchema.optional(), + + actions_zoom_in: performActionActionSchema.optional(), + actions_zoom_in_start: performActionActionSchema.optional(), + actions_zoom_in_stop: performActionActionSchema.optional(), + + actions_zoom_out: performActionActionSchema.optional(), + actions_zoom_out_start: performActionActionSchema.optional(), + actions_zoom_out_stop: performActionActionSchema.optional(), + + // The number of seconds between subsequent relative calls when converting a + // relative request into a continuous request. + r2c_delay_between_calls_seconds: z + .number() + .default(ptzCameraConfigDefaults.r2c_delay_between_calls_seconds), + + // The number of seconds between the start/stop call when converting a + // continuous request into a relative request. + c2r_delay_between_calls_seconds: z + .number() + .default(ptzCameraConfigDefaults.c2r_delay_between_calls_seconds), + + presets: z + .preprocess( + dataPTZFormatToFullFormat(''), + z.union([ + z.record(performActionActionSchema), + + // This is used by the data_ style of action. + z.object({ service: z.string().optional() }), + ]), + ) + .optional(), + + // This is used by the data_ style of action. + service: z.string().optional(), + }) + // We allow passthrough as there may be user-configured presets as "actions_" . + .passthrough(), +); diff --git a/src/config/schema/cameras.ts b/src/config/schema/cameras.ts new file mode 100644 index 00000000..e9c22d39 --- /dev/null +++ b/src/config/schema/cameras.ts @@ -0,0 +1,268 @@ +import { z } from 'zod'; +import { capabilityKeys } from '../../types'; +import { mediaLayoutConfigSchema } from './camera/media-layout'; +import { ptzCameraConfigDefaults, ptzCameraConfigSchema } from './camera/ptz'; +import { aspectRatioSchema } from './common/aspect-ratio'; +import { imageBaseConfigSchema, imageConfigDefault } from './common/image'; + +const CAMERA_TRIGGER_EVENT_TYPES = [ + // An event whether or not it has any media yet associated with it. + 'events', + + // Specific media availability. + 'clips', + 'snapshots', +] as const; +export type CameraTriggerEventType = (typeof CAMERA_TRIGGER_EVENT_TYPES)[number]; + +// ************************************************************************* +// Live Provider Configuration +// ************************************************************************* + +const LIVE_PROVIDERS = [ + 'auto', + 'image', + 'ha', + 'jsmpeg', + 'go2rtc', + 'webrtc-card', +] as const; +export type LiveProvider = (typeof LIVE_PROVIDERS)[number]; + +const go2rtcConfigSchema = z.object({ + url: z + .string() + .transform((input) => input.replace(/\/+$/, '')) + .optional(), + host: z.string().optional(), + modes: z.enum(['webrtc', 'mse', 'mp4', 'mjpeg']).array().optional(), + stream: z.string().optional(), +}); + +const webrtcCardConfigSchema = z + .object({ + entity: z.string().optional(), + url: z.string().optional(), + }) + .passthrough(); + +const jsmpegConfigSchema = z.object({ + options: z + .object({ + // https://github.com/phoboslab/jsmpeg#usage + audio: z.boolean().optional(), + video: z.boolean().optional(), + pauseWhenHidden: z.boolean().optional(), + disableGl: z.boolean().optional(), + disableWebAssembly: z.boolean().optional(), + preserveDrawingBuffer: z.boolean().optional(), + progressive: z.boolean().optional(), + throttled: z.boolean().optional(), + chunkSize: z.number().optional(), + maxAudioLag: z.number().optional(), + videoBufferSize: z.number().optional(), + audioBufferSize: z.number().optional(), + }) + .optional(), +}); + +// ************************************************************************* +// Cast Configuration +// ************************************************************************* + +const castConfigDefault = { + method: 'standard' as const, +}; + +const castSchema = z.object({ + method: z.enum(['standard', 'dashboard']).default(castConfigDefault.method).optional(), + dashboard: z + .object({ + dashboard_path: z.string().optional(), + view_path: z.string().optional(), + }) + .optional(), +}); + +// ************************************************************************* +// Camera Configuration +// ************************************************************************* + +const ENGINES = ['auto', 'frigate', 'generic', 'motioneye', 'reolink'] as const; + +export const cameraConfigDefault = { + dependencies: { + all_cameras: false, + cameras: [], + }, + engine: 'auto' as const, + frigate: { + client_id: 'frigate' as const, + }, + live_provider: 'auto' as const, + motioneye: { + images: { + directory_pattern: '%Y-%m-%d' as const, + file_pattern: '%H-%M-%S' as const, + }, + movies: { + directory_pattern: '%Y-%m-%d' as const, + file_pattern: '%H-%M-%S' as const, + }, + }, + reolink: { + media_resolution: 'low' as const, + }, + ptz: ptzCameraConfigDefaults, + triggers: { + motion: false, + occupancy: false, + events: [...CAMERA_TRIGGER_EVENT_TYPES], + entities: [], + }, + proxy: { + media: 'auto' as const, + dynamic: true, + ssl_verification: 'auto' as const, + ssl_ciphers: 'auto' as const, + }, + always_error_if_entity_unavailable: false, +}; + +const SSL_CIPHERS = ['default', 'insecure', 'intermediate', 'modern'] as const; +export type SSLCiphers = (typeof SSL_CIPHERS)[number]; + +const proxyConfigSchema = z.object({ + media: z.boolean().or(z.literal('auto')).default(cameraConfigDefault.proxy.media), + dynamic: z.boolean().default(cameraConfigDefault.proxy.dynamic), + ssl_verification: z + .boolean() + .or(z.literal('auto')) + .default(cameraConfigDefault.proxy.ssl_verification), + ssl_ciphers: z + .enum(SSL_CIPHERS) + .or(z.literal('auto')) + .default(cameraConfigDefault.proxy.ssl_ciphers), +}); +export type ProxyConfig = z.infer; + +export const cameraConfigSchema = z + .object({ + camera_entity: z.string().optional(), + + // Used for presentation in the UI (autodetected from the entity if + // specified). + icon: z.string().optional(), + title: z.string().optional(), + + capabilities: z + .object({ + disable: z.enum(capabilityKeys).array().optional(), + disable_except: z.enum(capabilityKeys).array().optional(), + }) + .optional(), + + // Optional identifier to separate different camera configurations used in + // this card. + id: z.string().optional(), + + dependencies: z + .object({ + all_cameras: z.boolean().default(cameraConfigDefault.dependencies.all_cameras), + cameras: z.string().array().default(cameraConfigDefault.dependencies.cameras), + }) + .default(cameraConfigDefault.dependencies), + + triggers: z + .object({ + motion: z.boolean().default(cameraConfigDefault.triggers.motion), + occupancy: z.boolean().default(cameraConfigDefault.triggers.occupancy), + entities: z.string().array().default(cameraConfigDefault.triggers.entities), + events: z + .enum(CAMERA_TRIGGER_EVENT_TYPES) + .array() + .default(cameraConfigDefault.triggers.events), + }) + .default(cameraConfigDefault.triggers), + + // Engine options. + engine: z.enum(ENGINES).default('auto'), + frigate: z + .object({ + url: z.string().optional(), + client_id: z.string().default(cameraConfigDefault.frigate.client_id), + camera_name: z.string().optional(), + labels: z.string().array().optional(), + zones: z.string().array().optional(), + }) + .default(cameraConfigDefault.frigate), + motioneye: z + .object({ + url: z.string().optional(), + images: z + .object({ + directory_pattern: z + .string() + .includes('%') + .default(cameraConfigDefault.motioneye.images.directory_pattern), + file_pattern: z + .string() + .includes('%') + .default(cameraConfigDefault.motioneye.images.file_pattern), + }) + .default(cameraConfigDefault.motioneye.images), + movies: z + .object({ + directory_pattern: z + .string() + .includes('%') + .default(cameraConfigDefault.motioneye.movies.directory_pattern), + file_pattern: z + .string() + .includes('%') + .default(cameraConfigDefault.motioneye.movies.file_pattern), + }) + .default(cameraConfigDefault.motioneye.movies), + }) + .default(cameraConfigDefault.motioneye), + reolink: z + .object({ + url: z.string().optional(), + media_resolution: z + .enum(['high', 'low']) + .default(cameraConfigDefault.reolink.media_resolution), + }) + .default(cameraConfigDefault.reolink), + + // Live provider options. + live_provider: z.enum(LIVE_PROVIDERS).default(cameraConfigDefault.live_provider), + go2rtc: go2rtcConfigSchema.optional(), + image: imageBaseConfigSchema.optional().default(imageConfigDefault), + jsmpeg: jsmpegConfigSchema.optional(), + webrtc_card: webrtcCardConfigSchema.optional(), + + cast: castSchema.optional(), + + ptz: ptzCameraConfigSchema.default(cameraConfigDefault.ptz), + + dimensions: z + .object({ + aspect_ratio: aspectRatioSchema.optional(), + layout: mediaLayoutConfigSchema.optional(), + }) + .optional(), + + proxy: proxyConfigSchema.default(cameraConfigDefault.proxy), + + // See: https://github.com/dermotduffy/advanced-camera-card/issues/1650 + always_error_if_entity_unavailable: z + .boolean() + .default(cameraConfigDefault.always_error_if_entity_unavailable), + }) + .default(cameraConfigDefault); +export type CameraConfig = z.infer; + +// Avoid using .nonempty() to avoid changing the inferred type +// (https://github.com/colinhacks/zod#minmaxlength). +export const camerasConfigSchema = cameraConfigSchema.array().min(1); +export type CamerasConfig = z.infer; diff --git a/src/config/schema/common/aspect-ratio.ts b/src/config/schema/common/aspect-ratio.ts new file mode 100644 index 00000000..60a2b88a --- /dev/null +++ b/src/config/schema/common/aspect-ratio.ts @@ -0,0 +1,12 @@ +import { z } from 'zod'; + +export const aspectRatioSchema = z + .number() + .array() + .length(2) + .or( + z + .string() + .regex(/^\s*\d+\.?\d*\s*[:/]\s*\d+\.?\d*\s*$/) + .transform((input) => input.split(/[:\/]/).map((d) => Number(d))), + ); diff --git a/src/config/schema/common/const.ts b/src/config/schema/common/const.ts new file mode 100644 index 00000000..e922cdce --- /dev/null +++ b/src/config/schema/common/const.ts @@ -0,0 +1,35 @@ +export const cardIDRegex = /^[-\w]+$/; + +export const MENU_PRIORITY_DEFAULT = 50; +export const MENU_PRIORITY_MAX = 100; + +export const STATUS_BAR_PRIORITY_DEFAULT = 50; +export const STATUS_BAR_PRIORITY_MAX = 100; + +export const BUTTON_SIZE_MIN = 20; + +// The default view (may not be supported on all cameras). +export const VIEW_DEFAULT = 'live' as const; + +export const VIEWS_USER_SPECIFIED = [ + 'diagnostics', + 'live', + 'clip', + 'clips', + 'snapshot', + 'snapshots', + 'recording', + 'recordings', + 'image', + 'timeline', +] as const; +export type AdvancedCameraCardUserSpecifiedView = (typeof VIEWS_USER_SPECIFIED)[number]; + +const VIEWS = [ + ...VIEWS_USER_SPECIFIED, + 'diagnostics', + + // Media: A generic piece of media (could be clip, snapshot, recording). + 'media', +] as const; +export type AdvancedCameraCardView = (typeof VIEWS)[number]; diff --git a/src/config/schema/common/controls/next-previous.ts b/src/config/schema/common/controls/next-previous.ts new file mode 100644 index 00000000..4bd90385 --- /dev/null +++ b/src/config/schema/common/controls/next-previous.ts @@ -0,0 +1,8 @@ +import { z } from 'zod'; +import { BUTTON_SIZE_MIN } from '../const'; + +export const nextPreviousControlConfigSchema = z.object({ + style: z.enum(['none', 'chevrons', 'icons', 'thumbnails']), + size: z.number().min(BUTTON_SIZE_MIN), +}); +export type NextPreviousControlConfig = z.infer; diff --git a/src/config/schema/common/controls/ptz.ts b/src/config/schema/common/controls/ptz.ts new file mode 100644 index 00000000..06037a52 --- /dev/null +++ b/src/config/schema/common/controls/ptz.ts @@ -0,0 +1,27 @@ +import { z } from 'zod'; + +export const ptzControlsDefaults = { + orientation: 'horizontal' as const, + mode: 'auto' as const, + hide_pan_tilt: false, + hide_zoom: false, + hide_home: false, + position: 'bottom-right' as const, +}; + +export const ptzControlsConfigSchema = z.object({ + mode: z.enum(['off', 'auto', 'on']).default(ptzControlsDefaults.mode), + position: z + .enum(['top-left', 'top-right', 'bottom-left', 'bottom-right']) + .default(ptzControlsDefaults.position), + orientation: z + .enum(['vertical', 'horizontal']) + .default(ptzControlsDefaults.orientation), + + hide_pan_tilt: z.boolean().default(ptzControlsDefaults.hide_pan_tilt), + hide_zoom: z.boolean().default(ptzControlsDefaults.hide_zoom), + hide_home: z.boolean().default(ptzControlsDefaults.hide_home), + + style: z.object({}).passthrough().optional(), +}); +export type PTZControlsConfig = z.infer; diff --git a/src/config/schema/common/controls/thumbnails.ts b/src/config/schema/common/controls/thumbnails.ts new file mode 100644 index 00000000..49211d33 --- /dev/null +++ b/src/config/schema/common/controls/thumbnails.ts @@ -0,0 +1,45 @@ +import { z } from 'zod'; + +// The min/max width thumbnail. +export const THUMBNAIL_WIDTH_MIN = 75; +export const THUMBNAIL_WIDTH_MAX = 300; + +const thumbnailControlsBaseDefaults = { + size: 100, + show_details: true, + show_favorite_control: true, + show_timeline_control: true, + show_download_control: true, +}; + +// Configuration for the actual rendered thumbnail. +const thumbnailsControlBaseSchema = z.object({ + size: z + .number() + .min(THUMBNAIL_WIDTH_MIN) + .max(THUMBNAIL_WIDTH_MAX) + .default(thumbnailControlsBaseDefaults.size), + show_details: z.boolean().default(thumbnailControlsBaseDefaults.show_details), + show_favorite_control: z + .boolean() + .default(thumbnailControlsBaseDefaults.show_favorite_control), + show_timeline_control: z + .boolean() + .default(thumbnailControlsBaseDefaults.show_timeline_control), + show_download_control: z + .boolean() + .default(thumbnailControlsBaseDefaults.show_download_control), +}); +export type ThumbnailsControlBaseConfig = z.infer; + +export const thumbnailControlsDefaults = { + ...thumbnailControlsBaseDefaults, + mode: 'right' as const, +}; + +export const thumbnailsControlSchema = thumbnailsControlBaseSchema.extend({ + mode: z + .enum(['none', 'above', 'below', 'left', 'right']) + .default(thumbnailControlsDefaults.mode), +}); +export type ThumbnailsControlConfig = z.infer; diff --git a/src/config/schema/common/controls/timeline.ts b/src/config/schema/common/controls/timeline.ts new file mode 100644 index 00000000..1d550867 --- /dev/null +++ b/src/config/schema/common/controls/timeline.ts @@ -0,0 +1,67 @@ +import { z } from 'zod'; +import { eventsMediaTypeSchema } from '../events-media'; +import { thumbnailControlsDefaults } from './thumbnails'; + +const timelineCoreConfigDefault = { + clustering_threshold: 3, + events_media_type: 'all' as const, + window_seconds: 60 * 60, + show_recordings: true, + style: 'stack' as const, + pan_mode: 'pan' as const, + format: { + '24h': true, + }, +}; + +const timelinePanModeSchema = z.enum(['pan', 'seek', 'seek-in-media', 'seek-in-camera']); +export type TimelinePanMode = z.infer; + +const timelineFormatSchema = z.object({ + '24h': z.boolean().optional().default(timelineCoreConfigDefault.format['24h']), +}); + +export const timelineCoreConfigSchema = z.object({ + clustering_threshold: z + .number() + .optional() + .default(timelineCoreConfigDefault.clustering_threshold), + events_media_type: eventsMediaTypeSchema + .optional() + .default(timelineCoreConfigDefault.events_media_type), + window_seconds: z + .number() + .min(1 * 60) + .max(24 * 60 * 60) + .optional() + .default(timelineCoreConfigDefault.window_seconds), + show_recordings: z + .boolean() + .optional() + .default(timelineCoreConfigDefault.show_recordings), + style: z.enum(['stack', 'ribbon']).optional().default(timelineCoreConfigDefault.style), + pan_mode: timelinePanModeSchema.optional().default(timelineCoreConfigDefault.pan_mode), + format: timelineFormatSchema.optional().default(timelineCoreConfigDefault.format), +}); +export type TimelineCoreConfig = z.infer; + +export const miniTimelineConfigDefault = { + ...timelineCoreConfigDefault, + mode: 'none' as const, + + // Mini-timeline defaults to ribbon style. + style: 'ribbon' as const, +}; + +export const miniTimelineConfigSchema = timelineCoreConfigSchema.extend({ + mode: z.enum(['none', 'above', 'below']).default(miniTimelineConfigDefault.mode), + style: timelineCoreConfigSchema.shape.style.default(miniTimelineConfigDefault.style), +}); +export type MiniTimelineControlConfig = z.infer; + +export const timelineConfigDefault = { + ...timelineCoreConfigDefault, + controls: { + thumbnails: thumbnailControlsDefaults, + }, +}; diff --git a/src/config/schema/common/display.ts b/src/config/schema/common/display.ts new file mode 100644 index 00000000..1b47cb3c --- /dev/null +++ b/src/config/schema/common/display.ts @@ -0,0 +1,14 @@ +import { z } from 'zod'; + +export const viewDisplayModeSchema = z.enum(['single', 'grid']); +export type ViewDisplayMode = z.infer; + +export const viewDisplaySchema = z + .object({ + mode: viewDisplayModeSchema.optional(), + grid_selected_width_factor: z.number().min(0).optional(), + grid_max_columns: z.number().min(0).optional(), + grid_columns: z.number().min(0).optional(), + }) + .optional(); +export type ViewDisplayConfig = z.infer; diff --git a/src/config/schema/common/events-media.ts b/src/config/schema/common/events-media.ts new file mode 100644 index 00000000..2c854441 --- /dev/null +++ b/src/config/schema/common/events-media.ts @@ -0,0 +1,3 @@ +import { z } from 'zod'; + +export const eventsMediaTypeSchema = z.enum(['all', 'clips', 'snapshots']); diff --git a/src/config/schema/common/image.ts b/src/config/schema/common/image.ts new file mode 100644 index 00000000..8569eeb4 --- /dev/null +++ b/src/config/schema/common/image.ts @@ -0,0 +1,20 @@ +import { z } from 'zod'; + +export const imageConfigDefault = { + mode: 'auto' as const, + refresh_seconds: 1, + zoomable: true, +}; + +const IMAGE_MODES = ['auto', 'camera', 'entity', 'screensaver', 'url'] as const; +export type ImageMode = (typeof IMAGE_MODES)[number]; + +export const imageBaseConfigSchema = z.object({ + mode: z.enum(IMAGE_MODES).default(imageConfigDefault.mode), + + refresh_seconds: z.number().min(0).default(imageConfigDefault.refresh_seconds), + + url: z.string().optional(), + entity: z.string().optional(), + entity_parameters: z.string().optional(), +}); diff --git a/src/config/schema/common/media-actions.ts b/src/config/schema/common/media-actions.ts new file mode 100644 index 00000000..51bbe8a4 --- /dev/null +++ b/src/config/schema/common/media-actions.ts @@ -0,0 +1,19 @@ +export const MEDIA_ACTION_POSITIVE_CONDITIONS = ['selected', 'visible'] as const; +export const MEDIA_ACTION_NEGATIVE_CONDITIONS = ['unselected', 'hidden'] as const; + +export const MEDIA_MUTE_CONDITIONS = [ + ...MEDIA_ACTION_NEGATIVE_CONDITIONS, + 'microphone', +] as const; + +export const MEDIA_UNMUTE_CONDITIONS = [ + ...MEDIA_ACTION_POSITIVE_CONDITIONS, + 'microphone', +] as const; + +export type AutoPlayCondition = (typeof MEDIA_ACTION_POSITIVE_CONDITIONS)[number]; +export type AutoPauseCondition = (typeof MEDIA_ACTION_NEGATIVE_CONDITIONS)[number]; +export type AutoMuteCondition = (typeof MEDIA_MUTE_CONDITIONS)[number]; +export type AutoUnmuteCondition = (typeof MEDIA_UNMUTE_CONDITIONS)[number]; + +export type LazyUnloadCondition = (typeof MEDIA_ACTION_NEGATIVE_CONDITIONS)[number]; diff --git a/src/config/schema/common/pan.ts b/src/config/schema/common/pan.ts new file mode 100644 index 00000000..0793873c --- /dev/null +++ b/src/config/schema/common/pan.ts @@ -0,0 +1,6 @@ +import { z } from 'zod'; + +export const panSchema = z.object({ + x: z.number().min(0).max(100).optional(), + y: z.number().min(0).max(100).optional(), +}); diff --git a/src/config/schema/common/status-bar.ts b/src/config/schema/common/status-bar.ts new file mode 100644 index 00000000..d7227b7a --- /dev/null +++ b/src/config/schema/common/status-bar.ts @@ -0,0 +1,12 @@ +import { z } from 'zod'; +import { STATUS_BAR_PRIORITY_DEFAULT, STATUS_BAR_PRIORITY_MAX } from './const'; + +export const statusBarItemBaseSchema = z.object({ + enabled: z.boolean().default(true).optional(), + priority: z + .number() + .min(0) + .max(STATUS_BAR_PRIORITY_MAX) + .default(STATUS_BAR_PRIORITY_DEFAULT) + .optional(), +}); diff --git a/src/config/schema/common/transition-effect.ts b/src/config/schema/common/transition-effect.ts new file mode 100644 index 00000000..ff6a8297 --- /dev/null +++ b/src/config/schema/common/transition-effect.ts @@ -0,0 +1,4 @@ +import { z } from 'zod'; + +export const transitionEffectConfigSchema = z.enum(['none', 'slide']); +export type TransitionEffect = z.infer; diff --git a/src/config/schema/common/zoom.ts b/src/config/schema/common/zoom.ts new file mode 100644 index 00000000..79971359 --- /dev/null +++ b/src/config/schema/common/zoom.ts @@ -0,0 +1,6 @@ +import { z } from 'zod'; + +export const ZOOM_MIN = 1; +export const ZOOM_MAX = 10; + +export const zoomSchema = z.number().min(ZOOM_MIN).max(ZOOM_MAX); diff --git a/src/config/schema/conditions/custom/camera.ts b/src/config/schema/conditions/custom/camera.ts new file mode 100644 index 00000000..b1de86bc --- /dev/null +++ b/src/config/schema/conditions/custom/camera.ts @@ -0,0 +1,6 @@ +import { z } from 'zod'; + +export const cameraConditionSchema = z.object({ + condition: z.literal('camera'), + cameras: z.string().array().optional(), +}); diff --git a/src/config/schema/conditions/custom/config.ts b/src/config/schema/conditions/custom/config.ts new file mode 100644 index 00000000..0db0e5b8 --- /dev/null +++ b/src/config/schema/conditions/custom/config.ts @@ -0,0 +1,6 @@ +import { z } from 'zod'; + +export const configConditionSchema = z.object({ + condition: z.literal('config'), + paths: z.string().array().optional(), +}); diff --git a/src/config/schema/conditions/custom/display-mode.ts b/src/config/schema/conditions/custom/display-mode.ts new file mode 100644 index 00000000..897545b7 --- /dev/null +++ b/src/config/schema/conditions/custom/display-mode.ts @@ -0,0 +1,7 @@ +import { z } from 'zod'; +import { viewDisplayModeSchema } from '../../common/display'; + +export const displayModeConditionSchema = z.object({ + condition: z.literal('display_mode'), + display_mode: viewDisplayModeSchema, +}); diff --git a/src/config/schema/conditions/custom/expand.ts b/src/config/schema/conditions/custom/expand.ts new file mode 100644 index 00000000..158d92f4 --- /dev/null +++ b/src/config/schema/conditions/custom/expand.ts @@ -0,0 +1,6 @@ +import { z } from 'zod'; + +export const expandConditionSchema = z.object({ + condition: z.literal('expand'), + expand: z.boolean(), +}); diff --git a/src/config/schema/conditions/custom/fullscreen.ts b/src/config/schema/conditions/custom/fullscreen.ts new file mode 100644 index 00000000..5fc9f112 --- /dev/null +++ b/src/config/schema/conditions/custom/fullscreen.ts @@ -0,0 +1,6 @@ +import { z } from 'zod'; + +export const fullscreenConditionSchema = z.object({ + condition: z.literal('fullscreen'), + fullscreen: z.boolean(), +}); diff --git a/src/config/schema/conditions/custom/initialized.ts b/src/config/schema/conditions/custom/initialized.ts new file mode 100644 index 00000000..086d8d3f --- /dev/null +++ b/src/config/schema/conditions/custom/initialized.ts @@ -0,0 +1,5 @@ +import { z } from 'zod'; + +export const initializedConditionSchema = z.object({ + condition: z.literal('initialized'), +}); diff --git a/src/config/schema/conditions/custom/interaction.ts b/src/config/schema/conditions/custom/interaction.ts new file mode 100644 index 00000000..77fee551 --- /dev/null +++ b/src/config/schema/conditions/custom/interaction.ts @@ -0,0 +1,6 @@ +import { z } from 'zod'; + +export const interactionConditionSchema = z.object({ + condition: z.literal('interaction'), + interaction: z.boolean(), +}); diff --git a/src/config/schema/conditions/custom/key.ts b/src/config/schema/conditions/custom/key.ts new file mode 100644 index 00000000..2d2100ba --- /dev/null +++ b/src/config/schema/conditions/custom/key.ts @@ -0,0 +1,11 @@ +import { z } from 'zod'; + +export const keyConditionSchema = z.object({ + condition: z.literal('key'), + key: z.string(), + state: z.enum(['down', 'up']).optional(), + ctrl: z.boolean().optional(), + shift: z.boolean().optional(), + alt: z.boolean().optional(), + meta: z.boolean().optional(), +}); diff --git a/src/config/schema/conditions/custom/media-loaded.ts b/src/config/schema/conditions/custom/media-loaded.ts new file mode 100644 index 00000000..aeb4df52 --- /dev/null +++ b/src/config/schema/conditions/custom/media-loaded.ts @@ -0,0 +1,6 @@ +import { z } from 'zod'; + +export const mediaLoadedConditionSchema = z.object({ + condition: z.literal('media_loaded'), + media_loaded: z.boolean(), +}); diff --git a/src/config/schema/conditions/custom/microphone.ts b/src/config/schema/conditions/custom/microphone.ts new file mode 100644 index 00000000..0407b2f0 --- /dev/null +++ b/src/config/schema/conditions/custom/microphone.ts @@ -0,0 +1,7 @@ +import { z } from 'zod'; + +export const microphoneConditionSchema = z.object({ + condition: z.literal('microphone'), + connected: z.boolean().optional(), + muted: z.boolean().optional(), +}); diff --git a/src/config/schema/conditions/custom/triggered.ts b/src/config/schema/conditions/custom/triggered.ts new file mode 100644 index 00000000..5ed8364c --- /dev/null +++ b/src/config/schema/conditions/custom/triggered.ts @@ -0,0 +1,6 @@ +import { z } from 'zod'; + +export const triggeredConditionSchema = z.object({ + condition: z.literal('triggered'), + triggered: z.string().array(), +}); diff --git a/src/config/schema/conditions/custom/user-agent.ts b/src/config/schema/conditions/custom/user-agent.ts new file mode 100644 index 00000000..49086c1e --- /dev/null +++ b/src/config/schema/conditions/custom/user-agent.ts @@ -0,0 +1,21 @@ +import { z } from 'zod'; + +export const userAgentConditionSchema = z.object({ + condition: z.literal('user_agent'), + user_agent: z.string().optional(), + user_agent_re: z + .string() + .refine( + (val) => { + try { + new RegExp(val); + } catch { + return false; + } + return true; + }, + { message: 'Invalid regular expression' }, + ) + .optional(), + companion: z.boolean().optional(), +}); diff --git a/src/config/schema/conditions/custom/view.ts b/src/config/schema/conditions/custom/view.ts new file mode 100644 index 00000000..893af791 --- /dev/null +++ b/src/config/schema/conditions/custom/view.ts @@ -0,0 +1,6 @@ +import { z } from 'zod'; + +export const viewConditionSchema = z.object({ + condition: z.literal('view'), + views: z.string().array().optional(), +}); diff --git a/src/config/schema/conditions/stock/numeric.ts b/src/config/schema/conditions/stock/numeric.ts new file mode 100644 index 00000000..c83e02ff --- /dev/null +++ b/src/config/schema/conditions/stock/numeric.ts @@ -0,0 +1,9 @@ +import { z } from 'zod'; + +// https://www.home-assistant.io/dashboards/conditional/#numeric-state +export const numericStateConditionSchema = z.object({ + condition: z.literal('numeric_state'), + entity: z.string(), + above: z.number().optional(), + below: z.number().optional(), +}); diff --git a/src/config/schema/conditions/stock/screen.ts b/src/config/schema/conditions/stock/screen.ts new file mode 100644 index 00000000..a59d10a5 --- /dev/null +++ b/src/config/schema/conditions/stock/screen.ts @@ -0,0 +1,7 @@ +import { z } from 'zod'; + +// https://www.home-assistant.io/dashboards/conditional/#screen +export const screenConditionSchema = z.object({ + condition: z.literal('screen'), + media_query: z.string(), +}); diff --git a/src/config/schema/conditions/stock/state.ts b/src/config/schema/conditions/stock/state.ts new file mode 100644 index 00000000..e3c2a853 --- /dev/null +++ b/src/config/schema/conditions/stock/state.ts @@ -0,0 +1,12 @@ +import { z } from 'zod'; + +// https://www.home-assistant.io/dashboards/conditional/#state +export const stateConditionSchema = z.object({ + // If the condition is not specified, a state condition is assumed. This + // allows the syntax to match a picture elements conditional: + // https://www.home-assistant.io/dashboards/picture-elements/#conditional-element + condition: z.literal('state').optional(), + entity: z.string(), + state: z.string().or(z.string().array()).optional(), + state_not: z.string().or(z.string().array()).optional(), +}); diff --git a/src/config/schema/conditions/stock/types.ts b/src/config/schema/conditions/stock/types.ts new file mode 100644 index 00000000..32cace37 --- /dev/null +++ b/src/config/schema/conditions/stock/types.ts @@ -0,0 +1,13 @@ +import { z } from 'zod'; +import { numericStateConditionSchema } from './numeric'; +import { screenConditionSchema } from './screen'; +import { stateConditionSchema } from './state'; +import { usersConditionSchema } from './users'; + +export const stockConditionSchema = z.discriminatedUnion('condition', [ + stateConditionSchema, + numericStateConditionSchema, + screenConditionSchema, + usersConditionSchema, +]); +export type StockCondition = z.infer; diff --git a/src/config/schema/conditions/stock/users.ts b/src/config/schema/conditions/stock/users.ts new file mode 100644 index 00000000..8884ec26 --- /dev/null +++ b/src/config/schema/conditions/stock/users.ts @@ -0,0 +1,7 @@ +import { z } from 'zod'; + +// https://www.home-assistant.io/dashboards/conditional/#user +export const usersConditionSchema = z.object({ + condition: z.literal('user'), + users: z.string().array().min(1), +}); diff --git a/src/config/schema/conditions/types.ts b/src/config/schema/conditions/types.ts new file mode 100644 index 00000000..cd17dd99 --- /dev/null +++ b/src/config/schema/conditions/types.ts @@ -0,0 +1,44 @@ +import { z } from 'zod'; +import { cameraConditionSchema } from './custom/camera'; +import { configConditionSchema } from './custom/config'; +import { displayModeConditionSchema } from './custom/display-mode'; +import { expandConditionSchema } from './custom/expand'; +import { fullscreenConditionSchema } from './custom/fullscreen'; +import { initializedConditionSchema } from './custom/initialized'; +import { interactionConditionSchema } from './custom/interaction'; +import { keyConditionSchema } from './custom/key'; +import { mediaLoadedConditionSchema } from './custom/media-loaded'; +import { microphoneConditionSchema } from './custom/microphone'; +import { triggeredConditionSchema } from './custom/triggered'; +import { userAgentConditionSchema } from './custom/user-agent'; +import { viewConditionSchema } from './custom/view'; +import { numericStateConditionSchema } from './stock/numeric'; +import { screenConditionSchema } from './stock/screen'; +import { stateConditionSchema } from './stock/state'; +import { usersConditionSchema } from './stock/users'; + +export const advancedCameraCardConditionSchema = z.discriminatedUnion('condition', [ + // Stock conditions: + numericStateConditionSchema, + screenConditionSchema, + stateConditionSchema, + usersConditionSchema, + + // Custom conditions: + cameraConditionSchema, + configConditionSchema, + displayModeConditionSchema, + expandConditionSchema, + fullscreenConditionSchema, + initializedConditionSchema, + interactionConditionSchema, + keyConditionSchema, + mediaLoadedConditionSchema, + microphoneConditionSchema, + triggeredConditionSchema, + userAgentConditionSchema, + viewConditionSchema, +]); +export type AdvancedCameraCardCondition = z.infer< + typeof advancedCameraCardConditionSchema +>; diff --git a/src/config/schema/debug.ts b/src/config/schema/debug.ts new file mode 100644 index 00000000..bb0cce39 --- /dev/null +++ b/src/config/schema/debug.ts @@ -0,0 +1,12 @@ +import { z } from 'zod'; + +export const debugConfigDefault = { + logging: false, +}; + +export const debugConfigSchema = z + .object({ + logging: z.boolean().default(debugConfigDefault.logging), + }) + .default(debugConfigDefault); +export type DebugConfig = z.infer; diff --git a/src/config/schema/dimensions.ts b/src/config/schema/dimensions.ts new file mode 100644 index 00000000..6ae24982 --- /dev/null +++ b/src/config/schema/dimensions.ts @@ -0,0 +1,18 @@ +import { z } from 'zod'; +import { aspectRatioSchema } from './common/aspect-ratio'; + +const dimensionsConfigDefault = { + aspect_ratio_mode: 'dynamic' as const, + aspect_ratio: [16, 9], + height: 'auto', +}; + +export const dimensionsConfigSchema = z + .object({ + aspect_ratio_mode: z + .enum(['dynamic', 'static', 'unconstrained']) + .default(dimensionsConfigDefault.aspect_ratio_mode), + aspect_ratio: aspectRatioSchema.default(dimensionsConfigDefault.aspect_ratio), + height: z.string().default(dimensionsConfigDefault.height), + }) + .default(dimensionsConfigDefault); diff --git a/src/config/schema/elements/base.ts b/src/config/schema/elements/base.ts new file mode 100644 index 00000000..9da19578 --- /dev/null +++ b/src/config/schema/elements/base.ts @@ -0,0 +1,7 @@ +import { z } from 'zod'; +import { actionsBaseSchema } from '../actions/types'; + +export const elementsBaseSchema = actionsBaseSchema.extend({ + style: z.record(z.string().nullable().or(z.undefined()).or(z.number())).optional(), + title: z.string().nullable().optional(), +}); diff --git a/src/config/schema/elements/custom/menu/base.ts b/src/config/schema/elements/custom/menu/base.ts new file mode 100644 index 00000000..c03ca165 --- /dev/null +++ b/src/config/schema/elements/custom/menu/base.ts @@ -0,0 +1,15 @@ +import { z } from 'zod'; +import { MENU_PRIORITY_DEFAULT, MENU_PRIORITY_MAX } from '../../../common/const'; + +export const menuBaseSchema = z.object({ + enabled: z.boolean().default(true).optional(), + priority: z + .number() + .min(0) + .max(MENU_PRIORITY_MAX) + .default(MENU_PRIORITY_DEFAULT) + .optional(), + alignment: z.enum(['matching', 'opposing']).default('matching').optional(), + icon: z.string().optional(), + permanent: z.boolean().default(false).optional(), +}); diff --git a/src/config/schema/elements/custom/menu/icon.ts b/src/config/schema/elements/custom/menu/icon.ts new file mode 100644 index 00000000..b9cd0da4 --- /dev/null +++ b/src/config/schema/elements/custom/menu/icon.ts @@ -0,0 +1,8 @@ +import { z } from 'zod'; +import { iconSchema } from '../../stock/icon'; +import { menuBaseSchema } from './base'; + +export const menuIconSchema = menuBaseSchema.merge(iconSchema).extend({ + type: z.literal('custom:advanced-camera-card-menu-icon'), +}); +export type MenuIcon = z.infer; diff --git a/src/config/schema/elements/custom/menu/state-icon.ts b/src/config/schema/elements/custom/menu/state-icon.ts new file mode 100644 index 00000000..bc6e2141 --- /dev/null +++ b/src/config/schema/elements/custom/menu/state-icon.ts @@ -0,0 +1,11 @@ +import { z } from 'zod'; +import { stateIconSchema } from '../../stock/state-icon'; +import { menuBaseSchema } from './base'; + +export const menuStateIconSchema = menuBaseSchema + .merge(stateIconSchema) + .extend({ + type: z.literal('custom:advanced-camera-card-menu-state-icon'), + }) + .merge(menuBaseSchema); +export type MenuStateIcon = z.infer; diff --git a/src/config/schema/elements/custom/menu/submenu-select.ts b/src/config/schema/elements/custom/menu/submenu-select.ts new file mode 100644 index 00000000..2727921e --- /dev/null +++ b/src/config/schema/elements/custom/menu/submenu-select.ts @@ -0,0 +1,10 @@ +import { z } from 'zod'; +import { stateIconSchema } from '../../stock/state-icon'; +import { menuBaseSchema } from './base'; +import { menuSubmenuItemSchema } from './submenu'; + +export const menuSubmenuSelectSchema = menuBaseSchema.merge(stateIconSchema).extend({ + type: z.literal('custom:advanced-camera-card-menu-submenu-select'), + options: z.record(menuSubmenuItemSchema.deepPartial()).optional(), +}); +export type MenuSubmenuSelect = z.infer; diff --git a/src/config/schema/elements/custom/menu/submenu.ts b/src/config/schema/elements/custom/menu/submenu.ts new file mode 100644 index 00000000..ad358d6f --- /dev/null +++ b/src/config/schema/elements/custom/menu/submenu.ts @@ -0,0 +1,20 @@ +import { z } from 'zod'; +import { elementsBaseSchema } from '../../base'; +import { iconSchema } from '../../stock/icon'; +import { menuBaseSchema } from './base'; + +export const menuSubmenuItemSchema = elementsBaseSchema.extend({ + entity: z.string().optional(), + icon: z.string().optional(), + state_color: z.boolean().default(true), + selected: z.boolean().default(false), + subtitle: z.string().optional(), + enabled: z.boolean().default(true), +}); +export type MenuSubmenuItem = z.infer; + +export const menuSubmenuSchema = menuBaseSchema.merge(iconSchema).extend({ + type: z.literal('custom:advanced-camera-card-menu-submenu'), + items: menuSubmenuItemSchema.array(), +}); +export type MenuSubmenu = z.infer; diff --git a/src/config/schema/elements/custom/menu/types.ts b/src/config/schema/elements/custom/menu/types.ts new file mode 100644 index 00000000..fd7ef6c5 --- /dev/null +++ b/src/config/schema/elements/custom/menu/types.ts @@ -0,0 +1,6 @@ +import { MenuIcon } from './icon'; +import { MenuStateIcon } from './state-icon'; +import { MenuSubmenu } from './submenu'; +import { MenuSubmenuSelect } from './submenu-select'; + +export type MenuItem = MenuIcon | MenuStateIcon | MenuSubmenu | MenuSubmenuSelect; diff --git a/src/config/schema/elements/stock/custom.ts b/src/config/schema/elements/stock/custom.ts new file mode 100644 index 00000000..71f97d23 --- /dev/null +++ b/src/config/schema/elements/stock/custom.ts @@ -0,0 +1,17 @@ +import { z } from 'zod'; + +// https://www.home-assistant.io/lovelace/picture-elements/#custom-elements +export const customSchema = z + .object({ + // Insist that Advanced Camera Card custom elements are handled by other schemas. + type: z.string().superRefine((val, ctx) => { + if (!val.match(/^custom:(?!advanced-camera-card).+/)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'advanced-camera-card custom elements must match specific schemas', + fatal: true, + }); + } + }), + }) + .passthrough(); diff --git a/src/config/schema/elements/stock/icon.ts b/src/config/schema/elements/stock/icon.ts new file mode 100644 index 00000000..373d43f6 --- /dev/null +++ b/src/config/schema/elements/stock/icon.ts @@ -0,0 +1,9 @@ +import { z } from 'zod'; +import { elementsBaseSchema } from '../base'; + +// https://www.home-assistant.io/lovelace/picture-elements/#icon-element +export const iconSchema = elementsBaseSchema.extend({ + type: z.literal('icon'), + icon: z.string(), + entity: z.string().optional(), +}); diff --git a/src/config/schema/elements/stock/image.ts b/src/config/schema/elements/stock/image.ts new file mode 100644 index 00000000..073503fa --- /dev/null +++ b/src/config/schema/elements/stock/image.ts @@ -0,0 +1,15 @@ +import { z } from 'zod'; +import { elementsBaseSchema } from '../base'; + +// https://www.home-assistant.io/lovelace/picture-elements/#image-element +export const imageSchema = elementsBaseSchema.extend({ + type: z.literal('image'), + entity: z.string().optional(), + image: z.string().optional(), + camera_image: z.string().optional(), + camera_view: z.string().optional(), + state_image: z.object({}).passthrough().optional(), + filter: z.string().optional(), + state_filter: z.object({}).passthrough().optional(), + aspect_ratio: z.string().optional(), +}); diff --git a/src/config/schema/elements/stock/service-call.ts b/src/config/schema/elements/stock/service-call.ts new file mode 100644 index 00000000..7f6743d3 --- /dev/null +++ b/src/config/schema/elements/stock/service-call.ts @@ -0,0 +1,12 @@ +import { z } from 'zod'; +import { elementsBaseSchema } from '../base'; + +// https://www.home-assistant.io/lovelace/picture-elements/#service-call-button +export const serviceCallButtonSchema = elementsBaseSchema.extend({ + type: z.literal('service-button'), + + // Title is required for service button. + title: z.string(), + service: z.string(), + service_data: z.object({}).passthrough().optional(), +}); diff --git a/src/config/schema/elements/stock/state-badge.ts b/src/config/schema/elements/stock/state-badge.ts new file mode 100644 index 00000000..1e37e565 --- /dev/null +++ b/src/config/schema/elements/stock/state-badge.ts @@ -0,0 +1,8 @@ +import { z } from 'zod'; +import { elementsBaseSchema } from '../base'; + +// https://www.home-assistant.io/lovelace/picture-elements/#state-badge +export const stateBadgeIconSchema = elementsBaseSchema.extend({ + type: z.literal('state-badge'), + entity: z.string(), +}); diff --git a/src/config/schema/elements/stock/state-icon.ts b/src/config/schema/elements/stock/state-icon.ts new file mode 100644 index 00000000..78b97286 --- /dev/null +++ b/src/config/schema/elements/stock/state-icon.ts @@ -0,0 +1,10 @@ +import { z } from 'zod'; +import { elementsBaseSchema } from '../base'; + +// https://www.home-assistant.io/lovelace/picture-elements/#state-icon +export const stateIconSchema = elementsBaseSchema.extend({ + type: z.literal('state-icon'), + entity: z.string(), + icon: z.string().optional(), + state_color: z.boolean().default(true), +}); diff --git a/src/config/schema/elements/stock/state-label.ts b/src/config/schema/elements/stock/state-label.ts new file mode 100644 index 00000000..0efbfe05 --- /dev/null +++ b/src/config/schema/elements/stock/state-label.ts @@ -0,0 +1,11 @@ +import { z } from 'zod'; +import { elementsBaseSchema } from '../base'; + +// https://www.home-assistant.io/lovelace/picture-elements/#state-label +export const stateLabelSchema = elementsBaseSchema.extend({ + type: z.literal('state-label'), + entity: z.string(), + attribute: z.string().optional(), + prefix: z.string().optional(), + suffix: z.string().optional(), +}); diff --git a/src/config/schema/elements/types.ts b/src/config/schema/elements/types.ts new file mode 100644 index 00000000..ac5e0db2 --- /dev/null +++ b/src/config/schema/elements/types.ts @@ -0,0 +1,81 @@ +import { z } from 'zod'; +import { + statusBarIconItemSchema, + statusBarImageItemSchema, + statusBarStringItemSchema, +} from '../actions/types'; +import { StockCondition, stockConditionSchema } from '../conditions/stock/types'; +import { + AdvancedCameraCardCondition, + advancedCameraCardConditionSchema, +} from '../conditions/types'; +import { menuIconSchema } from './custom/menu/icon'; +import { menuStateIconSchema } from './custom/menu/state-icon'; +import { menuSubmenuSchema } from './custom/menu/submenu'; +import { menuSubmenuSelectSchema } from './custom/menu/submenu-select'; +import { customSchema } from './stock/custom'; +import { iconSchema } from './stock/icon'; +import { imageSchema } from './stock/image'; +import { serviceCallButtonSchema } from './stock/service-call'; +import { stateBadgeIconSchema } from './stock/state-badge'; +import { stateIconSchema } from './stock/state-icon'; +import { stateLabelSchema } from './stock/state-label'; + +// Condition elements are included in this upper file as they recursively +// include other elements. Putting these elements elsewhere would cause +// typescript circular dependency errors as the types need to be both included +// in the master pictureElementSchema, but also refer to it internally. +// +// Provide a manual type definition to avoid the `any` that would be created by +// the lazy() evaluation below. +// See: https://zod.dev/?id=recursive-types + +// https://www.home-assistant.io/lovelace/picture-elements/#image-element +type Conditional = { + type: 'conditional'; + conditions: StockCondition[]; + elements?: PictureElements; +}; +export const conditionalSchema: z.ZodSchema = z.object({ + type: z.literal('conditional'), + conditions: stockConditionSchema.array(), + elements: z.lazy(() => pictureElementsSchema), +}); + +export type AdvancedCameraCardConditional = { + type: 'custom:advanced-camera-card-conditional'; + conditions: AdvancedCameraCardCondition[]; + elements?: PictureElements; +}; +const advancedCameraCardConditionalSchema: z.ZodSchema< + AdvancedCameraCardConditional, + z.ZodTypeDef +> = z.object({ + type: z.literal('custom:advanced-camera-card-conditional'), + conditions: advancedCameraCardConditionSchema.array(), + elements: z.lazy(() => pictureElementsSchema), +}); + +// Cannot use discriminatedUnion since customSchema uses a superRefine, which +// causes false rejections. +const pictureElementSchema = z.union([ + conditionalSchema, + customSchema, + advancedCameraCardConditionalSchema, + iconSchema, + imageSchema, + menuIconSchema, + menuStateIconSchema, + menuSubmenuSchema, + menuSubmenuSelectSchema, + serviceCallButtonSchema, + stateBadgeIconSchema, + stateIconSchema, + stateLabelSchema, + statusBarIconItemSchema, + statusBarImageItemSchema, + statusBarStringItemSchema, +]); + +export const pictureElementsSchema = pictureElementSchema.array().optional(); +export type PictureElements = z.infer; diff --git a/src/config/schema/gallery.ts b/src/config/schema/gallery.ts new file mode 100644 index 00000000..932b1296 --- /dev/null +++ b/src/config/schema/gallery.ts @@ -0,0 +1,45 @@ +import { z } from 'zod'; +import { actionsSchema } from './actions/types'; +import { + thumbnailControlsDefaults, + thumbnailsControlSchema, +} from './common/controls/thumbnails'; + +const galleryThumbnailControlsDefaults = { + ...thumbnailControlsDefaults, + show_details: false, +}; + +export const galleryConfigDefault = { + controls: { + thumbnails: galleryThumbnailControlsDefaults, + filter: { + mode: 'right' as const, + }, + }, +}; + +const gallerythumbnailsControlSchema = thumbnailsControlSchema.extend({ + show_details: z.boolean().default(galleryThumbnailControlsDefaults.show_details), +}); + +export const galleryConfigSchema = z + .object({ + controls: z + .object({ + thumbnails: gallerythumbnailsControlSchema.default( + galleryConfigDefault.controls.thumbnails, + ), + filter: z + .object({ + mode: z + .enum(['none', 'left', 'right']) + .default(galleryConfigDefault.controls.filter.mode), + }) + .default(galleryConfigDefault.controls.filter), + }) + .default(galleryConfigDefault.controls), + }) + .merge(actionsSchema) + .default(galleryConfigDefault); +export type GalleryConfig = z.infer; diff --git a/src/config/schema/image.ts b/src/config/schema/image.ts new file mode 100644 index 00000000..6b752efc --- /dev/null +++ b/src/config/schema/image.ts @@ -0,0 +1,12 @@ +import { z } from 'zod'; +import { actionsSchema } from './actions/types'; +import { imageBaseConfigSchema, imageConfigDefault } from './common/image'; + +export const imageConfigSchema = imageBaseConfigSchema + .extend({ + zoomable: z.boolean().default(imageConfigDefault.zoomable), + }) + .merge(actionsSchema) + .default(imageConfigDefault); + +export type ImageViewConfig = z.infer; diff --git a/src/config/schema/live.ts b/src/config/schema/live.ts new file mode 100644 index 00000000..ce48e496 --- /dev/null +++ b/src/config/schema/live.ts @@ -0,0 +1,145 @@ +import { z } from 'zod'; +import { actionsSchema } from './actions/types'; +import { nextPreviousControlConfigSchema } from './common/controls/next-previous'; +import { ptzControlsConfigSchema, ptzControlsDefaults } from './common/controls/ptz'; +import { + thumbnailControlsDefaults, + thumbnailsControlSchema, +} from './common/controls/thumbnails'; +import { + miniTimelineConfigDefault, + miniTimelineConfigSchema, +} from './common/controls/timeline'; +import { viewDisplaySchema } from './common/display'; +import { eventsMediaTypeSchema } from './common/events-media'; +import { + MEDIA_ACTION_NEGATIVE_CONDITIONS, + MEDIA_ACTION_POSITIVE_CONDITIONS, + MEDIA_MUTE_CONDITIONS, + MEDIA_UNMUTE_CONDITIONS, +} from './common/media-actions'; +import { transitionEffectConfigSchema } from './common/transition-effect'; + +const microphoneConfigDefault = { + always_connected: false, + disconnect_seconds: 90, + mute_after_microphone_mute_seconds: 60, +}; + +const microphoneConfigSchema = z + .object({ + always_connected: z.boolean().default(microphoneConfigDefault.always_connected), + disconnect_seconds: z + .number() + .min(0) + .default(microphoneConfigDefault.disconnect_seconds), + mute_after_microphone_mute_seconds: z + .number() + .min(0) + .default(microphoneConfigDefault.mute_after_microphone_mute_seconds), + }) + .default(microphoneConfigDefault); +export type MicrophoneConfig = z.infer; + +const liveThumbnailControlsDefaults = { + ...thumbnailControlsDefaults, + media_type: 'events' as const, + events_media_type: 'all' as const, +}; + +export const liveConfigDefault = { + auto_play: [...MEDIA_ACTION_POSITIVE_CONDITIONS], + auto_pause: [], + auto_mute: [...MEDIA_MUTE_CONDITIONS], + auto_unmute: ['microphone' as const], + preload: false, + lazy_load: true, + lazy_unload: [], + draggable: true, + zoomable: true, + transition_effect: 'slide' as const, + show_image_during_load: true, + mode: 'single' as const, + controls: { + builtin: true, + next_previous: { + size: 48, + style: 'chevrons' as const, + }, + ptz: ptzControlsDefaults, + thumbnails: liveThumbnailControlsDefaults, + timeline: miniTimelineConfigDefault, + }, + microphone: { + ...microphoneConfigDefault, + }, +}; + +const livethumbnailsControlSchema = thumbnailsControlSchema.extend({ + media_type: z + .enum(['events', 'recordings']) + .default(liveConfigDefault.controls.thumbnails.media_type), + events_media_type: eventsMediaTypeSchema.default( + liveConfigDefault.controls.thumbnails.events_media_type, + ), +}); + +export const liveConfigSchema = z + .object({ + auto_pause: z + .enum(MEDIA_ACTION_NEGATIVE_CONDITIONS) + .array() + .default(liveConfigDefault.auto_pause), + auto_play: z + .enum(MEDIA_ACTION_POSITIVE_CONDITIONS) + .array() + .default(liveConfigDefault.auto_play), + auto_mute: z + .enum(MEDIA_MUTE_CONDITIONS) + .array() + .default(liveConfigDefault.auto_mute), + auto_unmute: z + .enum(MEDIA_UNMUTE_CONDITIONS) + .array() + .default(liveConfigDefault.auto_unmute), + controls: z + .object({ + builtin: z.boolean().default(liveConfigDefault.controls.builtin), + next_previous: nextPreviousControlConfigSchema + .extend({ + // Live cannot show thumbnails, remove that option. + style: z + .enum(['none', 'chevrons', 'icons']) + .default(liveConfigDefault.controls.next_previous.style), + size: nextPreviousControlConfigSchema.shape.size.default( + liveConfigDefault.controls.next_previous.size, + ), + }) + .default(liveConfigDefault.controls.next_previous), + ptz: ptzControlsConfigSchema.default(liveConfigDefault.controls.ptz), + thumbnails: livethumbnailsControlSchema.default( + liveConfigDefault.controls.thumbnails, + ), + timeline: miniTimelineConfigSchema.default(liveConfigDefault.controls.timeline), + }) + .default(liveConfigDefault.controls), + display: viewDisplaySchema, + draggable: z.boolean().default(liveConfigDefault.draggable), + lazy_load: z.boolean().default(liveConfigDefault.lazy_load), + lazy_unload: z + .enum(MEDIA_ACTION_NEGATIVE_CONDITIONS) + .array() + .default(liveConfigDefault.lazy_unload), + microphone: microphoneConfigSchema.default(liveConfigDefault.microphone), + preload: z.boolean().default(liveConfigDefault.preload), + show_image_during_load: z + .boolean() + .default(liveConfigDefault.show_image_during_load), + transition_effect: transitionEffectConfigSchema.default( + liveConfigDefault.transition_effect, + ), + zoomable: z.boolean().default(liveConfigDefault.zoomable), + }) + .merge(actionsSchema) + .default(liveConfigDefault); +export type LiveConfig = z.infer; diff --git a/src/config/schema/menu.ts b/src/config/schema/menu.ts new file mode 100644 index 00000000..acce4f3e --- /dev/null +++ b/src/config/schema/menu.ts @@ -0,0 +1,112 @@ +import { z } from 'zod'; +import { BUTTON_SIZE_MIN, MENU_PRIORITY_DEFAULT } from './common/const'; +import { menuBaseSchema } from './elements/custom/menu/base'; + +const MENU_STYLES = [ + 'none', + 'hidden', + 'overlay', + 'hover', + 'hover-card', + 'outside', +] as const; +const MENU_POSITIONS = ['left', 'right', 'top', 'bottom'] as const; +const MENU_ALIGNMENTS = MENU_POSITIONS; + +const visibleButtonDefault = { + priority: MENU_PRIORITY_DEFAULT, + enabled: true, +}; + +const hiddenButtonDefault = { + priority: MENU_PRIORITY_DEFAULT, + enabled: false, +}; + +export const menuConfigDefault = { + alignment: 'left' as const, + button_size: 40, + buttons: { + camera_ui: visibleButtonDefault, + cameras: visibleButtonDefault, + clips: visibleButtonDefault, + ptz_home: hiddenButtonDefault, + display_mode: visibleButtonDefault, + download: visibleButtonDefault, + expand: hiddenButtonDefault, + iris: visibleButtonDefault, + fullscreen: visibleButtonDefault, + image: hiddenButtonDefault, + live: visibleButtonDefault, + media_player: visibleButtonDefault, + microphone: { + ...hiddenButtonDefault, + type: 'momentary' as const, + }, + mute: hiddenButtonDefault, + play: hiddenButtonDefault, + ptz_controls: hiddenButtonDefault, + recordings: hiddenButtonDefault, + screenshot: hiddenButtonDefault, + snapshots: visibleButtonDefault, + substreams: visibleButtonDefault, + timeline: visibleButtonDefault, + }, + position: 'top' as const, + style: 'hidden' as const, +}; + +const visibleButtonSchema = menuBaseSchema.extend({ + enabled: menuBaseSchema.shape.enabled.default(visibleButtonDefault.enabled), + priority: menuBaseSchema.shape.priority.default(visibleButtonDefault.priority), +}); + +const hiddenButtonSchema = menuBaseSchema.extend({ + enabled: menuBaseSchema.shape.enabled.default(hiddenButtonDefault.enabled), + priority: menuBaseSchema.shape.priority.default(hiddenButtonDefault.priority), +}); + +export const menuConfigSchema = z + .object({ + style: z.enum(MENU_STYLES).default(menuConfigDefault.style), + position: z.enum(MENU_POSITIONS).default(menuConfigDefault.position), + alignment: z.enum(MENU_ALIGNMENTS).default(menuConfigDefault.alignment), + buttons: z + .object({ + camera_ui: visibleButtonSchema.default(menuConfigDefault.buttons.camera_ui), + cameras: visibleButtonSchema.default(menuConfigDefault.buttons.cameras), + clips: visibleButtonSchema.default(menuConfigDefault.buttons.clips), + ptz_home: hiddenButtonSchema.default(menuConfigDefault.buttons.ptz_home), + display_mode: visibleButtonSchema.default( + menuConfigDefault.buttons.display_mode, + ), + download: visibleButtonSchema.default(menuConfigDefault.buttons.download), + expand: hiddenButtonSchema.default(menuConfigDefault.buttons.expand), + iris: visibleButtonSchema.default(menuConfigDefault.buttons.iris), + fullscreen: visibleButtonSchema.default(menuConfigDefault.buttons.fullscreen), + image: hiddenButtonSchema.default(menuConfigDefault.buttons.image), + live: visibleButtonSchema.default(menuConfigDefault.buttons.live), + media_player: visibleButtonSchema.default( + menuConfigDefault.buttons.media_player, + ), + microphone: hiddenButtonSchema + .extend({ + type: z + .enum(['momentary', 'toggle']) + .default(menuConfigDefault.buttons.microphone.type), + }) + .default(menuConfigDefault.buttons.microphone), + mute: hiddenButtonSchema.default(menuConfigDefault.buttons.mute), + play: hiddenButtonSchema.default(menuConfigDefault.buttons.play), + ptz_controls: hiddenButtonSchema.default(menuConfigDefault.buttons.ptz_controls), + recordings: hiddenButtonSchema.default(menuConfigDefault.buttons.recordings), + screenshot: hiddenButtonSchema.default(menuConfigDefault.buttons.screenshot), + snapshots: visibleButtonSchema.default(menuConfigDefault.buttons.snapshots), + substreams: visibleButtonSchema.default(menuConfigDefault.buttons.substreams), + timeline: visibleButtonSchema.default(menuConfigDefault.buttons.timeline), + }) + .default(menuConfigDefault.buttons), + button_size: z.number().min(BUTTON_SIZE_MIN).default(menuConfigDefault.button_size), + }) + .default(menuConfigDefault); +export type MenuConfig = z.infer; diff --git a/src/config/schema/overrides.ts b/src/config/schema/overrides.ts new file mode 100644 index 00000000..0a31d9f9 --- /dev/null +++ b/src/config/schema/overrides.ts @@ -0,0 +1,13 @@ +import { z } from 'zod'; +import { advancedCameraCardConditionSchema } from './conditions/types'; + +export const overridesSchema = z + .object({ + conditions: advancedCameraCardConditionSchema.array(), + merge: z.object({}).passthrough().optional(), + set: z.object({}).passthrough().optional(), + delete: z.string().array().optional(), + }) + .array() + .optional(); +export type Overrides = z.infer; diff --git a/src/config/schema/performance.ts b/src/config/schema/performance.ts new file mode 100644 index 00000000..e9f4c878 --- /dev/null +++ b/src/config/schema/performance.ts @@ -0,0 +1,42 @@ +import { z } from 'zod'; +import { MEDIA_CHUNK_SIZE_DEFAULT, MEDIA_CHUNK_SIZE_MAX } from '../../const'; + +export const performanceConfigDefault = { + features: { + animated_progress_indicator: true, + card_loading_indicator: true, + media_chunk_size: MEDIA_CHUNK_SIZE_DEFAULT, + }, + style: { + border_radius: true, + box_shadow: true, + }, +}; + +export const performanceConfigSchema = z + .object({ + features: z + .object({ + animated_progress_indicator: z + .boolean() + .default(performanceConfigDefault.features.animated_progress_indicator), + card_loading_indicator: z + .boolean() + .default(performanceConfigDefault.features.card_loading_indicator), + media_chunk_size: z + .number() + .min(0) + .max(MEDIA_CHUNK_SIZE_MAX) + .default(performanceConfigDefault.features.media_chunk_size), + max_simultaneous_engine_requests: z.number().min(1).optional(), + }) + .default(performanceConfigDefault.features), + style: z + .object({ + border_radius: z.boolean().default(performanceConfigDefault.style.border_radius), + box_shadow: z.boolean().default(performanceConfigDefault.style.box_shadow), + }) + .default(performanceConfigDefault.style), + }) + .default(performanceConfigDefault); +export type PerformanceConfig = z.infer; diff --git a/src/config/schema/profiles.ts b/src/config/schema/profiles.ts new file mode 100644 index 00000000..9db1b174 --- /dev/null +++ b/src/config/schema/profiles.ts @@ -0,0 +1,5 @@ +import { z } from 'zod'; + +const PROFILES = ['casting', 'low-performance', 'scrubbing'] as const; +export type ProfileType = (typeof PROFILES)[number]; +export const profilesSchema = z.enum(PROFILES).array().optional(); diff --git a/src/config/schema/remote-control.ts b/src/config/schema/remote-control.ts new file mode 100644 index 00000000..2b068afa --- /dev/null +++ b/src/config/schema/remote-control.ts @@ -0,0 +1,11 @@ +import { z } from 'zod'; + +export const remoteControlConfigSchema = z + .object({ + entities: z + .object({ + camera: z.string().startsWith('input_select.').optional(), + }) + .optional(), + }) + .optional(); diff --git a/src/config/schema/status-bar.ts b/src/config/schema/status-bar.ts new file mode 100644 index 00000000..73ad15ca --- /dev/null +++ b/src/config/schema/status-bar.ts @@ -0,0 +1,58 @@ +import { z } from 'zod'; +import { BUTTON_SIZE_MIN, STATUS_BAR_PRIORITY_DEFAULT } from './common/const'; +import { statusBarItemBaseSchema } from './common/status-bar'; + +export const STATUS_BAR_HEIGHT_MIN = BUTTON_SIZE_MIN; +const STATUS_BAR_STYLES = [ + 'none', + 'overlay', + 'hover', + 'hover-card', + 'outside', + 'popup', +] as const; +const STATUS_BAR_POSITIONS = ['top', 'bottom'] as const; + +const statusBarItemDefault = { + priority: STATUS_BAR_PRIORITY_DEFAULT, + enabled: true, +}; + +export const statusBarConfigDefault = { + height: 40, + items: { + engine: statusBarItemDefault, + resolution: statusBarItemDefault, + technology: statusBarItemDefault, + title: statusBarItemDefault, + }, + position: 'bottom' as const, + style: 'popup' as const, + popup_seconds: 3, +}; + +export const statusBarConfigSchema = z + .object({ + position: z.enum(STATUS_BAR_POSITIONS).default(statusBarConfigDefault.position), + style: z.enum(STATUS_BAR_STYLES).default(statusBarConfigDefault.style), + popup_seconds: z + .number() + .min(0) + .max(60) + .default(statusBarConfigDefault.popup_seconds), + height: z.number().min(STATUS_BAR_HEIGHT_MIN).default(statusBarConfigDefault.height), + items: z + .object({ + engine: statusBarItemBaseSchema.default(statusBarConfigDefault.items.engine), + technology: statusBarItemBaseSchema.default( + statusBarConfigDefault.items.technology, + ), + resolution: statusBarItemBaseSchema.default( + statusBarConfigDefault.items.resolution, + ), + title: statusBarItemBaseSchema.default(statusBarConfigDefault.items.title), + }) + .default(statusBarConfigDefault.items), + }) + .default(statusBarConfigDefault); +export type StatusBarConfig = z.infer; diff --git a/src/config/schema/timeline.ts b/src/config/schema/timeline.ts new file mode 100644 index 00000000..e4c06637 --- /dev/null +++ b/src/config/schema/timeline.ts @@ -0,0 +1,19 @@ +import { z } from 'zod'; +import { thumbnailsControlSchema } from './common/controls/thumbnails'; +import { + timelineConfigDefault, + timelineCoreConfigSchema, +} from './common/controls/timeline'; + +export const timelineConfigSchema = timelineCoreConfigSchema + .extend({ + controls: z + .object({ + thumbnails: thumbnailsControlSchema.default( + timelineConfigDefault.controls.thumbnails, + ), + }) + .default(timelineConfigDefault.controls), + }) + .default(timelineConfigDefault); +export type TimelineConfig = z.infer; diff --git a/src/config/schema/types.ts b/src/config/schema/types.ts new file mode 100644 index 00000000..9ca0f408 --- /dev/null +++ b/src/config/schema/types.ts @@ -0,0 +1,87 @@ +import { z } from 'zod'; +import { deepRemoveDefaults } from '../../utils/zod'; +import { automationsSchema } from './automations'; +import { cameraConfigDefault, cameraConfigSchema, camerasConfigSchema } from './cameras'; +import { cardIDRegex } from './common/const'; +import { timelineConfigDefault } from './common/controls/timeline'; +import { imageConfigDefault } from './common/image'; +import { DebugConfig, debugConfigDefault, debugConfigSchema } from './debug'; +import { dimensionsConfigSchema } from './dimensions'; +import { pictureElementsSchema } from './elements/types'; +import { galleryConfigDefault, galleryConfigSchema } from './gallery'; +import { imageConfigSchema } from './image'; +import { liveConfigDefault, liveConfigSchema } from './live'; +import { menuConfigDefault, menuConfigSchema } from './menu'; +import { overridesSchema } from './overrides'; +import { + PerformanceConfig, + performanceConfigDefault, + performanceConfigSchema, +} from './performance'; +import { profilesSchema } from './profiles'; +import { remoteControlConfigSchema } from './remote-control'; +import { statusBarConfigDefault, statusBarConfigSchema } from './status-bar'; +import { timelineConfigSchema } from './timeline'; +import { viewConfigDefault, viewConfigSchema } from './view'; +import { viewerConfigDefault, viewerConfigSchema } from './viewer'; + +export interface CardWideConfig { + performance?: PerformanceConfig; + debug?: DebugConfig; +} + +export const advancedCameraCardConfigSchema = z.object({ + // Defaults are stripped out of the individual cameras, since each camera will + // be merged with `cameras_global` which *does* have defaults. If we didn't do + // this, the default values of each individual camera would override the + // intentionally specified values in `cameras_global` during camera + // initialization when the two configs are merged. + cameras: deepRemoveDefaults(camerasConfigSchema), + cameras_global: cameraConfigSchema, + + view: viewConfigSchema, + menu: menuConfigSchema, + status_bar: statusBarConfigSchema, + live: liveConfigSchema, + media_gallery: galleryConfigSchema, + media_viewer: viewerConfigSchema, + image: imageConfigSchema, + elements: pictureElementsSchema, + dimensions: dimensionsConfigSchema, + timeline: timelineConfigSchema, + performance: performanceConfigSchema, + debug: debugConfigSchema, + automations: automationsSchema.optional(), + + profiles: profilesSchema, + + // Configuration overrides. + overrides: overridesSchema, + + // Support for card_mod (https://github.com/thomasloven/lovelace-card-mod). + card_mod: z.unknown(), + + // Card ID (used for query string commands). Restrict contents to only values + // that be easily used in a URL. + card_id: z.string().regex(cardIDRegex).optional(), + + remote_control: remoteControlConfigSchema, + + // Stock lovelace card config. + type: z.string(), +}); +export type AdvancedCameraCardConfig = z.infer; + +export const configDefaults = { + cameras: cameraConfigDefault, + view: viewConfigDefault, + menu: menuConfigDefault, + status_bar: statusBarConfigDefault, + live: liveConfigDefault, + media_gallery: galleryConfigDefault, + media_viewer: viewerConfigDefault, + image: imageConfigDefault, + timeline: timelineConfigDefault, + performance: performanceConfigDefault, + debug: debugConfigDefault, +}; diff --git a/src/config/schema/view.ts b/src/config/schema/view.ts new file mode 100644 index 00000000..16c2b69e --- /dev/null +++ b/src/config/schema/view.ts @@ -0,0 +1,144 @@ +import { z } from 'zod'; +import { actionsSchema } from './actions/types'; +import { VIEW_DEFAULT, VIEWS_USER_SPECIFIED } from './common/const'; + +const keyboardShortcut = z.object({ + key: z.string(), + ctrl: z.boolean().optional(), + shift: z.boolean().optional(), + alt: z.boolean().optional(), + meta: z.boolean().optional(), +}); +export type KeyboardShortcut = z.infer; + +const keyboardShortcutsDefault = { + enabled: true, + ptz_left: { key: 'ArrowLeft' }, + ptz_right: { key: 'ArrowRight' }, + ptz_up: { key: 'ArrowUp' }, + ptz_down: { key: 'ArrowDown' }, + ptz_zoom_in: { key: '+' }, + ptz_zoom_out: { key: '-' }, + ptz_home: { key: 'h' }, +}; + +const keyboardShortcutsSchema = z.object({ + enabled: z.boolean().default(keyboardShortcutsDefault.enabled), + ptz_left: keyboardShortcut.nullable().default(keyboardShortcutsDefault.ptz_left), + ptz_right: keyboardShortcut.nullable().default(keyboardShortcutsDefault.ptz_right), + ptz_up: keyboardShortcut.nullable().default(keyboardShortcutsDefault.ptz_up), + ptz_down: keyboardShortcut.nullable().default(keyboardShortcutsDefault.ptz_down), + ptz_zoom_in: keyboardShortcut.nullable().default(keyboardShortcutsDefault.ptz_zoom_in), + ptz_zoom_out: keyboardShortcut + .nullable() + .default(keyboardShortcutsDefault.ptz_zoom_out), + ptz_home: keyboardShortcut.nullable().default(keyboardShortcutsDefault.ptz_home), +}); +export type KeyboardShortcuts = z.infer; + +const KEYBOARD_SHORTCUT_PTZ_NAMES = [ + 'ptz_down', + 'ptz_home', + 'ptz_left', + 'ptz_right', + 'ptz_up', + 'ptz_zoom_in', + 'ptz_zoom_out', +] as const; +export type PTZKeyboardShortcutName = (typeof KEYBOARD_SHORTCUT_PTZ_NAMES)[number]; + +export const viewConfigDefault = { + default: VIEW_DEFAULT, + camera_select: 'current' as const, + interaction_seconds: 300, + default_reset: { + every_seconds: 0, + after_interaction: false, + entities: [], + interaction_mode: 'inactive' as const, + }, + default_cycle_camera: false, + dim: false, + theme: { + themes: ['traditional' as const], + }, + triggers: { + show_trigger_status: false, + filter_selected_camera: true, + actions: { + trigger: 'update' as const, + untrigger: 'none' as const, + }, + untrigger_seconds: 0, + }, + keyboard_shortcuts: keyboardShortcutsDefault, +}; + +const interactionModeSchema = z.enum(['all', 'inactive', 'active']).default('inactive'); +export type InteractionMode = z.infer; + +export const triggersSchema = z.object({ + actions: z + .object({ + interaction_mode: interactionModeSchema, + trigger: z + .enum(['default', 'live', 'media', 'none', 'update']) + .default(viewConfigDefault.triggers.actions.trigger), + untrigger: z + .enum(['default', 'none']) + .default(viewConfigDefault.triggers.actions.untrigger), + }) + .default(viewConfigDefault.triggers.actions), + filter_selected_camera: z + .boolean() + .default(viewConfigDefault.triggers.filter_selected_camera), + show_trigger_status: z + .boolean() + .default(viewConfigDefault.triggers.show_trigger_status), + untrigger_seconds: z.number().default(viewConfigDefault.triggers.untrigger_seconds), +}); +export type TriggersOptions = z.infer; + +const themeName = z.enum(['ha', 'dark', 'light', 'traditional']); +export type ThemeName = z.infer; + +const themeConfigSchema = z.object({ + themes: themeName.array().default(viewConfigDefault.theme.themes), + overrides: z.record(z.string()).optional(), +}); +export type ThemeConfig = z.infer; + +export const viewConfigSchema = z + .object({ + default: z.enum(VIEWS_USER_SPECIFIED).default(viewConfigDefault.default), + camera_select: z + .enum([...VIEWS_USER_SPECIFIED, 'current']) + .default(viewConfigDefault.camera_select), + interaction_seconds: z.number().default(viewConfigDefault.interaction_seconds), + default_cycle_camera: z.boolean().default(viewConfigDefault.default_cycle_camera), + + default_reset: z + .object({ + after_interaction: z + .boolean() + .default(viewConfigDefault.default_reset.after_interaction), + every_seconds: z.number().default(viewConfigDefault.default_reset.every_seconds), + entities: z.string().array().default(viewConfigDefault.default_reset.entities), + interaction_mode: interactionModeSchema.default( + viewConfigDefault.default_reset.interaction_mode, + ), + }) + .default(viewConfigDefault.default_reset), + + render_entities: z.string().array().optional(), + + theme: themeConfigSchema.default(viewConfigDefault.theme), + + dim: z.boolean().default(viewConfigDefault.dim), + triggers: triggersSchema.default(viewConfigDefault.triggers), + keyboard_shortcuts: keyboardShortcutsSchema.default( + viewConfigDefault.keyboard_shortcuts, + ), + }) + .merge(actionsSchema) + .default(viewConfigDefault); diff --git a/src/config/schema/viewer.ts b/src/config/schema/viewer.ts new file mode 100644 index 00000000..15799517 --- /dev/null +++ b/src/config/schema/viewer.ts @@ -0,0 +1,109 @@ +import { z } from 'zod'; +import { actionsSchema } from './actions/types'; +import { nextPreviousControlConfigSchema } from './common/controls/next-previous'; +import { ptzControlsConfigSchema, ptzControlsDefaults } from './common/controls/ptz'; +import { + thumbnailControlsDefaults, + thumbnailsControlSchema, +} from './common/controls/thumbnails'; +import { + miniTimelineConfigDefault, + miniTimelineConfigSchema, +} from './common/controls/timeline'; +import { viewDisplaySchema } from './common/display'; +import { + MEDIA_ACTION_NEGATIVE_CONDITIONS, + MEDIA_ACTION_POSITIVE_CONDITIONS, +} from './common/media-actions'; +import { transitionEffectConfigSchema } from './common/transition-effect'; + +export const viewerConfigDefault = { + auto_play: [...MEDIA_ACTION_POSITIVE_CONDITIONS], + auto_pause: [...MEDIA_ACTION_NEGATIVE_CONDITIONS], + auto_mute: [...MEDIA_ACTION_NEGATIVE_CONDITIONS], + auto_unmute: [], + lazy_load: true, + draggable: true, + zoomable: true, + transition_effect: 'slide' as const, + snapshot_click_plays_clip: true, + display_mode: 'single' as const, + controls: { + builtin: true, + next_previous: { + size: 48, + style: 'thumbnails' as const, + }, + thumbnails: thumbnailControlsDefaults, + timeline: miniTimelineConfigDefault, + ptz: { + ...ptzControlsDefaults, + mode: 'off' as const, + }, + }, +}; + +const viewerNextPreviousControlConfigSchema = nextPreviousControlConfigSchema.extend({ + style: z + .enum(['none', 'thumbnails', 'chevrons']) + .default(viewerConfigDefault.controls.next_previous.style), + size: nextPreviousControlConfigSchema.shape.size.default( + viewerConfigDefault.controls.next_previous.size, + ), +}); + +export const viewerConfigSchema = z + .object({ + auto_play: z + .enum(MEDIA_ACTION_POSITIVE_CONDITIONS) + .array() + .default(viewerConfigDefault.auto_play), + auto_pause: z + .enum(MEDIA_ACTION_NEGATIVE_CONDITIONS) + .array() + .default(viewerConfigDefault.auto_pause), + + // Don't use MEDIA_UNMUTE_CONDITIONS and MEDIA_MUTE_CONDITIONS here, since + // it includes 'microphone' which doesn't make sense for viewer media. + auto_mute: z + .enum(MEDIA_ACTION_NEGATIVE_CONDITIONS) + .array() + .default(viewerConfigDefault.auto_mute), + auto_unmute: z + .enum(MEDIA_ACTION_POSITIVE_CONDITIONS) + .array() + .default(viewerConfigDefault.auto_unmute), + lazy_load: z.boolean().default(viewerConfigDefault.lazy_load), + draggable: z.boolean().default(viewerConfigDefault.draggable), + zoomable: z.boolean().default(viewerConfigDefault.zoomable), + transition_effect: transitionEffectConfigSchema.default( + viewerConfigDefault.transition_effect, + ), + snapshot_click_plays_clip: z + .boolean() + .default(viewerConfigDefault.snapshot_click_plays_clip), + display: viewDisplaySchema, + controls: z + .object({ + builtin: z.boolean().default(viewerConfigDefault.controls.builtin), + next_previous: viewerNextPreviousControlConfigSchema.default( + viewerConfigDefault.controls.next_previous, + ), + ptz: ptzControlsConfigSchema + .extend({ + // The media_viewer ptz has no 'auto' mode. + mode: z.enum(['off', 'on']).default(viewerConfigDefault.controls.ptz.mode), + }) + .default(viewerConfigDefault.controls.ptz), + thumbnails: thumbnailsControlSchema.default( + viewerConfigDefault.controls.thumbnails, + ), + timeline: miniTimelineConfigSchema.default( + viewerConfigDefault.controls.timeline, + ), + }) + .default(viewerConfigDefault.controls), + }) + .merge(actionsSchema) + .default(viewerConfigDefault); +export type ViewerConfig = z.infer; diff --git a/src/config/types.ts b/src/config/types.ts index d7711efa..ca5a5d6e 100644 --- a/src/config/types.ts +++ b/src/config/types.ts @@ -1,2136 +1,2 @@ -import { HassServiceTarget } from 'home-assistant-js-websocket'; -import { z } from 'zod'; -import { MEDIA_CHUNK_SIZE_DEFAULT, MEDIA_CHUNK_SIZE_MAX } from '../const.js'; -import { capabilityKeys } from '../types.js'; -import { deepRemoveDefaults } from '../utils/zod.js'; -import { - keyboardShortcutsDefault, - keyboardShortcutsSchema, -} from './keyboard-shortcuts.js'; -import { PTZ_ACTIONS } from './ptz'; - -// ************************************************************************* -// Common Configuration Constants -// ************************************************************************* - -export const BUTTON_SIZE_MIN = 20; -export const STATUS_BAR_HEIGHT_MIN = BUTTON_SIZE_MIN; - -const MENU_PRIORITY_DEFAULT = 50; -export const MENU_PRIORITY_MAX = 100; - -export const STATUS_BAR_PRIORITY_DEFAULT = MENU_PRIORITY_DEFAULT; -export const STATUS_BAR_PRIORITY_MAX = MENU_PRIORITY_MAX; - -export const VIEWS_USER_SPECIFIED = [ - 'diagnostics', - 'live', - 'clip', - 'clips', - 'snapshot', - 'snapshots', - 'recording', - 'recordings', - 'image', - 'timeline', -] as const; -export type AdvancedCameraCardUserSpecifiedView = (typeof VIEWS_USER_SPECIFIED)[number]; - -const VIEWS = [ - ...VIEWS_USER_SPECIFIED, - 'diagnostics', - - // Media: A generic piece of media (could be clip, snapshot, recording). - 'media', -] as const; - -export type AdvancedCameraCardView = (typeof VIEWS)[number]; - -// The default view (may not be supported on all cameras). -export const VIEW_DEFAULT = 'live' as const; - -export const MEDIA_ACTION_NEGATIVE_CONDITIONS = ['unselected', 'hidden'] as const; -export type LazyUnloadCondition = (typeof MEDIA_ACTION_NEGATIVE_CONDITIONS)[number]; -export type AutoPauseCondition = (typeof MEDIA_ACTION_NEGATIVE_CONDITIONS)[number]; - -const MEDIA_ACTION_POSITIVE_CONDITIONS = ['selected', 'visible'] as const; -export type AutoPlayCondition = (typeof MEDIA_ACTION_POSITIVE_CONDITIONS)[number]; -const MEDIA_UNMUTE_CONDITIONS = [ - ...MEDIA_ACTION_POSITIVE_CONDITIONS, - 'microphone', -] as const; -export type AutoUnmuteCondition = (typeof MEDIA_UNMUTE_CONDITIONS)[number]; - -const MEDIA_MUTE_CONDITIONS = [ - ...MEDIA_ACTION_NEGATIVE_CONDITIONS, - 'microphone', -] as const; -export type AutoMuteCondition = (typeof MEDIA_MUTE_CONDITIONS)[number]; - -const ACTION_PHASES = ['start', 'stop'] as const; -export type ActionPhase = (typeof ACTION_PHASES)[number]; - -const CAMERA_TRIGGER_EVENT_TYPES = [ - // An event whether or not it has any media yet associated with it. - 'events', - - // Specific media availability. - 'clips', - 'snapshots', -] as const; -export type CameraTriggerEventType = (typeof CAMERA_TRIGGER_EVENT_TYPES)[number]; - -const cardIDRegex = /^[-\w]+$/; - -// ************************************************************************* -// Pan / Zoom -// ************************************************************************* - -export const ZOOM_MIN = 1; -export const ZOOM_MAX = 10; - -const panSchema = z.object({ - x: z.number().min(0).max(100).optional(), - y: z.number().min(0).max(100).optional(), -}); -const zoomSchema = z.number().min(ZOOM_MIN).max(ZOOM_MAX); - -// ************************************************************************* -// View Display Mode -// ************************************************************************* - -const viewDisplayModeSchema = z.enum(['single', 'grid']); -export type ViewDisplayMode = z.infer; - -const viewDisplaySchema = z - .object({ - mode: viewDisplayModeSchema.optional(), - grid_selected_width_factor: z.number().min(0).optional(), - grid_max_columns: z.number().min(0).optional(), - grid_columns: z.number().min(0).optional(), - }) - .optional(); -export type ViewDisplayConfig = z.infer; - -// ************************************************************************* -// Stock Actions -// ************************************************************************* - -// https://www.home-assistant.io/dashboards/actions/#options-for-confirmation -const actionBaseSchema = z.object({ - confirmation: z - .boolean() - .or( - z.object({ - text: z.string().optional(), - exemptions: z - .object({ - user: z.string(), - }) - .array() - .optional(), - }), - ) - .optional(), - - card_id: z - .string() - .regex(cardIDRegex, 'card_id parameter can only contain [a-z][A-Z][0-9_]-') - .optional(), -}); - -const toggleActionSchema = actionBaseSchema.extend({ - action: z.literal('toggle'), -}); -export type ToggleActionConfig = z.infer; - -const targetSchema: z.ZodSchema = z.object({ - entity_id: z.string().or(z.string().array()).optional(), - device_id: z.string().or(z.string().array()).optional(), - area_id: z.string().or(z.string().array()).optional(), - floor_id: z.string().or(z.string().array()).optional(), - label_id: z.string().or(z.string().array()).optional(), -}); - -const performActionActionSchema = actionBaseSchema.extend({ - action: z.literal('perform-action'), - perform_action: z.string(), - data: z.object({}).passthrough().optional(), - target: targetSchema.optional(), -}); -export type PerformActionActionConfig = z.infer; - -// Note: call-service is deprecated and will eventually go away. Please use -// perform-action instead. -// See: https://www.home-assistant.io/blog/2024/08/07/release-20248/#goodbye-service-calls-hello-actions- -const callServiceActionSchema = actionBaseSchema.extend({ - action: z.literal('call-service'), - service: z.string(), - data: z.object({}).passthrough().optional(), - target: targetSchema.optional(), -}); -export type CallServiceActionConfig = z.infer; - -const navigateActionSchema = actionBaseSchema.extend({ - action: z.literal('navigate'), - navigation_path: z.string(), - navigation_replace: z.boolean().optional(), -}); -export type NavigateActionConfig = z.infer; - -const urlActionSchema = actionBaseSchema.extend({ - action: z.literal('url'), - url_path: z.string(), -}); -export type URLActionConfig = z.infer; - -const moreInfoActionSchema = actionBaseSchema.extend({ - action: z.literal('more-info'), - entity: z.string().optional(), -}); -export type MoreInfoActionConfig = z.infer; - -const customActionSchema = actionBaseSchema - .extend({ - action: z.literal('fire-dom-event'), - }) - .passthrough(); -export type CustomActionConfig = z.infer; - -const noneActionSchema = actionBaseSchema.extend({ - action: z.literal('none'), -}); -export type NoneActionConfig = z.infer; - -export const advancedCameraCardCustomActionsBaseSchema = actionBaseSchema.extend({ - action: z - .literal('fire-dom-event') - .or( - z - .literal('custom:advanced-camera-card-action') - .transform((): 'fire-dom-event' => 'fire-dom-event'), - ), -}); - -// ************************************************************************* -// Custom Actions -// ************************************************************************* - -const GENERAL_ACTIONS = [ - 'camera_ui', - 'default', - 'download', - 'expand', - 'fullscreen', - 'live_substream_off', - 'live_substream_on', - 'menu_toggle', - 'microphone_connect', - 'microphone_disconnect', - 'microphone_mute', - 'microphone_unmute', - 'mute', - 'pause', - 'play', - 'screenshot', - 'unmute', -] as const; -export type AdvancedCameraCardGeneralAction = (typeof GENERAL_ACTIONS)[number]; - -const viewActionConfigSchema = advancedCameraCardCustomActionsBaseSchema.extend({ - advanced_camera_card_action: z.enum(VIEWS_USER_SPECIFIED), -}); -export type ViewActionConfig = z.infer; - -const generalActionConfigSchema = advancedCameraCardCustomActionsBaseSchema.extend({ - advanced_camera_card_action: z.enum(GENERAL_ACTIONS), -}); -export type GeneralActionConfig = z.infer; - -const cameraSelectActionConfigSchema = advancedCameraCardCustomActionsBaseSchema.extend({ - advanced_camera_card_action: z.literal('camera_select'), - camera: z.string().optional(), - triggered: z.boolean().optional(), -}); -export type CameraSelectActionConfig = z.infer; - -const substreamSelectActionConfigSchema = - advancedCameraCardCustomActionsBaseSchema.extend({ - advanced_camera_card_action: z.literal('live_substream_select'), - camera: z.string(), - }); -export type SubstreamSelectActionConfig = z.infer< - typeof substreamSelectActionConfigSchema ->; - -const mediaPlayerActionConfigSchema = advancedCameraCardCustomActionsBaseSchema.extend({ - advanced_camera_card_action: z.literal('media_player'), - media_player: z.string(), - media_player_action: z.enum(['play', 'stop']), -}); -export type MediaPlayerActionConfig = z.infer; - -const viewDisplayModeActionConfigSchema = - advancedCameraCardCustomActionsBaseSchema.extend({ - advanced_camera_card_action: z.literal('display_mode_select'), - display_mode: viewDisplayModeSchema, - }); -export type DisplayModeActionConfig = z.infer; - -const ptzActionConfigSchema = advancedCameraCardCustomActionsBaseSchema.extend({ - advanced_camera_card_action: z.literal('ptz'), - camera: z.string().optional(), - ptz_action: z.enum(PTZ_ACTIONS).optional(), - ptz_phase: z.enum(ACTION_PHASES).optional(), - ptz_preset: z.string().optional(), -}); -export type PTZActionConfig = z.infer; - -const ptzDigitalActionConfigSchema = advancedCameraCardCustomActionsBaseSchema.extend({ - advanced_camera_card_action: z.literal('ptz_digital'), - target_id: z.string().optional(), - absolute: z - .object({ - zoom: zoomSchema.optional(), - pan: panSchema.optional(), - }) - .optional(), - ptz_action: z.enum(PTZ_ACTIONS).optional(), - ptz_phase: z.enum(ACTION_PHASES).optional(), -}); -export type PTZDigitialActionConfig = z.infer; - -const ptzMultiActionSchema = advancedCameraCardCustomActionsBaseSchema.extend({ - advanced_camera_card_action: z.literal('ptz_multi'), - target_id: z.string().optional(), - - ptz_action: z.enum(PTZ_ACTIONS).optional(), - ptz_phase: z.enum(ACTION_PHASES).optional(), - ptz_preset: z.string().optional(), -}); -export type PTZMultiActionConfig = z.infer; - -const ptzControlsActionConfigSchema = advancedCameraCardCustomActionsBaseSchema.extend({ - advanced_camera_card_action: z.literal('ptz_controls'), - enabled: z.boolean(), -}); -export type PTZControlsActionConfig = z.infer; - -const timeDeltaSchema = z.object({ - ms: z.number().optional(), - s: z.number().optional(), - m: z.number().optional(), - h: z.number().optional(), -}); -export type TimeDelta = z.infer; - -const sleepActionConfigSchema = advancedCameraCardCustomActionsBaseSchema.extend({ - advanced_camera_card_action: z.literal('sleep'), - duration: timeDeltaSchema.optional().default({ s: 1 }), -}); -export type SleepActionConfig = z.infer; - -// For StatusBarActionConfig provide a manual type definition to avoid the `any` -// that would be created by the lazy() evaluation below. -// See: https://zod.dev/?id=recursive-types -const statusBarActionConfigSchemaBase = advancedCameraCardCustomActionsBaseSchema.extend( - { - advanced_camera_card_action: z.literal('status_bar'), - status_bar_action: z.enum(['add', 'remove', 'reset']), - }, -); -export type StatusBarActionConfig = z.infer & { - items?: StatusBarItem[]; -}; -export const statusBarActionConfigSchema: z.ZodSchema< - StatusBarActionConfig, - z.ZodTypeDef, - unknown -> = statusBarActionConfigSchemaBase.extend({ - items: z - .lazy(() => statusBarItemSchema) - .array() - .optional(), -}); - -const LOG_ACTIONS_LEVELS = ['debug', 'info', 'warn', 'error'] as const; -export type LogActionLevel = (typeof LOG_ACTIONS_LEVELS)[number]; - -const logActionConfigSchema = advancedCameraCardCustomActionsBaseSchema.extend({ - advanced_camera_card_action: z.literal('log'), - message: z.string(), - level: z.enum(LOG_ACTIONS_LEVELS).default('info'), -}); -export type LogActionConfig = z.infer; - -// An action that can be used internally to call a callback (it is not possible -// for the user to pass this through via the configuration). -export const INTERNAL_CALLBACK_ACTION = '__INTERNAL_CALLBACK_ACTION__'; -const internalCallbackActionConfigSchema = - advancedCameraCardCustomActionsBaseSchema.extend({ - advanced_camera_card_action: z.literal(INTERNAL_CALLBACK_ACTION), - - // The callback is expected to be called with a CardController API object. - callback: z.function().args(z.any()).returns(z.promise(z.void())), - }); -export type InternalCallbackActionConfig = z.infer< - typeof internalCallbackActionConfigSchema ->; - -const stockActionSchema = z.union([ - callServiceActionSchema, - customActionSchema, - moreInfoActionSchema, - navigateActionSchema, - noneActionSchema, - performActionActionSchema, - toggleActionSchema, - urlActionSchema, -]); - -const advancedCameraCardCustomActionSchema = z.union([ - cameraSelectActionConfigSchema, - generalActionConfigSchema, - internalCallbackActionConfigSchema, - logActionConfigSchema, - mediaPlayerActionConfigSchema, - ptzActionConfigSchema, - ptzControlsActionConfigSchema, - ptzDigitalActionConfigSchema, - ptzMultiActionSchema, - sleepActionConfigSchema, - statusBarActionConfigSchema, - substreamSelectActionConfigSchema, - viewActionConfigSchema, - viewDisplayModeActionConfigSchema, -]); -export type AdvancedCameraCardCustomActionConfig = z.infer< - typeof advancedCameraCardCustomActionSchema ->; - -const actionConfigSchema = z.union([ - stockActionSchema, - advancedCameraCardCustomActionSchema, -]); -export type ActionConfig = z.infer; - -export interface AuxillaryActionConfig { - entity?: string; -} - -const actionsBaseSchema = z - .object({ - tap_action: actionConfigSchema.or(actionConfigSchema.array()).optional(), - hold_action: actionConfigSchema.or(actionConfigSchema.array()).optional(), - double_tap_action: actionConfigSchema.or(actionConfigSchema.array()).optional(), - start_tap_action: actionConfigSchema.or(actionConfigSchema.array()).optional(), - end_tap_action: actionConfigSchema.or(actionConfigSchema.array()).optional(), - }) - // Passthrough to allow (at least) entity/camera_image to go through. This - // card doesn't need these attributes, but handleAction() in - // custom_card_helpers may depending on how the action is configured. - .passthrough(); -export type Actions = z.infer; -export type ActionsConfig = Actions & AuxillaryActionConfig; - -const actionsSchema = z.object({ - actions: actionsBaseSchema.optional(), -}); - -// ************************************************************************* -// Picture Elements -// -// All picture element types are validated (not just the Advanced Camera Card -// custom ones) as a convenience to present the user with a consistent error -// display up-front regardless of where they made their error. -// ************************************************************************* - -const elementsBaseSchema = actionsBaseSchema.extend({ - style: z.record(z.string().nullable().or(z.undefined()).or(z.number())).optional(), - title: z.string().nullable().optional(), -}); - -// https://www.home-assistant.io/lovelace/picture-elements/#state-badge -const stateBadgeIconSchema = elementsBaseSchema.extend({ - type: z.literal('state-badge'), - entity: z.string(), -}); - -// https://www.home-assistant.io/lovelace/picture-elements/#state-icon -const stateIconSchema = elementsBaseSchema.extend({ - type: z.literal('state-icon'), - entity: z.string(), - icon: z.string().optional(), - state_color: z.boolean().default(true), -}); - -// https://www.home-assistant.io/lovelace/picture-elements/#state-label -const stateLabelSchema = elementsBaseSchema.extend({ - type: z.literal('state-label'), - entity: z.string(), - attribute: z.string().optional(), - prefix: z.string().optional(), - suffix: z.string().optional(), -}); - -// https://www.home-assistant.io/lovelace/picture-elements/#service-call-button -const serviceCallButtonSchema = elementsBaseSchema.extend({ - type: z.literal('service-button'), - // Title is required for service button. - title: z.string(), - service: z.string(), - service_data: z.object({}).passthrough().optional(), -}); - -// https://www.home-assistant.io/lovelace/picture-elements/#icon-element -const iconSchema = elementsBaseSchema.extend({ - type: z.literal('icon'), - icon: z.string(), - entity: z.string().optional(), -}); - -// https://www.home-assistant.io/lovelace/picture-elements/#image-element -const imageSchema = elementsBaseSchema.extend({ - type: z.literal('image'), - entity: z.string().optional(), - image: z.string().optional(), - camera_image: z.string().optional(), - camera_view: z.string().optional(), - state_image: z.object({}).passthrough().optional(), - filter: z.string().optional(), - state_filter: z.object({}).passthrough().optional(), - aspect_ratio: z.string().optional(), -}); - -// https://www.home-assistant.io/dashboards/conditional/#state -const stateConditionSchema = z.object({ - // If the condition is not specified, a state condition is assumed. This - // allows the syntax to match a picture elements conditional: - // https://www.home-assistant.io/dashboards/picture-elements/#conditional-element - condition: z.literal('state').optional(), - entity: z.string(), - state: z.string().or(z.string().array()).optional(), - state_not: z.string().or(z.string().array()).optional(), -}); - -// https://www.home-assistant.io/dashboards/conditional/#numeric-state -const numericStateConditionSchema = z.object({ - condition: z.literal('numeric_state'), - entity: z.string(), - above: z.number().optional(), - below: z.number().optional(), -}); - -// https://www.home-assistant.io/dashboards/conditional/#screen -const screenConditionSchema = z.object({ - condition: z.literal('screen'), - media_query: z.string(), -}); - -// https://www.home-assistant.io/dashboards/conditional/#user -const usersConditionSchema = z.object({ - condition: z.literal('user'), - users: z.string().array().min(1), -}); - -const stockConditionSchema = z.discriminatedUnion('condition', [ - stateConditionSchema, - numericStateConditionSchema, - screenConditionSchema, - usersConditionSchema, -]); - -// https://www.home-assistant.io/lovelace/picture-elements/#image-element -export const conditionalSchema = z.object({ - type: z.literal('conditional'), - conditions: stockConditionSchema.array(), - elements: z.lazy(() => pictureElementsSchema), -}); - -// https://www.home-assistant.io/lovelace/picture-elements/#custom-elements -export const customSchema = z - .object({ - // Insist that Advanced Camera Card custom elements are handled by other schemas. - type: z.string().superRefine((val, ctx) => { - if (!val.match(/^custom:(?!advanced-camera-card).+/)) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: 'advanced-camera-card custom elements must match specific schemas', - fatal: true, - }); - } - }), - }) - .passthrough(); - -// ************************************************************************* -// Custom Element Configuration: Menu -// ************************************************************************* - -const menuBaseSchema = z.object({ - enabled: z.boolean().default(true).optional(), - priority: z - .number() - .min(0) - .max(MENU_PRIORITY_MAX) - .default(MENU_PRIORITY_DEFAULT) - .optional(), - alignment: z.enum(['matching', 'opposing']).default('matching').optional(), - icon: z.string().optional(), - permanent: z.boolean().default(false).optional(), -}); - -const menuIconSchema = menuBaseSchema.merge(iconSchema).extend({ - type: z.literal('custom:advanced-camera-card-menu-icon'), -}); -export type MenuIcon = z.infer; - -const menuStateIconSchema = menuBaseSchema - .merge(stateIconSchema) - .extend({ - type: z.literal('custom:advanced-camera-card-menu-state-icon'), - }) - .merge(menuBaseSchema); -export type MenuStateIcon = z.infer; - -const menuSubmenuItemSchema = elementsBaseSchema.extend({ - entity: z.string().optional(), - icon: z.string().optional(), - state_color: z.boolean().default(true), - selected: z.boolean().default(false), - subtitle: z.string().optional(), - enabled: z.boolean().default(true), -}); -export type MenuSubmenuItem = z.infer; - -const menuSubmenuSchema = menuBaseSchema.merge(iconSchema).extend({ - type: z.literal('custom:advanced-camera-card-menu-submenu'), - items: menuSubmenuItemSchema.array(), -}); -export type MenuSubmenu = z.infer; - -const menuSubmenuSelectSchema = menuBaseSchema.merge(stateIconSchema).extend({ - type: z.literal('custom:advanced-camera-card-menu-submenu-select'), - options: z.record(menuSubmenuItemSchema.deepPartial()).optional(), -}); -export type MenuSubmenuSelect = z.infer; -export type MenuItem = MenuIcon | MenuStateIcon | MenuSubmenu | MenuSubmenuSelect; - -// ************************************************************************* -// Custom Element Configuration: Status bar -// ************************************************************************* - -const statusBarItemBaseSchema = z.object({ - enabled: z.boolean().default(true).optional(), - priority: z - .number() - .min(0) - .max(STATUS_BAR_PRIORITY_MAX) - .default(STATUS_BAR_PRIORITY_DEFAULT) - .optional(), -}); - -const statusBarItemElementsBaseSchema = statusBarItemBaseSchema.extend({ - sufficient: z.boolean().default(false).optional(), - exclusive: z.boolean().default(false).optional(), - expand: z.boolean().default(false).optional(), - actions: actionsBaseSchema.optional(), -}); - -const statusBarIconItemSchema = statusBarItemElementsBaseSchema.extend({ - type: z.literal('custom:advanced-camera-card-status-bar-icon'), - icon: z.string(), -}); -export type StatusBarIcon = z.infer; - -const statusBarImageItemSchema = statusBarItemElementsBaseSchema.extend({ - type: z.literal('custom:advanced-camera-card-status-bar-image'), - image: z.string(), -}); -export type StatusBarImage = z.infer; - -const statusBarStringItemSchema = statusBarItemElementsBaseSchema.extend({ - type: z.literal('custom:advanced-camera-card-status-bar-string'), - string: z.string(), -}); -export type StatusBarString = z.infer; - -const statusBarItemSchema = z.union([ - statusBarIconItemSchema, - statusBarImageItemSchema, - statusBarStringItemSchema, -]); -export type StatusBarItem = z.infer; - -// ************************************************************************* -// Custom Element Configuration: Conditions -// ************************************************************************* - -const cameraConditionSchema = z.object({ - condition: z.literal('camera'), - cameras: z.string().array().optional(), -}); -const configConditionSchema = z.object({ - condition: z.literal('config'), - paths: z.string().array().optional(), -}); -const displayModeConditionSchema = z.object({ - condition: z.literal('display_mode'), - display_mode: viewDisplayModeSchema, -}); -const expandConditionSchema = z.object({ - condition: z.literal('expand'), - expand: z.boolean(), -}); -const fullscreenConditionSchema = z.object({ - condition: z.literal('fullscreen'), - fullscreen: z.boolean(), -}); -const initializedConditionSchema = z.object({ - condition: z.literal('initialized'), -}); -const interactionConditionSchema = z.object({ - condition: z.literal('interaction'), - interaction: z.boolean(), -}); -const keyConditionSchema = z.object({ - condition: z.literal('key'), - key: z.string(), - state: z.enum(['down', 'up']).optional(), - ctrl: z.boolean().optional(), - shift: z.boolean().optional(), - alt: z.boolean().optional(), - meta: z.boolean().optional(), -}); -const mediaLoadedConditionSchema = z.object({ - condition: z.literal('media_loaded'), - media_loaded: z.boolean(), -}); -const microphoneConditionSchema = z.object({ - condition: z.literal('microphone'), - connected: z.boolean().optional(), - muted: z.boolean().optional(), -}); -const triggeredConditionSchema = z.object({ - condition: z.literal('triggered'), - triggered: z.string().array(), -}); -const userAgentConditionSchema = z.object({ - condition: z.literal('user_agent'), - user_agent: z.string().optional(), - user_agent_re: z - .string() - .refine( - (val) => { - try { - new RegExp(val); - } catch { - return false; - } - return true; - }, - { message: 'Invalid regular expression' }, - ) - .optional(), - companion: z.boolean().optional(), -}); -const viewConditionSchema = z.object({ - condition: z.literal('view'), - views: z.string().array().optional(), -}); - -export const advancedCameraCardConditionSchema = z.discriminatedUnion('condition', [ - // Stock conditions: - numericStateConditionSchema, - screenConditionSchema, - stateConditionSchema, - usersConditionSchema, - - // Custom conditions: - cameraConditionSchema, - configConditionSchema, - displayModeConditionSchema, - expandConditionSchema, - fullscreenConditionSchema, - initializedConditionSchema, - interactionConditionSchema, - keyConditionSchema, - mediaLoadedConditionSchema, - microphoneConditionSchema, - triggeredConditionSchema, - userAgentConditionSchema, - viewConditionSchema, -]); -export type AdvancedCameraCardCondition = z.infer< - typeof advancedCameraCardConditionSchema ->; - -const advancedCameraCardConditionalSchema = z.object({ - type: z.literal('custom:advanced-camera-card-conditional'), - conditions: advancedCameraCardConditionSchema.array(), - elements: z.lazy(() => pictureElementsSchema), -}); -export type AdvancedCameraCardConditional = z.infer< - typeof advancedCameraCardConditionalSchema ->; - -// ************************************************************************* -// Custom Element Configuration: Stock Picture Elements + Custom -// ************************************************************************* - -// Cannot use discriminatedUnion since customSchema uses a superRefine, which -// causes false rejections. -const pictureElementSchema = z.union([ - conditionalSchema, - customSchema, - advancedCameraCardConditionalSchema, - iconSchema, - imageSchema, - menuIconSchema, - menuStateIconSchema, - menuSubmenuSchema, - menuSubmenuSelectSchema, - serviceCallButtonSchema, - stateBadgeIconSchema, - stateIconSchema, - stateLabelSchema, - statusBarIconItemSchema, - statusBarImageItemSchema, - statusBarStringItemSchema, -]); -const pictureElementsSchema = pictureElementSchema.array().optional(); -export type PictureElements = z.infer; - -// ************************************************************************* -// Media Layout Configuration -// ************************************************************************* - -const mediaLayoutConfigSchema = z.object({ - fit: z.enum(['contain', 'cover', 'fill']).optional(), - position: z - .object({ - x: z.number().min(0).max(100).optional(), - y: z.number().min(0).max(100).optional(), - }) - .optional(), - view_box: z - .object({ - bottom: z.number().min(0).max(100).optional().default(0), - left: z.number().min(0).max(100).optional().default(0), - right: z.number().min(0).max(100).optional().default(0), - top: z.number().min(0).max(100).optional().default(0), - }) - .optional(), - pan: panSchema.optional(), - zoom: zoomSchema.optional(), -}); -export type MediaLayoutConfig = z.infer; - -// ************************************************************************* -// Aspect Ratio Configuration -// ************************************************************************* - -const aspectRatioSchema = z - .number() - .array() - .length(2) - .or( - z - .string() - .regex(/^\s*\d+\.?\d*\s*[:/]\s*\d+\.?\d*\s*$/) - .transform((input) => input.split(/[:\/]/).map((d) => Number(d))), - ); - -// ************************************************************************* -// PTZ Configuration -// ************************************************************************* - -const ptzCameraConfigDefaults = { - r2c_delay_between_calls_seconds: 0.5, - c2r_delay_between_calls_seconds: 0.2, -}; - -// To avoid lots of YAML duplication, provide an easy way to just specify the -// service data as actions for each PTZ action, and it will be preprocessed -// into the full form. This also provides compatability with the AlexIT/WebRTC -// PTZ configuration. -const dataPTZFormatToFullFormat = function (suffix: string): (data: unknown) => unknown { - return (data) => { - if (!data || typeof data !== 'object' || !data['service']) { - return data; - } - const out = { ...data }; - Object.keys(data).forEach((key) => { - const match = key.match(/^data_(.+)$/); - const name = match?.[1]; - if (name && !(`${suffix}${name}` in data)) { - out[`${suffix}${name}`] = { - action: 'perform-action', - perform_action: data['service'], - data: data[key], - }; - delete out[key]; - delete out['service']; - } - }); - return out; - }; -}; - -const ptzCameraConfigSchema = z.preprocess( - dataPTZFormatToFullFormat('actions_'), - z - .object({ - actions_left: performActionActionSchema.optional(), - actions_left_start: performActionActionSchema.optional(), - actions_left_stop: performActionActionSchema.optional(), - - actions_right: performActionActionSchema.optional(), - actions_right_start: performActionActionSchema.optional(), - actions_right_stop: performActionActionSchema.optional(), - - actions_up: performActionActionSchema.optional(), - actions_up_start: performActionActionSchema.optional(), - actions_up_stop: performActionActionSchema.optional(), - - actions_down: performActionActionSchema.optional(), - actions_down_start: performActionActionSchema.optional(), - actions_down_stop: performActionActionSchema.optional(), - - actions_zoom_in: performActionActionSchema.optional(), - actions_zoom_in_start: performActionActionSchema.optional(), - actions_zoom_in_stop: performActionActionSchema.optional(), - - actions_zoom_out: performActionActionSchema.optional(), - actions_zoom_out_start: performActionActionSchema.optional(), - actions_zoom_out_stop: performActionActionSchema.optional(), - - // The number of seconds between subsequent relative calls when converting a - // relative request into a continuous request. - r2c_delay_between_calls_seconds: z - .number() - .default(ptzCameraConfigDefaults.r2c_delay_between_calls_seconds), - - // The number of seconds between the start/stop call when converting a - // continuous request into a relative request. - c2r_delay_between_calls_seconds: z - .number() - .default(ptzCameraConfigDefaults.c2r_delay_between_calls_seconds), - - presets: z - .preprocess( - dataPTZFormatToFullFormat(''), - z.union([ - z.record(performActionActionSchema), - - // This is used by the data_ style of action. - z.object({ service: z.string().optional() }), - ]), - ) - .optional(), - - // This is used by the data_ style of action. - service: z.string().optional(), - }) - // We allow passthrough as there may be user-configured presets as "actions_" . - .passthrough(), -); - -const ptzControlsDefaults = { - orientation: 'horizontal' as const, - mode: 'auto' as const, - hide_pan_tilt: false, - hide_zoom: false, - hide_home: false, - position: 'bottom-right' as const, -}; - -export const ptzControlsConfigSchema = z.object({ - mode: z.enum(['off', 'auto', 'on']).default(ptzControlsDefaults.mode), - position: z - .enum(['top-left', 'top-right', 'bottom-left', 'bottom-right']) - .default(ptzControlsDefaults.position), - orientation: z - .enum(['vertical', 'horizontal']) - .default(ptzControlsDefaults.orientation), - - hide_pan_tilt: z.boolean().default(ptzControlsDefaults.hide_pan_tilt), - hide_zoom: z.boolean().default(ptzControlsDefaults.hide_zoom), - hide_home: z.boolean().default(ptzControlsDefaults.hide_home), - - style: z.object({}).passthrough().optional(), -}); -export type PTZControlsConfig = z.infer; - -// ************************************************************************* -// Image Configuration -// Image config base options are used both for the `image` live provider and the -// `image` card view. -// ************************************************************************* - -const imageConfigDefault = { - mode: 'auto' as const, - refresh_seconds: 1, - zoomable: true, -}; - -const IMAGE_MODES = ['auto', 'camera', 'entity', 'screensaver', 'url'] as const; -export type ImageMode = (typeof IMAGE_MODES)[number]; - -const imageBaseConfigSchema = z.object({ - mode: z.enum(IMAGE_MODES).default(imageConfigDefault.mode), - - refresh_seconds: z.number().min(0).default(imageConfigDefault.refresh_seconds), - - url: z.string().optional(), - entity: z.string().optional(), - entity_parameters: z.string().optional(), -}); - -const imageConfigSchema = imageBaseConfigSchema - .extend({ - zoomable: z.boolean().default(imageConfigDefault.zoomable), - }) - .merge(actionsSchema) - .default(imageConfigDefault); -export type ImageViewConfig = z.infer; - -// ************************************************************************* -// Thumbnail Configuration -// ************************************************************************* - -// The min/max width thumbnail. -export const THUMBNAIL_WIDTH_MIN = 75; -export const THUMBNAIL_WIDTH_MAX = 300; - -const thumbnailControlsBaseDefaults = { - size: 100, - show_details: true, - show_favorite_control: true, - show_timeline_control: true, - show_download_control: true, -}; - -// Configuration for the actual rendered thumbnail. -const thumbnailsControlBaseSchema = z.object({ - size: z - .number() - .min(THUMBNAIL_WIDTH_MIN) - .max(THUMBNAIL_WIDTH_MAX) - .default(thumbnailControlsBaseDefaults.size), - show_details: z.boolean().default(thumbnailControlsBaseDefaults.show_details), - show_favorite_control: z - .boolean() - .default(thumbnailControlsBaseDefaults.show_favorite_control), - show_timeline_control: z - .boolean() - .default(thumbnailControlsBaseDefaults.show_timeline_control), - show_download_control: z - .boolean() - .default(thumbnailControlsBaseDefaults.show_download_control), -}); -export type ThumbnailsControlBaseConfig = z.infer; - -const thumbnailControlsDefaults = { - ...thumbnailControlsBaseDefaults, - mode: 'right' as const, -}; - -const thumbnailsControlSchema = thumbnailsControlBaseSchema.extend({ - mode: z - .enum(['none', 'above', 'below', 'left', 'right']) - .default(thumbnailControlsDefaults.mode), -}); -export type ThumbnailsControlConfig = z.infer; - -// ************************************************************************* -// Event Media Type Configuration -// ************************************************************************* -const eventsMediaTypeSchema = z.enum(['all', 'clips', 'snapshots']); - -// ************************************************************************* -// Timeline Configuration -// ************************************************************************* - -const timelineCoreConfigDefault = { - clustering_threshold: 3, - events_media_type: 'all' as const, - window_seconds: 60 * 60, - show_recordings: true, - style: 'stack' as const, - pan_mode: 'pan' as const, - format: { - '24h': true, - }, -}; - -const timelinePanModeSchema = z.enum(['pan', 'seek', 'seek-in-media', 'seek-in-camera']); -export type TimelinePanMode = z.infer; - -const timelineFormatSchema = z.object({ - '24h': z.boolean().optional().default(timelineCoreConfigDefault.format['24h']), -}); - -const timelineCoreConfigSchema = z.object({ - clustering_threshold: z - .number() - .optional() - .default(timelineCoreConfigDefault.clustering_threshold), - events_media_type: eventsMediaTypeSchema - .optional() - .default(timelineCoreConfigDefault.events_media_type), - window_seconds: z - .number() - .min(1 * 60) - .max(24 * 60 * 60) - .optional() - .default(timelineCoreConfigDefault.window_seconds), - show_recordings: z - .boolean() - .optional() - .default(timelineCoreConfigDefault.show_recordings), - style: z.enum(['stack', 'ribbon']).optional().default(timelineCoreConfigDefault.style), - pan_mode: timelinePanModeSchema.optional().default(timelineCoreConfigDefault.pan_mode), - format: timelineFormatSchema.optional().default(timelineCoreConfigDefault.format), -}); -export type TimelineCoreConfig = z.infer; - -const miniTimelineConfigDefault = { - ...timelineCoreConfigDefault, - mode: 'none' as const, - - // Mini-timeline defaults to ribbon style. - style: 'ribbon' as const, -}; - -const miniTimelineConfigSchema = timelineCoreConfigSchema.extend({ - mode: z.enum(['none', 'above', 'below']).default(miniTimelineConfigDefault.mode), - style: timelineCoreConfigSchema.shape.style.default(miniTimelineConfigDefault.style), -}); -export type MiniTimelineControlConfig = z.infer; - -const timelineConfigDefault = { - ...timelineCoreConfigDefault, - controls: { - thumbnails: thumbnailControlsDefaults, - }, -}; -const timelineConfigSchema = timelineCoreConfigSchema - .extend({ - controls: z - .object({ - thumbnails: thumbnailsControlSchema.default( - timelineConfigDefault.controls.thumbnails, - ), - }) - .default(timelineConfigDefault.controls), - }) - .default(timelineConfigDefault); -export type TimelineConfig = z.infer; - -// ************************************************************************* -// Next/Previous Control Configuration -// ************************************************************************* - -const nextPreviousControlConfigSchema = z.object({ - style: z.enum(['none', 'chevrons', 'icons', 'thumbnails']), - size: z.number().min(BUTTON_SIZE_MIN), -}); -export type NextPreviousControlConfig = z.infer; - -// ************************************************************************* -// Carousel Transition Configuration -// ************************************************************************* - -const transitionEffectConfigSchema = z.enum(['none', 'slide']); -export type TransitionEffect = z.infer; - -// ************************************************************************* -// Live Configuration -// ************************************************************************* - -const LIVE_PROVIDERS = [ - 'auto', - 'image', - 'ha', - 'jsmpeg', - 'go2rtc', - 'webrtc-card', -] as const; -export type LiveProvider = (typeof LIVE_PROVIDERS)[number]; - -const microphoneConfigDefault = { - always_connected: false, - disconnect_seconds: 90, - mute_after_microphone_mute_seconds: 60, -}; - -const microphoneConfigSchema = z - .object({ - always_connected: z.boolean().default(microphoneConfigDefault.always_connected), - disconnect_seconds: z - .number() - .min(0) - .default(microphoneConfigDefault.disconnect_seconds), - mute_after_microphone_mute_seconds: z - .number() - .min(0) - .default(microphoneConfigDefault.mute_after_microphone_mute_seconds), - }) - .default(microphoneConfigDefault); -export type MicrophoneConfig = z.infer; - -const go2rtcConfigSchema = z.object({ - url: z - .string() - .transform((input) => input.replace(/\/+$/, '')) - .optional(), - host: z.string().optional(), - modes: z.enum(['webrtc', 'mse', 'mp4', 'mjpeg']).array().optional(), - stream: z.string().optional(), -}); - -const webrtcCardConfigSchema = z - .object({ - entity: z.string().optional(), - url: z.string().optional(), - }) - .passthrough(); - -const jsmpegConfigSchema = z.object({ - options: z - .object({ - // https://github.com/phoboslab/jsmpeg#usage - audio: z.boolean().optional(), - video: z.boolean().optional(), - pauseWhenHidden: z.boolean().optional(), - disableGl: z.boolean().optional(), - disableWebAssembly: z.boolean().optional(), - preserveDrawingBuffer: z.boolean().optional(), - progressive: z.boolean().optional(), - throttled: z.boolean().optional(), - chunkSize: z.number().optional(), - maxAudioLag: z.number().optional(), - videoBufferSize: z.number().optional(), - audioBufferSize: z.number().optional(), - }) - .optional(), -}); - -const liveThumbnailControlsDefaults = { - ...thumbnailControlsDefaults, - media_type: 'events' as const, - events_media_type: 'all' as const, -}; - -const liveConfigDefault = { - auto_play: [...MEDIA_ACTION_POSITIVE_CONDITIONS], - auto_pause: [], - auto_mute: [...MEDIA_MUTE_CONDITIONS], - auto_unmute: ['microphone' as const], - preload: false, - lazy_load: true, - lazy_unload: [], - draggable: true, - zoomable: true, - transition_effect: 'slide' as const, - show_image_during_load: true, - mode: 'single' as const, - controls: { - builtin: true, - next_previous: { - size: 48, - style: 'chevrons' as const, - }, - ptz: ptzControlsDefaults, - thumbnails: liveThumbnailControlsDefaults, - timeline: miniTimelineConfigDefault, - }, - microphone: { - ...microphoneConfigDefault, - }, -}; - -const livethumbnailsControlSchema = thumbnailsControlSchema.extend({ - media_type: z - .enum(['events', 'recordings']) - .default(liveConfigDefault.controls.thumbnails.media_type), - events_media_type: eventsMediaTypeSchema.default( - liveConfigDefault.controls.thumbnails.events_media_type, - ), -}); - -const liveConfigSchema = z - .object({ - auto_pause: z - .enum(MEDIA_ACTION_NEGATIVE_CONDITIONS) - .array() - .default(liveConfigDefault.auto_pause), - auto_play: z - .enum(MEDIA_ACTION_POSITIVE_CONDITIONS) - .array() - .default(liveConfigDefault.auto_play), - auto_mute: z - .enum(MEDIA_MUTE_CONDITIONS) - .array() - .default(liveConfigDefault.auto_mute), - auto_unmute: z - .enum(MEDIA_UNMUTE_CONDITIONS) - .array() - .default(liveConfigDefault.auto_unmute), - controls: z - .object({ - builtin: z.boolean().default(liveConfigDefault.controls.builtin), - next_previous: nextPreviousControlConfigSchema - .extend({ - // Live cannot show thumbnails, remove that option. - style: z - .enum(['none', 'chevrons', 'icons']) - .default(liveConfigDefault.controls.next_previous.style), - size: nextPreviousControlConfigSchema.shape.size.default( - liveConfigDefault.controls.next_previous.size, - ), - }) - .default(liveConfigDefault.controls.next_previous), - ptz: ptzControlsConfigSchema.default(liveConfigDefault.controls.ptz), - thumbnails: livethumbnailsControlSchema.default( - liveConfigDefault.controls.thumbnails, - ), - timeline: miniTimelineConfigSchema.default(liveConfigDefault.controls.timeline), - }) - .default(liveConfigDefault.controls), - display: viewDisplaySchema, - draggable: z.boolean().default(liveConfigDefault.draggable), - lazy_load: z.boolean().default(liveConfigDefault.lazy_load), - lazy_unload: z - .enum(MEDIA_ACTION_NEGATIVE_CONDITIONS) - .array() - .default(liveConfigDefault.lazy_unload), - microphone: microphoneConfigSchema.default(liveConfigDefault.microphone), - preload: z.boolean().default(liveConfigDefault.preload), - show_image_during_load: z - .boolean() - .default(liveConfigDefault.show_image_during_load), - transition_effect: transitionEffectConfigSchema.default( - liveConfigDefault.transition_effect, - ), - zoomable: z.boolean().default(liveConfigDefault.zoomable), - }) - .merge(actionsSchema) - .default(liveConfigDefault); -export type LiveConfig = z.infer; - -// ************************************************************************* -// Cast Configuration -// ************************************************************************* - -const castConfigDefault = { - method: 'standard' as const, -}; - -const castSchema = z.object({ - method: z.enum(['standard', 'dashboard']).default(castConfigDefault.method).optional(), - dashboard: z - .object({ - dashboard_path: z.string().optional(), - view_path: z.string().optional(), - }) - .optional(), -}); - -// ************************************************************************* -// Camera Configuration -// ************************************************************************* - -const ENGINES = ['auto', 'frigate', 'generic', 'motioneye', 'reolink'] as const; - -const cameraConfigDefault = { - dependencies: { - all_cameras: false, - cameras: [], - }, - engine: 'auto' as const, - frigate: { - client_id: 'frigate' as const, - }, - live_provider: 'auto' as const, - motioneye: { - images: { - directory_pattern: '%Y-%m-%d' as const, - file_pattern: '%H-%M-%S' as const, - }, - movies: { - directory_pattern: '%Y-%m-%d' as const, - file_pattern: '%H-%M-%S' as const, - }, - }, - reolink: { - media_resolution: 'low' as const, - }, - ptz: ptzCameraConfigDefaults, - triggers: { - motion: false, - occupancy: false, - events: [...CAMERA_TRIGGER_EVENT_TYPES], - entities: [], - }, - proxy: { - media: 'auto' as const, - dynamic: true, - ssl_verification: 'auto' as const, - ssl_ciphers: 'auto' as const, - }, - always_error_if_entity_unavailable: false, -}; - -const SSL_CIPHERS = ['default', 'insecure', 'intermediate', 'modern'] as const; -export type SSLCiphers = (typeof SSL_CIPHERS)[number]; - -const proxyConfigSchema = z.object({ - media: z.boolean().or(z.literal('auto')).default(cameraConfigDefault.proxy.media), - dynamic: z.boolean().default(cameraConfigDefault.proxy.dynamic), - ssl_verification: z - .boolean() - .or(z.literal('auto')) - .default(cameraConfigDefault.proxy.ssl_verification), - ssl_ciphers: z - .enum(SSL_CIPHERS) - .or(z.literal('auto')) - .default(cameraConfigDefault.proxy.ssl_ciphers), -}); -export type ProxyConfig = z.infer; - -export const cameraConfigSchema = z - .object({ - camera_entity: z.string().optional(), - - // Used for presentation in the UI (autodetected from the entity if - // specified). - icon: z.string().optional(), - title: z.string().optional(), - - capabilities: z - .object({ - disable: z.enum(capabilityKeys).array().optional(), - disable_except: z.enum(capabilityKeys).array().optional(), - }) - .optional(), - - // Optional identifier to separate different camera configurations used in - // this card. - id: z.string().optional(), - - dependencies: z - .object({ - all_cameras: z.boolean().default(cameraConfigDefault.dependencies.all_cameras), - cameras: z.string().array().default(cameraConfigDefault.dependencies.cameras), - }) - .default(cameraConfigDefault.dependencies), - - triggers: z - .object({ - motion: z.boolean().default(cameraConfigDefault.triggers.motion), - occupancy: z.boolean().default(cameraConfigDefault.triggers.occupancy), - entities: z.string().array().default(cameraConfigDefault.triggers.entities), - events: z - .enum(CAMERA_TRIGGER_EVENT_TYPES) - .array() - .default(cameraConfigDefault.triggers.events), - }) - .default(cameraConfigDefault.triggers), - - // Engine options. - engine: z.enum(ENGINES).default('auto'), - frigate: z - .object({ - url: z.string().optional(), - client_id: z.string().default(cameraConfigDefault.frigate.client_id), - camera_name: z.string().optional(), - labels: z.string().array().optional(), - zones: z.string().array().optional(), - }) - .default(cameraConfigDefault.frigate), - motioneye: z - .object({ - url: z.string().optional(), - images: z - .object({ - directory_pattern: z - .string() - .includes('%') - .default(cameraConfigDefault.motioneye.images.directory_pattern), - file_pattern: z - .string() - .includes('%') - .default(cameraConfigDefault.motioneye.images.file_pattern), - }) - .default(cameraConfigDefault.motioneye.images), - movies: z - .object({ - directory_pattern: z - .string() - .includes('%') - .default(cameraConfigDefault.motioneye.movies.directory_pattern), - file_pattern: z - .string() - .includes('%') - .default(cameraConfigDefault.motioneye.movies.file_pattern), - }) - .default(cameraConfigDefault.motioneye.movies), - }) - .default(cameraConfigDefault.motioneye), - reolink: z - .object({ - url: z.string().optional(), - media_resolution: z - .enum(['high', 'low']) - .default(cameraConfigDefault.reolink.media_resolution), - }) - .default(cameraConfigDefault.reolink), - - // Live provider options. - live_provider: z.enum(LIVE_PROVIDERS).default(cameraConfigDefault.live_provider), - go2rtc: go2rtcConfigSchema.optional(), - image: imageBaseConfigSchema.optional().default(imageConfigDefault), - jsmpeg: jsmpegConfigSchema.optional(), - webrtc_card: webrtcCardConfigSchema.optional(), - - cast: castSchema.optional(), - - ptz: ptzCameraConfigSchema.default(cameraConfigDefault.ptz), - - dimensions: z - .object({ - aspect_ratio: aspectRatioSchema.optional(), - layout: mediaLayoutConfigSchema.optional(), - }) - .optional(), - - proxy: proxyConfigSchema.default(cameraConfigDefault.proxy), - - // See: https://github.com/dermotduffy/advanced-camera-card/issues/1650 - always_error_if_entity_unavailable: z - .boolean() - .default(cameraConfigDefault.always_error_if_entity_unavailable), - }) - .default(cameraConfigDefault); -export type CameraConfig = z.infer; - -// Avoid using .nonempty() to avoid changing the inferred type -// (https://github.com/colinhacks/zod#minmaxlength). -const camerasConfigSchema = cameraConfigSchema.array().min(1); -export type CamerasConfig = z.infer; - -// ************************************************************************* -// View Configuration -// ************************************************************************* - -const viewConfigDefault = { - default: VIEW_DEFAULT, - camera_select: 'current' as const, - interaction_seconds: 300, - default_reset: { - every_seconds: 0, - after_interaction: false, - entities: [], - interaction_mode: 'inactive' as const, - }, - default_cycle_camera: false, - dim: false, - theme: { - themes: ['traditional' as const], - }, - triggers: { - show_trigger_status: false, - filter_selected_camera: true, - actions: { - trigger: 'update' as const, - untrigger: 'none' as const, - }, - untrigger_seconds: 0, - }, - keyboard_shortcuts: keyboardShortcutsDefault, -}; - -const interactionModeSchema = z.enum(['all', 'inactive', 'active']).default('inactive'); -export type InteractionMode = z.infer; - -export const triggersSchema = z.object({ - actions: z - .object({ - interaction_mode: interactionModeSchema, - trigger: z - .enum(['default', 'live', 'media', 'none', 'update']) - .default(viewConfigDefault.triggers.actions.trigger), - untrigger: z - .enum(['default', 'none']) - .default(viewConfigDefault.triggers.actions.untrigger), - }) - .default(viewConfigDefault.triggers.actions), - filter_selected_camera: z - .boolean() - .default(viewConfigDefault.triggers.filter_selected_camera), - show_trigger_status: z - .boolean() - .default(viewConfigDefault.triggers.show_trigger_status), - untrigger_seconds: z.number().default(viewConfigDefault.triggers.untrigger_seconds), -}); -export type TriggersOptions = z.infer; - -const themeName = z.enum(['ha', 'dark', 'light', 'traditional']); -export type ThemeName = z.infer; - -const themeConfigSchema = z.object({ - themes: themeName.array().default(viewConfigDefault.theme.themes), - overrides: z.record(z.string()).optional(), -}); -export type ThemeConfig = z.infer; - -const viewConfigSchema = z - .object({ - default: z.enum(VIEWS_USER_SPECIFIED).default(viewConfigDefault.default), - camera_select: z - .enum([...VIEWS_USER_SPECIFIED, 'current']) - .default(viewConfigDefault.camera_select), - interaction_seconds: z.number().default(viewConfigDefault.interaction_seconds), - default_cycle_camera: z.boolean().default(viewConfigDefault.default_cycle_camera), - - default_reset: z - .object({ - after_interaction: z - .boolean() - .default(viewConfigDefault.default_reset.after_interaction), - every_seconds: z.number().default(viewConfigDefault.default_reset.every_seconds), - entities: z.string().array().default(viewConfigDefault.default_reset.entities), - interaction_mode: interactionModeSchema.default( - viewConfigDefault.default_reset.interaction_mode, - ), - }) - .default(viewConfigDefault.default_reset), - - render_entities: z.string().array().optional(), - - theme: themeConfigSchema.default(viewConfigDefault.theme), - - dim: z.boolean().default(viewConfigDefault.dim), - triggers: triggersSchema.default(viewConfigDefault.triggers), - keyboard_shortcuts: keyboardShortcutsSchema.default( - viewConfigDefault.keyboard_shortcuts, - ), - }) - .merge(actionsSchema) - .default(viewConfigDefault); - -// ************************************************************************* -// Menu Configuration -// ************************************************************************* - -const MENU_STYLES = [ - 'none', - 'hidden', - 'overlay', - 'hover', - 'hover-card', - 'outside', -] as const; -const MENU_POSITIONS = ['left', 'right', 'top', 'bottom'] as const; -const MENU_ALIGNMENTS = MENU_POSITIONS; - -const visibleButtonDefault = { - priority: MENU_PRIORITY_DEFAULT, - enabled: true, -}; - -const hiddenButtonDefault = { - priority: MENU_PRIORITY_DEFAULT, - enabled: false, -}; - -const menuConfigDefault = { - alignment: 'left' as const, - button_size: 40, - buttons: { - camera_ui: visibleButtonDefault, - cameras: visibleButtonDefault, - clips: visibleButtonDefault, - ptz_home: hiddenButtonDefault, - display_mode: visibleButtonDefault, - download: visibleButtonDefault, - expand: hiddenButtonDefault, - iris: visibleButtonDefault, - fullscreen: visibleButtonDefault, - image: hiddenButtonDefault, - live: visibleButtonDefault, - media_player: visibleButtonDefault, - microphone: { - ...hiddenButtonDefault, - type: 'momentary' as const, - }, - mute: hiddenButtonDefault, - play: hiddenButtonDefault, - ptz_controls: hiddenButtonDefault, - recordings: hiddenButtonDefault, - screenshot: hiddenButtonDefault, - snapshots: visibleButtonDefault, - substreams: visibleButtonDefault, - timeline: visibleButtonDefault, - }, - position: 'top' as const, - style: 'hidden' as const, -}; - -const visibleButtonSchema = menuBaseSchema.extend({ - enabled: menuBaseSchema.shape.enabled.default(visibleButtonDefault.enabled), - priority: menuBaseSchema.shape.priority.default(visibleButtonDefault.priority), -}); - -const hiddenButtonSchema = menuBaseSchema.extend({ - enabled: menuBaseSchema.shape.enabled.default(hiddenButtonDefault.enabled), - priority: menuBaseSchema.shape.priority.default(hiddenButtonDefault.priority), -}); - -export const menuConfigSchema = z - .object({ - style: z.enum(MENU_STYLES).default(menuConfigDefault.style), - position: z.enum(MENU_POSITIONS).default(menuConfigDefault.position), - alignment: z.enum(MENU_ALIGNMENTS).default(menuConfigDefault.alignment), - buttons: z - .object({ - camera_ui: visibleButtonSchema.default(menuConfigDefault.buttons.camera_ui), - cameras: visibleButtonSchema.default(menuConfigDefault.buttons.cameras), - clips: visibleButtonSchema.default(menuConfigDefault.buttons.clips), - ptz_home: hiddenButtonSchema.default(menuConfigDefault.buttons.ptz_home), - display_mode: visibleButtonSchema.default( - menuConfigDefault.buttons.display_mode, - ), - download: visibleButtonSchema.default(menuConfigDefault.buttons.download), - expand: hiddenButtonSchema.default(menuConfigDefault.buttons.expand), - iris: visibleButtonSchema.default(menuConfigDefault.buttons.iris), - fullscreen: visibleButtonSchema.default(menuConfigDefault.buttons.fullscreen), - image: hiddenButtonSchema.default(menuConfigDefault.buttons.image), - live: visibleButtonSchema.default(menuConfigDefault.buttons.live), - media_player: visibleButtonSchema.default( - menuConfigDefault.buttons.media_player, - ), - microphone: hiddenButtonSchema - .extend({ - type: z - .enum(['momentary', 'toggle']) - .default(menuConfigDefault.buttons.microphone.type), - }) - .default(menuConfigDefault.buttons.microphone), - mute: hiddenButtonSchema.default(menuConfigDefault.buttons.mute), - play: hiddenButtonSchema.default(menuConfigDefault.buttons.play), - ptz_controls: hiddenButtonSchema.default(menuConfigDefault.buttons.ptz_controls), - recordings: hiddenButtonSchema.default(menuConfigDefault.buttons.recordings), - screenshot: hiddenButtonSchema.default(menuConfigDefault.buttons.screenshot), - snapshots: visibleButtonSchema.default(menuConfigDefault.buttons.snapshots), - substreams: visibleButtonSchema.default(menuConfigDefault.buttons.substreams), - timeline: visibleButtonSchema.default(menuConfigDefault.buttons.timeline), - }) - .default(menuConfigDefault.buttons), - button_size: z.number().min(BUTTON_SIZE_MIN).default(menuConfigDefault.button_size), - }) - .default(menuConfigDefault); -export type MenuConfig = z.infer; - -// ************************************************************************* -// Status Bar Configuration -// ************************************************************************* - -const STATUS_BAR_STYLES = [ - 'none', - 'overlay', - 'hover', - 'hover-card', - 'outside', - 'popup', -] as const; - -const STATUS_BAR_POSITIONS = ['top', 'bottom'] as const; - -const statusBarItemDefault = { - priority: STATUS_BAR_PRIORITY_DEFAULT, - enabled: true, -}; - -const statusBarConfigDefault = { - height: 40, - items: { - engine: statusBarItemDefault, - resolution: statusBarItemDefault, - technology: statusBarItemDefault, - title: statusBarItemDefault, - }, - position: 'bottom' as const, - style: 'popup' as const, - popup_seconds: 3, -}; - -export const statusBarConfigSchema = z - .object({ - position: z.enum(STATUS_BAR_POSITIONS).default(statusBarConfigDefault.position), - style: z.enum(STATUS_BAR_STYLES).default(statusBarConfigDefault.style), - popup_seconds: z - .number() - .min(0) - .max(60) - .default(statusBarConfigDefault.popup_seconds), - height: z.number().min(STATUS_BAR_HEIGHT_MIN).default(statusBarConfigDefault.height), - items: z - .object({ - engine: statusBarItemBaseSchema.default(statusBarConfigDefault.items.engine), - technology: statusBarItemBaseSchema.default( - statusBarConfigDefault.items.technology, - ), - resolution: statusBarItemBaseSchema.default( - statusBarConfigDefault.items.resolution, - ), - title: statusBarItemBaseSchema.default(statusBarConfigDefault.items.title), - }) - .default(statusBarConfigDefault.items), - }) - .default(statusBarConfigDefault); -export type StatusBarConfig = z.infer; - -// ************************************************************************* -// Event Viewer Configuration -// ************************************************************************* - -const viewerConfigDefault = { - auto_play: [...MEDIA_ACTION_POSITIVE_CONDITIONS], - auto_pause: [...MEDIA_ACTION_NEGATIVE_CONDITIONS], - auto_mute: [...MEDIA_ACTION_NEGATIVE_CONDITIONS], - auto_unmute: [], - lazy_load: true, - draggable: true, - zoomable: true, - transition_effect: 'slide' as const, - snapshot_click_plays_clip: true, - display_mode: 'single' as const, - controls: { - builtin: true, - next_previous: { - size: 48, - style: 'thumbnails' as const, - }, - thumbnails: thumbnailControlsDefaults, - timeline: miniTimelineConfigDefault, - ptz: { - ...ptzControlsDefaults, - mode: 'off' as const, - }, - }, -}; - -const viewerNextPreviousControlConfigSchema = nextPreviousControlConfigSchema.extend({ - style: z - .enum(['none', 'thumbnails', 'chevrons']) - .default(viewerConfigDefault.controls.next_previous.style), - size: nextPreviousControlConfigSchema.shape.size.default( - viewerConfigDefault.controls.next_previous.size, - ), -}); - -const viewerConfigSchema = z - .object({ - auto_play: z - .enum(MEDIA_ACTION_POSITIVE_CONDITIONS) - .array() - .default(viewerConfigDefault.auto_play), - auto_pause: z - .enum(MEDIA_ACTION_NEGATIVE_CONDITIONS) - .array() - .default(viewerConfigDefault.auto_pause), - - // Don't use MEDIA_UNMUTE_CONDITIONS and MEDIA_MUTE_CONDITIONS here, since - // it includes 'microphone' which doesn't make sense for viewer media. - auto_mute: z - .enum(MEDIA_ACTION_NEGATIVE_CONDITIONS) - .array() - .default(viewerConfigDefault.auto_mute), - auto_unmute: z - .enum(MEDIA_ACTION_POSITIVE_CONDITIONS) - .array() - .default(viewerConfigDefault.auto_unmute), - lazy_load: z.boolean().default(viewerConfigDefault.lazy_load), - draggable: z.boolean().default(viewerConfigDefault.draggable), - zoomable: z.boolean().default(viewerConfigDefault.zoomable), - transition_effect: transitionEffectConfigSchema.default( - viewerConfigDefault.transition_effect, - ), - snapshot_click_plays_clip: z - .boolean() - .default(viewerConfigDefault.snapshot_click_plays_clip), - display: viewDisplaySchema, - controls: z - .object({ - builtin: z.boolean().default(viewerConfigDefault.controls.builtin), - next_previous: viewerNextPreviousControlConfigSchema.default( - viewerConfigDefault.controls.next_previous, - ), - ptz: ptzControlsConfigSchema - .extend({ - // The media_viewer ptz has no 'auto' mode. - mode: z.enum(['off', 'on']).default(viewerConfigDefault.controls.ptz.mode), - }) - .default(viewerConfigDefault.controls.ptz), - thumbnails: thumbnailsControlSchema.default( - viewerConfigDefault.controls.thumbnails, - ), - timeline: miniTimelineConfigSchema.default( - viewerConfigDefault.controls.timeline, - ), - }) - .default(viewerConfigDefault.controls), - }) - .merge(actionsSchema) - .default(viewerConfigDefault); -export type ViewerConfig = z.infer; - -// ************************************************************************* -// Event Gallery Configuration -// ************************************************************************* - -const galleryThumbnailControlsDefaults = { - ...thumbnailControlsDefaults, - show_details: false, -}; - -const galleryConfigDefault = { - controls: { - thumbnails: galleryThumbnailControlsDefaults, - filter: { - mode: 'right' as const, - }, - }, -}; - -const gallerythumbnailsControlSchema = thumbnailsControlSchema.extend({ - show_details: z.boolean().default(galleryThumbnailControlsDefaults.show_details), -}); - -const galleryConfigSchema = z - .object({ - controls: z - .object({ - thumbnails: gallerythumbnailsControlSchema.default( - galleryConfigDefault.controls.thumbnails, - ), - filter: z - .object({ - mode: z - .enum(['none', 'left', 'right']) - .default(galleryConfigDefault.controls.filter.mode), - }) - .default(galleryConfigDefault.controls.filter), - }) - .default(galleryConfigDefault.controls), - }) - .merge(actionsSchema) - .default(galleryConfigDefault); -export type GalleryConfig = z.infer; - -// ************************************************************************* -// Dimensions Configuration -// ************************************************************************* - -const dimensionsConfigDefault = { - aspect_ratio_mode: 'dynamic' as const, - aspect_ratio: [16, 9], - height: 'auto', -}; - -export const dimensionsConfigSchema = z - .object({ - aspect_ratio_mode: z - .enum(['dynamic', 'static', 'unconstrained']) - .default(dimensionsConfigDefault.aspect_ratio_mode), - aspect_ratio: aspectRatioSchema.default(dimensionsConfigDefault.aspect_ratio), - height: z.string().default(dimensionsConfigDefault.height), - }) - .default(dimensionsConfigDefault); - -// ************************************************************************* -// Override Configuration -// ************************************************************************* - -const overridesSchema = z - .object({ - conditions: advancedCameraCardConditionSchema.array(), - merge: z.object({}).passthrough().optional(), - set: z.object({}).passthrough().optional(), - delete: z.string().array().optional(), - }) - .array() - .optional(); -export type Overrides = z.infer; - -// ************************************************************************* -// Automation Configuration -// ************************************************************************* - -const automationActionSchema = actionConfigSchema.array(); -export type AutomationActions = z.infer; - -const automationSchema = z - .object({ - conditions: advancedCameraCardConditionSchema.array(), - actions: automationActionSchema.optional(), - actions_not: automationActionSchema.optional(), - }) - .refine( - (data) => data.actions?.length || data.actions_not?.length, - 'Automations must include at least one action', - ); -export type Automation = z.infer; - -const automationsSchema = automationSchema.array(); - -// ************************************************************************* -// Performance Configuration -// ************************************************************************* - -const performanceConfigDefault = { - features: { - animated_progress_indicator: true, - card_loading_indicator: true, - media_chunk_size: MEDIA_CHUNK_SIZE_DEFAULT, - }, - style: { - border_radius: true, - box_shadow: true, - }, -}; - -export const performanceConfigSchema = z - .object({ - features: z - .object({ - animated_progress_indicator: z - .boolean() - .default(performanceConfigDefault.features.animated_progress_indicator), - card_loading_indicator: z - .boolean() - .default(performanceConfigDefault.features.card_loading_indicator), - media_chunk_size: z - .number() - .min(0) - .max(MEDIA_CHUNK_SIZE_MAX) - .default(performanceConfigDefault.features.media_chunk_size), - max_simultaneous_engine_requests: z.number().min(1).optional(), - }) - .default(performanceConfigDefault.features), - style: z - .object({ - border_radius: z.boolean().default(performanceConfigDefault.style.border_radius), - box_shadow: z.boolean().default(performanceConfigDefault.style.box_shadow), - }) - .default(performanceConfigDefault.style), - }) - .default(performanceConfigDefault); -export type PerformanceConfig = z.infer; - -const debugConfigDefault = { - logging: false, -}; - -const debugConfigSchema = z - .object({ - logging: z.boolean().default(debugConfigDefault.logging), - }) - .default(debugConfigDefault); -type DebugConfig = z.infer; - -// ************************************************************************* -// CardWideConfig Configuration -// ************************************************************************* - -export interface CardWideConfig { - performance?: PerformanceConfig; - debug?: DebugConfig; -} - -// ************************************************************************* -// *** Profile Configuration *** -// ************************************************************************* -const PROFILES = ['casting', 'low-performance', 'scrubbing'] as const; -export type ProfileType = (typeof PROFILES)[number]; -export const profilesSchema = z.enum(PROFILES).array().optional(); - -// ************************************************************************* -// *** Remote Control Configuration *** -// ************************************************************************* - -const remoteControlConfigSchema = z - .object({ - entities: z - .object({ - camera: z.string().startsWith('input_select.').optional(), - }) - .optional(), - }) - .optional(); - -// ************************************************************************* -// *** Card Configuration *** -// ************************************************************************* - -export const advancedCameraCardConfigSchema = z.object({ - // Defaults are stripped out of the individual cameras, since each camera will - // be merged with `cameras_global` which *does* have defaults. If we didn't do - // this, the default values of each individual camera would override the - // intentionally specified values in `cameras_global` during camera - // initialization when the two configs are merged. - cameras: deepRemoveDefaults(camerasConfigSchema), - cameras_global: cameraConfigSchema, - - view: viewConfigSchema, - menu: menuConfigSchema, - status_bar: statusBarConfigSchema, - live: liveConfigSchema, - media_gallery: galleryConfigSchema, - media_viewer: viewerConfigSchema, - image: imageConfigSchema, - elements: pictureElementsSchema, - dimensions: dimensionsConfigSchema, - timeline: timelineConfigSchema, - performance: performanceConfigSchema, - debug: debugConfigSchema, - automations: automationsSchema.optional(), - - profiles: profilesSchema, - - // Configuration overrides. - overrides: overridesSchema, - - // Support for card_mod (https://github.com/thomasloven/lovelace-card-mod). - card_mod: z.unknown(), - - // Card ID (used for query string commands). Restrict contents to only values - // that be easily used in a URL. - card_id: z.string().regex(cardIDRegex).optional(), - - remote_control: remoteControlConfigSchema, - - // Stock lovelace card config. - type: z.string(), -}); - -export type AdvancedCameraCardConfig = z.infer; export type RawAdvancedCameraCardConfig = Record; export type RawAdvancedCameraCardConfigArray = RawAdvancedCameraCardConfig[]; - -export const configDefaults = { - cameras: cameraConfigDefault, - view: viewConfigDefault, - menu: menuConfigDefault, - status_bar: statusBarConfigDefault, - live: liveConfigDefault, - media_gallery: galleryConfigDefault, - media_viewer: viewerConfigDefault, - image: imageConfigDefault, - timeline: timelineConfigDefault, - performance: performanceConfigDefault, - debug: debugConfigDefault, -}; diff --git a/src/editor.ts b/src/editor.ts index 47fd8a7a..689ab75c 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -3,7 +3,6 @@ import { customElement, property, state } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import './components/icon.js'; import './components/key-assigner.js'; -import { KeyboardShortcut } from './config/keyboard-shortcuts.js'; import { copyConfig, deleteConfigValue, @@ -13,21 +12,24 @@ import { setConfigValue, upgradeConfig, } from './config/management.js'; -import { setProfiles } from './config/profiles/index.js'; +import { setProfiles } from './config/profiles/set-profiles.js'; import { - AdvancedCameraCardConfig, BUTTON_SIZE_MIN, - configDefaults, MENU_PRIORITY_MAX, - profilesSchema, - RawAdvancedCameraCardConfig, - RawAdvancedCameraCardConfigArray, - STATUS_BAR_HEIGHT_MIN, STATUS_BAR_PRIORITY_MAX, +} from './config/schema/common/const.js'; +import { THUMBNAIL_WIDTH_MAX, THUMBNAIL_WIDTH_MIN, - ZOOM_MAX, - ZOOM_MIN, +} from './config/schema/common/controls/thumbnails.js'; +import { ZOOM_MAX, ZOOM_MIN } from './config/schema/common/zoom.js'; +import { profilesSchema } from './config/schema/profiles.js'; +import { STATUS_BAR_HEIGHT_MIN } from './config/schema/status-bar.js'; +import { AdvancedCameraCardConfig, configDefaults } from './config/schema/types.js'; +import { KeyboardShortcut } from './config/schema/view.js'; +import { + RawAdvancedCameraCardConfig, + RawAdvancedCameraCardConfigArray, } from './config/types.js'; import { CONF_CAMERAS, diff --git a/src/utils/action.ts b/src/utils/action.ts index 49be3df6..8f3c9bac 100644 --- a/src/utils/action.ts +++ b/src/utils/action.ts @@ -1,29 +1,34 @@ import { CardActionsAPI } from '../card-controller/types.js'; import { ZoomSettingsBase } from '../components-lib/zoom/types.js'; -import { PTZAction } from '../config/ptz.js'; +import { CameraSelectActionConfig } from '../config/schema/actions/custom/camera-select.js'; +import { DisplayModeActionConfig } from '../config/schema/actions/custom/display-mode.js'; import { - ActionConfig, - ActionPhase, - ActionsConfig, - AdvancedCameraCardCustomActionConfig, AdvancedCameraCardGeneralAction, - AdvancedCameraCardUserSpecifiedView, - CameraSelectActionConfig, - DisplayModeActionConfig, GeneralActionConfig, +} from '../config/schema/actions/custom/general.js'; +import { INTERNAL_CALLBACK_ACTION, InternalCallbackActionConfig, - LogActionConfig, - LogActionLevel, - MediaPlayerActionConfig, - PerformActionActionConfig, +} from '../config/schema/actions/custom/internal.js'; +import { LogActionConfig, LogActionLevel } from '../config/schema/actions/custom/log.js'; +import { MediaPlayerActionConfig } from '../config/schema/actions/custom/media-player.js'; +import { PTZControlsActionConfig } from '../config/schema/actions/custom/ptz-controls.js'; +import { PTZDigitialActionConfig } from '../config/schema/actions/custom/ptz-digital.js'; +import { PTZMultiActionConfig } from '../config/schema/actions/custom/ptz-multi.js'; +import { + PTZAction, PTZActionConfig, - PTZControlsActionConfig, - PTZDigitialActionConfig, - PTZMultiActionConfig, - SubstreamSelectActionConfig, - ViewActionConfig, -} from '../config/types.js'; + PTZActionPhase, +} from '../config/schema/actions/custom/ptz.js'; +import { SubstreamSelectActionConfig } from '../config/schema/actions/custom/substream-select.js'; +import { ViewActionConfig } from '../config/schema/actions/custom/view.js'; +import { PerformActionActionConfig } from '../config/schema/actions/stock/perform-action.js'; +import { + ActionConfig, + ActionsConfig, + AdvancedCameraCardCustomActionConfig, +} from '../config/schema/actions/types.js'; +import { AdvancedCameraCardUserSpecifiedView } from '../config/schema/common/const.js'; import { ServiceCallRequest } from '../ha/types.js'; import { arrayify } from './basic.js'; @@ -115,7 +120,7 @@ export function createPTZControlsAction( export function createPTZAction(options?: { cardID?: string; ptzAction?: PTZAction; - ptzPhase?: ActionPhase; + ptzPhase?: PTZActionPhase; ptzPreset?: string; cameraID?: string; }): PTZActionConfig { @@ -132,7 +137,7 @@ export function createPTZAction(options?: { export function createPTZDigitalAction(options?: { cardID?: string; - ptzPhase?: ActionPhase; + ptzPhase?: PTZActionPhase; ptzAction?: PTZAction; absolute?: ZoomSettingsBase; targetID?: string; @@ -151,7 +156,7 @@ export function createPTZDigitalAction(options?: { export function createPTZMultiAction(options?: { cardID?: string; ptzAction?: PTZAction; - ptzPhase?: ActionPhase; + ptzPhase?: PTZActionPhase; ptzPreset?: string; targetID?: string; }): PTZMultiActionConfig { diff --git a/src/utils/camera.ts b/src/utils/camera.ts index ab5e952b..8190b797 100644 --- a/src/utils/camera.ts +++ b/src/utils/camera.ts @@ -1,4 +1,5 @@ -import { CameraConfig, RawAdvancedCameraCardConfig } from '../config/types.js'; +import { CameraConfig } from '../config/schema/cameras'; +import { RawAdvancedCameraCardConfig } from '../config/types'; /** * Get a camera id. diff --git a/src/utils/debug.ts b/src/utils/debug.ts index f89f819c..692238d1 100644 --- a/src/utils/debug.ts +++ b/src/utils/debug.ts @@ -1,4 +1,4 @@ -import { CardWideConfig } from '../config/types'; +import { CardWideConfig } from '../config/schema/types'; export const log = (cardWideConfig?: CardWideConfig | null, ...args: unknown[]) => { if (cardWideConfig?.debug?.logging) { diff --git a/src/utils/embla/carousel-controller.ts b/src/utils/embla/carousel-controller.ts index a32de0e6..0fdb66de 100644 --- a/src/utils/embla/carousel-controller.ts +++ b/src/utils/embla/carousel-controller.ts @@ -2,7 +2,7 @@ import EmblaCarousel, { EmblaCarouselType } from 'embla-carousel'; 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 { TransitionEffect } from '../../config/schema/common/transition-effect.js'; import { getChildrenFromElement } from '../basic.js'; import { fireAdvancedCameraCardEvent } from '../fire-advanced-camera-card-event'; import { TextDirection } from '../text-direction'; diff --git a/src/utils/embla/plugins/auto-lazy-load/auto-lazy-load.ts b/src/utils/embla/plugins/auto-lazy-load/auto-lazy-load.ts index c35b825d..da9092bc 100644 --- a/src/utils/embla/plugins/auto-lazy-load/auto-lazy-load.ts +++ b/src/utils/embla/plugins/auto-lazy-load/auto-lazy-load.ts @@ -2,7 +2,7 @@ import { EmblaCarouselType, EmblaEventType } from 'embla-carousel'; import { CreateOptionsType } from 'embla-carousel/components/Options'; import { OptionsHandlerType } from 'embla-carousel/components/OptionsHandler'; import { CreatePluginType, LoosePluginType } from 'embla-carousel/components/Plugins'; -import { LazyUnloadCondition } from '../../../../config/types'; +import { LazyUnloadCondition } from '../../../../config/schema/common/media-actions'; declare module 'embla-carousel/components/Plugins' { interface EmblaPluginsType { diff --git a/src/utils/interaction-mode.ts b/src/utils/interaction-mode.ts index a500f7b5..4d27ee05 100644 --- a/src/utils/interaction-mode.ts +++ b/src/utils/interaction-mode.ts @@ -1,4 +1,4 @@ -import { InteractionMode } from '../config/types'; +import { InteractionMode } from '../config/schema/view'; export const isActionAllowedBasedOnInteractionState = ( interactionMode: InteractionMode, diff --git a/src/utils/media-layout.ts b/src/utils/media-layout.ts index e7c52a0b..7de9333c 100644 --- a/src/utils/media-layout.ts +++ b/src/utils/media-layout.ts @@ -1,4 +1,4 @@ -import { MediaLayoutConfig } from '../config/types'; +import { MediaLayoutConfig } from '../config/schema/camera/media-layout'; /** * Update element style from a media configuration. diff --git a/src/utils/ptz.ts b/src/utils/ptz.ts index 8e8e03b1..34931f78 100644 --- a/src/utils/ptz.ts +++ b/src/utils/ptz.ts @@ -1,5 +1,5 @@ import { CameraManager } from '../camera-manager/manager'; -import { PTZAction } from '../config/ptz'; +import { PTZAction } from '../config/schema/actions/custom/ptz'; import { IMAGE_VIEW_ZOOM_TARGET_SENTINEL } from '../const'; import { PTZCapabilities } from '../types'; import { View } from '../view/view'; diff --git a/src/utils/task.ts b/src/utils/task.ts index 22502712..e6b39fc2 100644 --- a/src/utils/task.ts +++ b/src/utils/task.ts @@ -1,7 +1,7 @@ import { Task } from '@lit-labs/task'; import { html, TemplateResult } from 'lit'; import { renderProgressIndicator } from '../components/progress-indicator'; -import { CardWideConfig } from '../config/types'; +import { CardWideConfig } from '../config/schema/types'; import { errorToConsole } from './basic'; /** diff --git a/src/view/view-to-cameras.ts b/src/view/view-to-cameras.ts index 6d65c471..aacbd957 100644 --- a/src/view/view-to-cameras.ts +++ b/src/view/view-to-cameras.ts @@ -1,6 +1,6 @@ import { CameraManager } from '../camera-manager/manager'; import { CapabilitySearchOptions } from '../camera-manager/types'; -import { AdvancedCameraCardView } from '../config/types'; +import { AdvancedCameraCardView } from '../config/schema/common/const'; /** * Get cameraIDs that are relevant for a given view name based on camera diff --git a/src/view/view.ts b/src/view/view.ts index ef68fb60..9064474b 100644 --- a/src/view/view.ts +++ b/src/view/view.ts @@ -1,6 +1,7 @@ import merge from 'lodash-es/merge'; import { ViewContext } from 'view'; -import { AdvancedCameraCardView, ViewDisplayMode } from '../config/types.js'; +import { AdvancedCameraCardView } from '../config/schema/common/const'; +import { ViewDisplayMode } from '../config/schema/common/display'; import { MediaQueries } from './media-queries'; import { MediaQueriesResults } from './media-queries-results'; diff --git a/tests/camera-manager/camera.test.ts b/tests/camera-manager/camera.test.ts index 488d395d..84473c99 100644 --- a/tests/camera-manager/camera.test.ts +++ b/tests/camera-manager/camera.test.ts @@ -4,7 +4,7 @@ import { Camera } from '../../src/camera-manager/camera.js'; import { GenericCameraManagerEngine } from '../../src/camera-manager/generic/engine-generic.js'; import { CameraProxyConfig } from '../../src/camera-manager/types.js'; import { StateWatcherSubscriptionInterface } from '../../src/card-controller/hass/state-watcher.js'; -import { ProxyConfig } from '../../src/config/types.js'; +import { ProxyConfig } from '../../src/config/schema/cameras.js'; import { callStateWatcherCallback, createCameraConfig, diff --git a/tests/camera-manager/engine-factory.test.ts b/tests/camera-manager/engine-factory.test.ts index 7fc114f2..d5cf4051 100644 --- a/tests/camera-manager/engine-factory.test.ts +++ b/tests/camera-manager/engine-factory.test.ts @@ -4,9 +4,10 @@ import { CameraManagerEngineFactory } from '../../src/camera-manager/engine-fact import { FrigateCameraManagerEngine } from '../../src/camera-manager/frigate/engine-frigate'; import { GenericCameraManagerEngine } from '../../src/camera-manager/generic/engine-generic'; import { MotionEyeCameraManagerEngine } from '../../src/camera-manager/motioneye/engine-motioneye'; +import { ReolinkCameraManagerEngine } from '../../src/camera-manager/reolink/engine-reolink.js'; import { Engine } from '../../src/camera-manager/types.js'; import { StateWatcherSubscriptionInterface } from '../../src/card-controller/hass/state-watcher.js'; -import { CardWideConfig } from '../../src/config/types.js'; +import { CardWideConfig } from '../../src/config/schema/types.js'; import { createEntityRegistryCache, EntityRegistryManager, @@ -18,7 +19,6 @@ import { createRegistryEntity, createStateEntity, } from '../test-utils'; -import { ReolinkCameraManagerEngine } from '../../src/camera-manager/reolink/engine-reolink.js'; vi.mock('../../src/utils/ha/entity-registry'); vi.mock('../../src/utils/ha/entity-registry/cache'); diff --git a/tests/camera-manager/frigate/camera.test.ts b/tests/camera-manager/frigate/camera.test.ts index 90c8c410..935c40cc 100644 --- a/tests/camera-manager/frigate/camera.test.ts +++ b/tests/camera-manager/frigate/camera.test.ts @@ -6,7 +6,7 @@ import { FrigateEventWatcher } from '../../../src/camera-manager/frigate/event-w import { getPTZInfo } from '../../../src/camera-manager/frigate/requests'; import { FrigateEventChange } from '../../../src/camera-manager/frigate/types'; import { StateWatcher } from '../../../src/card-controller/hass/state-watcher'; -import { CameraTriggerEventType } from '../../../src/config/types'; +import { CameraTriggerEventType } from '../../../src/config/schema/cameras'; import { EntityRegistryManager } from '../../../src/utils/ha/registry/entity'; import { Entity } from '../../../src/utils/ha/registry/entity/types'; import { createCameraConfig, createHASS, createRegistryEntity } from '../../test-utils'; diff --git a/tests/camera-manager/frigate/engine-frigate.test.ts b/tests/camera-manager/frigate/engine-frigate.test.ts index 190886c3..7cf0ea85 100644 --- a/tests/camera-manager/frigate/engine-frigate.test.ts +++ b/tests/camera-manager/frigate/engine-frigate.test.ts @@ -7,12 +7,10 @@ import { } from '../../../src/camera-manager/frigate/media'; import { FrigateEvent, eventSchema } from '../../../src/camera-manager/frigate/types.js'; import { StateWatcher } from '../../../src/card-controller/hass/state-watcher'; -import { PTZAction } from '../../../src/config/ptz'; -import { - AdvancedCameraCardView, - CameraConfig, - RawAdvancedCameraCardConfig, -} from '../../../src/config/types'; +import { PTZAction } from '../../../src/config/schema/actions/custom/ptz'; +import { CameraConfig } from '../../../src/config/schema/cameras'; +import { AdvancedCameraCardView } from '../../../src/config/schema/common/const'; +import { RawAdvancedCameraCardConfig } from '../../../src/config/types'; import { EntityRegistryManager, createEntityRegistryCache, diff --git a/tests/camera-manager/frigate/util.test.ts b/tests/camera-manager/frigate/util.test.ts index 9b99fea4..3ba42f78 100644 --- a/tests/camera-manager/frigate/util.test.ts +++ b/tests/camera-manager/frigate/util.test.ts @@ -9,7 +9,7 @@ import { getRecordingMediaContentID, getRecordingTitle, } from '../../../src/camera-manager/frigate/util'; -import { CameraConfig } from '../../../src/config/types'; +import { CameraConfig } from '../../../src/config/schema/cameras'; import { createCameraConfig, createFrigateEvent, diff --git a/tests/camera-manager/generic/engine-generic.test.ts b/tests/camera-manager/generic/engine-generic.test.ts index 212d4594..bf015bb5 100644 --- a/tests/camera-manager/generic/engine-generic.test.ts +++ b/tests/camera-manager/generic/engine-generic.test.ts @@ -3,7 +3,8 @@ import { mock } from 'vitest-mock-extended'; import { GenericCameraManagerEngine } from '../../../src/camera-manager/generic/engine-generic'; import { Engine, QueryResultsType, QueryType } from '../../../src/camera-manager/types'; import { StateWatcherSubscriptionInterface } from '../../../src/card-controller/hass/state-watcher'; -import { CameraConfig, RawAdvancedCameraCardConfig } from '../../../src/config/types'; +import { CameraConfig } from '../../../src/config/schema/cameras'; +import { RawAdvancedCameraCardConfig } from '../../../src/config/types'; import { TestViewMedia, createCameraConfig, diff --git a/tests/camera-manager/manager.test.ts b/tests/camera-manager/manager.test.ts index 1db3c1ae..37860d72 100644 --- a/tests/camera-manager/manager.test.ts +++ b/tests/camera-manager/manager.test.ts @@ -27,7 +27,7 @@ import { } from '../../src/camera-manager/types.js'; import { sortMedia } from '../../src/camera-manager/utils/sort-media.js'; import { CardController } from '../../src/card-controller/controller.js'; -import { CameraConfig } from '../../src/config/types.js'; +import { CameraConfig } from '../../src/config/schema/cameras.js'; import { HomeAssistant } from '../../src/ha/types.js'; import { ViewMedia } from '../../src/view/media.js'; import { diff --git a/tests/camera-manager/reolink/camera.test.ts b/tests/camera-manager/reolink/camera.test.ts index d737eb3d..2dd662d7 100644 --- a/tests/camera-manager/reolink/camera.test.ts +++ b/tests/camera-manager/reolink/camera.test.ts @@ -4,7 +4,7 @@ import { CameraManagerEngine } from '../../../src/camera-manager/engine'; import { ReolinkCamera } from '../../../src/camera-manager/reolink/camera'; import { CameraProxyConfig } from '../../../src/camera-manager/types'; import { StateWatcher } from '../../../src/card-controller/hass/state-watcher'; -import { ProxyConfig } from '../../../src/config/types'; +import { ProxyConfig } from '../../../src/config/schema/cameras'; import { EntityRegistryManager } from '../../../src/utils/ha/registry/entity'; import { createCameraConfig, createHASS, createRegistryEntity } from '../../test-utils'; diff --git a/tests/camera-manager/utils/camera-entity-from-config.test.ts b/tests/camera-manager/utils/camera-entity-from-config.test.ts index ab2a63c3..0e49adb4 100644 --- a/tests/camera-manager/utils/camera-entity-from-config.test.ts +++ b/tests/camera-manager/utils/camera-entity-from-config.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; import { getCameraEntityFromConfig } from '../../../src/camera-manager/utils/camera-entity-from-config.js'; -import { CameraConfig, cameraConfigSchema } from '../../../src/config/types.js'; +import { CameraConfig, cameraConfigSchema } from '../../../src/config/schema/cameras.js'; describe('getCameraEntityFromConfig', () => { const createCameraConfig = (config: Partial): CameraConfig => { diff --git a/tests/card-controller/actions/actions-manager.test.ts b/tests/card-controller/actions/actions-manager.test.ts index e0688e44..3d227565 100644 --- a/tests/card-controller/actions/actions-manager.test.ts +++ b/tests/card-controller/actions/actions-manager.test.ts @@ -15,7 +15,7 @@ import { InteractionName, } from '../../../src/card-controller/actions/actions-manager'; import { TemplateRenderer } from '../../../src/card-controller/templates'; -import { AdvancedCameraCardView } from '../../../src/config/types'; +import { AdvancedCameraCardView } from '../../../src/config/schema/common/const'; import { createLogAction } from '../../../src/utils/action'; import { createCardAPI, createConfig, createHASS, createView } from '../../test-utils'; diff --git a/tests/card-controller/actions/actions/internal-callback.test.ts b/tests/card-controller/actions/actions/internal-callback.test.ts index 63547e6b..2ae1d29c 100644 --- a/tests/card-controller/actions/actions/internal-callback.test.ts +++ b/tests/card-controller/actions/actions/internal-callback.test.ts @@ -1,6 +1,6 @@ import { expect, it, vi } from 'vitest'; import { InternalCallbackAction } from '../../../../src/card-controller/actions/actions/internal-callback'; -import { INTERNAL_CALLBACK_ACTION } from '../../../../src/config/types'; +import { INTERNAL_CALLBACK_ACTION } from '../../../../src/config/schema/actions/custom/internal'; import { createCardAPI } from '../../../test-utils'; it('should handle internal callback action', async () => { diff --git a/tests/card-controller/actions/factory.test.ts b/tests/card-controller/actions/factory.test.ts index 098a6cc3..a7533ff2 100644 --- a/tests/card-controller/actions/factory.test.ts +++ b/tests/card-controller/actions/factory.test.ts @@ -38,7 +38,8 @@ import { UnmuteAction } from '../../../src/card-controller/actions/actions/unmut import { URLAction } from '../../../src/card-controller/actions/actions/url'; import { ViewAction } from '../../../src/card-controller/actions/actions/view'; import { ActionFactory } from '../../../src/card-controller/actions/factory'; -import { ActionConfig, INTERNAL_CALLBACK_ACTION } from '../../../src/config/types'; +import { INTERNAL_CALLBACK_ACTION } from '../../../src/config/schema/actions/custom/internal'; +import { ActionConfig } from '../../../src/config/schema/actions/types'; // @vitest-environment jsdom describe('ActionFactory', () => { diff --git a/tests/card-controller/automations-manager.test.ts b/tests/card-controller/automations-manager.test.ts index 36f043de..0b1cc964 100644 --- a/tests/card-controller/automations-manager.test.ts +++ b/tests/card-controller/automations-manager.test.ts @@ -1,7 +1,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; import { AutomationsManager } from '../../src/card-controller/automations-manager.js'; import { ConditionStateManager } from '../../src/conditions/state-manager.js'; -import { ActionConfig } from '../../src/config/types.js'; +import { ActionConfig } from '../../src/config/schema/actions/types.js'; import { createCardAPI } from '../test-utils.js'; describe('AutomationsManager', () => { diff --git a/tests/card-controller/config/config-manager.test.ts b/tests/card-controller/config/config-manager.test.ts index f6dacdf8..d75033d2 100644 --- a/tests/card-controller/config/config-manager.test.ts +++ b/tests/card-controller/config/config-manager.test.ts @@ -3,7 +3,7 @@ import { ZodError } from 'zod'; import { ConfigManager } from '../../../src/card-controller/config/config-manager'; import { InitializationAspect } from '../../../src/card-controller/initialization-manager'; import { ConditionStateManager } from '../../../src/conditions/state-manager'; -import { advancedCameraCardConfigSchema } from '../../../src/config/types'; +import { advancedCameraCardConfigSchema } from '../../../src/config/schema/types'; import { createCardAPI, flushPromises } from '../../test-utils'; describe('ConfigManager', () => { diff --git a/tests/card-controller/config/load-control-entities.test.ts b/tests/card-controller/config/load-control-entities.test.ts index 75797ac7..f18b9e0e 100644 --- a/tests/card-controller/config/load-control-entities.test.ts +++ b/tests/card-controller/config/load-control-entities.test.ts @@ -1,7 +1,8 @@ -import { describe, expect, it, vi } from 'vitest'; +import { assert, describe, expect, it, vi } from 'vitest'; import { setRemoteControlEntityFromConfig } from '../../../src/card-controller/config/load-control-entities'; +import { INTERNAL_CALLBACK_ACTION } from '../../../src/config/schema/actions/custom/internal'; +import { isAdvancedCameraCardCustomAction } from '../../../src/utils/action'; import { createCardAPI, createConfig, createHASS, createStore } from '../../test-utils'; -import { INTERNAL_CALLBACK_ACTION } from '../../../src/config/types'; describe('setRemoteControlEntityFromConfig', () => { it('without control entity', () => { @@ -119,8 +120,9 @@ describe('setRemoteControlEntityFromConfig', () => { setRemoteControlEntityFromConfig(api); const addOptionsAction = vi.mocked(api.getAutomationsManager().addAutomations).mock - .calls[0][0][0].actions[0]; - expect(addOptionsAction.advanced_camera_card_action).toBe(INTERNAL_CALLBACK_ACTION); + .calls[0][0][0].actions?.[0]; + assert(addOptionsAction && isAdvancedCameraCardCustomAction(addOptionsAction)); + assert(addOptionsAction.advanced_camera_card_action === INTERNAL_CALLBACK_ACTION); addOptionsAction.callback(api); expect(hass.callService).toBeCalledWith( diff --git a/tests/card-controller/status-bar-item-manager.test.ts b/tests/card-controller/status-bar-item-manager.test.ts index 7ccc9769..5707bfdd 100644 --- a/tests/card-controller/status-bar-item-manager.test.ts +++ b/tests/card-controller/status-bar-item-manager.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from 'vitest'; import { StatusBarItemManager } from '../../src/card-controller/status-bar-item-manager'; -import { StatusBarString } from '../../src/config/types'; +import { StatusBarString } from '../../src/config/schema/actions/types'; import { MediaQueriesResults } from '../../src/view/media-queries-results'; import { TestViewMedia, @@ -21,8 +21,7 @@ describe('StatusBarItemManager', () => { manager.addDynamicStatusBarItem(testItem); manager.addDynamicStatusBarItem(testItem); - expect(manager.calculateItems()).toContain(testItem); - expect(manager.calculateItems().find((item) => item === testItem).length === 1); + expect(manager.calculateItems()).toEqual([testItem]); }); it('should remove', () => { diff --git a/tests/card-controller/style-manager.test.ts b/tests/card-controller/style-manager.test.ts index 2b6ac8b2..76414651 100644 --- a/tests/card-controller/style-manager.test.ts +++ b/tests/card-controller/style-manager.test.ts @@ -1,6 +1,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { StyleManager } from '../../src/card-controller/style-manager'; -import { AdvancedCameraCardView, ThemeName } from '../../src/config/types'; +import { AdvancedCameraCardView } from '../../src/config/schema/common/const'; +import { ThemeName } from '../../src/config/schema/view'; import { createCardAPI, createConfig, createView } from '../test-utils'; // @vitest-environment jsdom diff --git a/tests/card-controller/triggers-manager.test.ts b/tests/card-controller/triggers-manager.test.ts index 147798dd..3f773435 100644 --- a/tests/card-controller/triggers-manager.test.ts +++ b/tests/card-controller/triggers-manager.test.ts @@ -2,11 +2,8 @@ import { add } from 'date-fns'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { CardController } from '../../src/card-controller/controller'; import { TriggersManager } from '../../src/card-controller/triggers-manager'; -import { - AdvancedCameraCardView, - TriggersOptions, - triggersSchema, -} from '../../src/config/types'; +import { AdvancedCameraCardView } from '../../src/config/schema/common/const'; +import { TriggersOptions, triggersSchema } from '../../src/config/schema/view'; import { createCameraConfig, createCameraManager, diff --git a/tests/card-controller/view/factory.test.ts b/tests/card-controller/view/factory.test.ts index 894dc00e..1b120eea 100644 --- a/tests/card-controller/view/factory.test.ts +++ b/tests/card-controller/view/factory.test.ts @@ -1,7 +1,8 @@ import { describe, expect, it, vi } from 'vitest'; import { ViewFactory } from '../../../src/card-controller/view/factory'; import { ViewModifier } from '../../../src/card-controller/view/types'; -import { AdvancedCameraCardView, ViewDisplayMode } from '../../../src/config/types'; +import { AdvancedCameraCardView } from '../../../src/config/schema/common/const'; +import { ViewDisplayMode } from '../../../src/config/schema/common/display'; import { View } from '../../../src/view/view'; import { createCameraManager, diff --git a/tests/card-controller/view/view-manager.test.ts b/tests/card-controller/view/view-manager.test.ts index d5c30737..3de021f4 100644 --- a/tests/card-controller/view/view-manager.test.ts +++ b/tests/card-controller/view/view-manager.test.ts @@ -10,7 +10,7 @@ import { } from '../../../src/card-controller/view/types'; import { ViewManager } from '../../../src/card-controller/view/view-manager'; import { ViewQueryExecutor } from '../../../src/card-controller/view/view-query-executor'; -import { AdvancedCameraCardView } from '../../../src/config/types'; +import { AdvancedCameraCardView } from '../../../src/config/schema/common/const'; import { ViewMedia } from '../../../src/view/media'; import { EventMediaQueries } from '../../../src/view/media-queries'; import { MediaQueriesResults } from '../../../src/view/media-queries-results'; diff --git a/tests/card-controller/view/view-query-executor.test.ts b/tests/card-controller/view/view-query-executor.test.ts index 6936ce9e..f7f6bbd7 100644 --- a/tests/card-controller/view/view-query-executor.test.ts +++ b/tests/card-controller/view/view-query-executor.test.ts @@ -3,7 +3,7 @@ import { mock } from 'vitest-mock-extended'; import { applyViewModifiers } from '../../../src/card-controller/view/modifiers'; import { QueryExecutor } from '../../../src/card-controller/view/query-executor'; import { ViewQueryExecutor } from '../../../src/card-controller/view/view-query-executor'; -import { AdvancedCameraCardView } from '../../../src/config/types'; +import { AdvancedCameraCardView } from '../../../src/config/schema/common/const'; import { EventMediaQueries, RecordingMediaQueries, diff --git a/tests/components-lib/menu-button-controller.test.ts b/tests/components-lib/menu-button-controller.test.ts index 0b8a3b8e..8f5db5c4 100644 --- a/tests/components-lib/menu-button-controller.test.ts +++ b/tests/components-lib/menu-button-controller.test.ts @@ -12,12 +12,10 @@ import { MenuButtonController, MenuButtonControllerOptions, } from '../../src/components-lib/menu-button-controller.js'; -import { - AdvancedCameraCardConfig, - AdvancedCameraCardView, - MenuItem, - ViewDisplayMode, -} from '../../src/config/types.js'; +import { AdvancedCameraCardView } from '../../src/config/schema/common/const.js'; +import { ViewDisplayMode } from '../../src/config/schema/common/display.js'; +import { MenuItem } from '../../src/config/schema/elements/custom/menu/types.js'; +import { AdvancedCameraCardConfig } from '../../src/config/schema/types.js'; import { HomeAssistant } from '../../src/ha/types.js'; import { MediaPlayerController } from '../../src/types.js'; import { createGeneralAction, createViewAction } from '../../src/utils/action.js'; diff --git a/tests/components-lib/menu-controller.test.ts b/tests/components-lib/menu-controller.test.ts index d1349c4e..51584f4a 100644 --- a/tests/components-lib/menu-controller.test.ts +++ b/tests/components-lib/menu-controller.test.ts @@ -1,7 +1,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { MenuController } from '../../src/components-lib/menu-controller.js'; import { SubmenuItem } from '../../src/components/submenu/types.js'; -import { MenuConfig, menuConfigSchema } from '../../src/config/types.js'; +import { MenuConfig, menuConfigSchema } from '../../src/config/schema/menu.js'; import { createInteractionActionEvent, createLitElement, diff --git a/tests/components-lib/ptz/ptz-controller.test.ts b/tests/components-lib/ptz/ptz-controller.test.ts index 87573982..240f84a1 100644 --- a/tests/components-lib/ptz/ptz-controller.test.ts +++ b/tests/components-lib/ptz/ptz-controller.test.ts @@ -1,8 +1,11 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { Capabilities } from '../../../src/camera-manager/capabilities'; import { PTZController } from '../../../src/components-lib/ptz/ptz-controller'; -import { PTZControlAction } from '../../../src/config/ptz'; -import { PTZControlsConfig, ptzControlsConfigSchema } from '../../../src/config/types'; +import { PTZControlAction } from '../../../src/config/schema/actions/custom/ptz'; +import { + PTZControlsConfig, + ptzControlsConfigSchema, +} from '../../../src/config/schema/common/controls/ptz'; import { createCameraManager, createCapabilities, createStore } from '../../test-utils'; const createConfig = (config?: Partial): PTZControlsConfig => { diff --git a/tests/components-lib/status-bar-controller.test.ts b/tests/components-lib/status-bar-controller.test.ts index 4b898671..a10dd7d5 100644 --- a/tests/components-lib/status-bar-controller.test.ts +++ b/tests/components-lib/status-bar-controller.test.ts @@ -1,6 +1,9 @@ import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; import { StatusBarController } from '../../src/components-lib/status-bar-controller'; -import { StatusBarConfig, statusBarConfigSchema } from '../../src/config/types'; +import { + StatusBarConfig, + statusBarConfigSchema, +} from '../../src/config/schema/status-bar'; import { setOrRemoveAttribute } from '../../src/utils/basic'; import { createInteractionActionEvent, createLitElement } from '../test-utils'; @@ -79,7 +82,7 @@ describe('StatusBarController', () => { const controller = new StatusBarController(host); const items = [ { - type: 'custom:advanced-camera-card-status-bar-string', + type: 'custom:advanced-camera-card-status-bar-string' as const, string: 'Test', }, ]; @@ -92,22 +95,22 @@ describe('StatusBarController', () => { const host = createLitElement(); const controller = new StatusBarController(host); const item1 = { - type: 'custom:advanced-camera-card-status-bar-string', + type: 'custom:advanced-camera-card-status-bar-string' as const, string: 'Item 1', priority: 40, }; const item2 = { - type: 'custom:advanced-camera-card-status-bar-string', + type: 'custom:advanced-camera-card-status-bar-string' as const, string: 'Item 2', priority: 10, }; const item3 = { - type: 'custom:advanced-camera-card-status-bar-string', + type: 'custom:advanced-camera-card-status-bar-string' as const, string: 'Item 3', priority: 60, }; const item4 = { - type: 'custom:advanced-camera-card-status-bar-string', + type: 'custom:advanced-camera-card-status-bar-string' as const, string: 'Item 4', priority: undefined, }; @@ -120,12 +123,12 @@ describe('StatusBarController', () => { const host = createLitElement(); const controller = new StatusBarController(host); const item1 = { - type: 'custom:advanced-camera-card-status-bar-string', + type: 'custom:advanced-camera-card-status-bar-string' as const, string: 'Item 1', priority: 100, }; const exclusiveItem = { - type: 'custom:advanced-camera-card-status-bar-string', + type: 'custom:advanced-camera-card-status-bar-string' as const, string: 'Item 2', priority: 1, exclusive: true, @@ -140,12 +143,12 @@ describe('StatusBarController', () => { const host = createLitElement(); const controller = new StatusBarController(host); const insufficientItem = { - type: 'custom:advanced-camera-card-status-bar-string', + type: 'custom:advanced-camera-card-status-bar-string' as const, string: 'Item 1', sufficient: false, }; const sufficientItem = { - type: 'custom:advanced-camera-card-status-bar-string', + type: 'custom:advanced-camera-card-status-bar-string' as const, string: 'Item 2', sufficient: true, }; @@ -159,12 +162,12 @@ describe('StatusBarController', () => { const host = createLitElement(); const controller = new StatusBarController(host); const insufficientItem = { - type: 'custom:advanced-camera-card-status-bar-string', + type: 'custom:advanced-camera-card-status-bar-string' as const, string: 'Item 1', sufficient: false, }; const sufficientItem = { - type: 'custom:advanced-camera-card-status-bar-string', + type: 'custom:advanced-camera-card-status-bar-string' as const, string: 'Item 2', sufficient: false, }; @@ -188,7 +191,7 @@ describe('StatusBarController', () => { ); const sufficientItem = { - type: 'custom:advanced-camera-card-status-bar-string', + type: 'custom:advanced-camera-card-status-bar-string' as const, string: 'Item 1', priority: 100, sufficient: true, @@ -210,7 +213,7 @@ describe('StatusBarController', () => { ); const insufficientItem = { - type: 'custom:advanced-camera-card-status-bar-string', + type: 'custom:advanced-camera-card-status-bar-string' as const, string: 'Item 1', priority: 100, sufficient: false, @@ -231,19 +234,19 @@ describe('StatusBarController', () => { ); const sufficientString = { - type: 'custom:advanced-camera-card-status-bar-string', + type: 'custom:advanced-camera-card-status-bar-string' as const, string: 'String', priority: 100, sufficient: true, }; const sufficientIcon = { - type: 'custom:advanced-camera-card-status-bar-icon', + type: 'custom:advanced-camera-card-status-bar-icon' as const, icon: 'Icon', priority: 100, sufficient: true, }; const sufficientImage = { - type: 'custom:advanced-camera-card-status-bar-image', + type: 'custom:advanced-camera-card-status-bar-image' as const, image: 'Image', priority: 100, sufficient: true, @@ -275,7 +278,7 @@ describe('StatusBarController', () => { ); const sufficientItem = { - type: 'custom:advanced-camera-card-status-bar-string', + type: 'custom:advanced-camera-card-status-bar-string' as const, string: 'Item 1', priority: 100, sufficient: true, diff --git a/tests/config/management.test.ts b/tests/config/management.test.ts index 51eab9cf..143a8e95 100644 --- a/tests/config/management.test.ts +++ b/tests/config/management.test.ts @@ -17,16 +17,12 @@ import { upgradeObjectRecursively, upgradeWithOverrides, } from '../../src/config/management.js'; -import { PTZControlAction } from '../../src/config/ptz.js'; -import { - Actions, - RawAdvancedCameraCardConfig, - advancedCameraCardConfigSchema, -} from '../../src/config/types.js'; -import { - CallServiceActionConfig, - PerformActionActionConfig, -} from '../../src/ha/types.js'; +import { PTZControlAction } from '../../src/config/schema/actions/custom/ptz.js'; +import { CallServiceActionConfig } from '../../src/config/schema/actions/stock/call-service.js'; +import { PerformActionActionConfig } from '../../src/config/schema/actions/stock/perform-action.js'; +import { Actions } from '../../src/config/schema/actions/types.js'; +import { advancedCameraCardConfigSchema } from '../../src/config/schema/types.js'; +import { RawAdvancedCameraCardConfig } from '../../src/config/types.js'; import { getParseErrorPaths } from '../../src/utils/zod.js'; describe('general functions', () => { diff --git a/tests/config/profiles/casting.test.ts b/tests/config/profiles/casting.test.ts index aabb8a5b..de21e06b 100644 --- a/tests/config/profiles/casting.test.ts +++ b/tests/config/profiles/casting.test.ts @@ -1,7 +1,7 @@ import { expect, it } from 'vitest'; -import { setProfiles } from '../../../src/config/profiles'; import { CASTING_PROFILE } from '../../../src/config/profiles/casting'; -import { advancedCameraCardConfigSchema } from '../../../src/config/types'; +import { setProfiles } from '../../../src/config/profiles/set-profiles'; +import { advancedCameraCardConfigSchema } from '../../../src/config/schema/types'; import { createRawConfig } from '../../test-utils'; it('should contain expected defaults', () => { diff --git a/tests/config/profiles/index.test.ts b/tests/config/profiles/index.test.ts index 230a50ed..da0067e4 100644 --- a/tests/config/profiles/index.test.ts +++ b/tests/config/profiles/index.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { ProfileType } from '../../../src/config/types'; -import { setProfiles } from '../../../src/config/profiles'; +import { setProfiles } from '../../../src/config/profiles/set-profiles'; +import { ProfileType } from '../../../src/config/schema/profiles'; import { createConfig } from '../../test-utils'; describe('setProfiles', () => { diff --git a/tests/config/profiles/low-performance.test.ts b/tests/config/profiles/low-performance.test.ts index 3056f47d..a12efd22 100644 --- a/tests/config/profiles/low-performance.test.ts +++ b/tests/config/profiles/low-performance.test.ts @@ -1,7 +1,7 @@ import { expect, it } from 'vitest'; -import { setProfiles } from '../../../src/config/profiles'; import { LOW_PERFORMANCE_PROFILE } from '../../../src/config/profiles/low-performance'; -import { advancedCameraCardConfigSchema } from '../../../src/config/types'; +import { setProfiles } from '../../../src/config/profiles/set-profiles'; +import { advancedCameraCardConfigSchema } from '../../../src/config/schema/types'; import { createRawConfig } from '../../test-utils'; it('should contain expected defaults', () => { diff --git a/tests/config/profiles/scrubbing.test.ts b/tests/config/profiles/scrubbing.test.ts index e9939e48..6df435f4 100644 --- a/tests/config/profiles/scrubbing.test.ts +++ b/tests/config/profiles/scrubbing.test.ts @@ -1,7 +1,7 @@ import { expect, it } from 'vitest'; -import { setProfiles } from '../../../src/config/profiles'; import { SCRUBBING_PROFILE } from '../../../src/config/profiles/scrubbing'; -import { advancedCameraCardConfigSchema } from '../../../src/config/types'; +import { setProfiles } from '../../../src/config/profiles/set-profiles'; +import { advancedCameraCardConfigSchema } from '../../../src/config/schema/types'; import { createRawConfig } from '../../test-utils'; it('should contain expected defaults', () => { diff --git a/tests/config/types.test.ts b/tests/config/types.test.ts index a29d0ed6..726fcb7c 100644 --- a/tests/config/types.test.ts +++ b/tests/config/types.test.ts @@ -1,13 +1,11 @@ import { describe, expect, it } from 'vitest'; -import { - advancedCameraCardConditionSchema, - advancedCameraCardCustomActionsBaseSchema, - cameraConfigSchema, - conditionalSchema, - customSchema, - dimensionsConfigSchema, - statusBarActionConfigSchema, -} from '../../src/config/types'; +import { advancedCameraCardCustomActionsBaseSchema } from '../../src/config/schema/actions/custom/base'; +import { statusBarActionConfigSchema } from '../../src/config/schema/actions/types'; +import { cameraConfigSchema } from '../../src/config/schema/cameras'; +import { advancedCameraCardConditionSchema } from '../../src/config/schema/conditions/types'; +import { dimensionsConfigSchema } from '../../src/config/schema/dimensions'; +import { customSchema } from '../../src/config/schema/elements/stock/custom'; +import { conditionalSchema } from '../../src/config/schema/elements/types'; import { createConfig } from '../test-utils'; // @vitest-environment jsdom @@ -741,22 +739,24 @@ describe('config defaults', () => { { condition: 'view', views: ['live'] }, ]; - expect( - createConfig({ + const elements = [ + { + type: 'custom:advanced-camera-card-conditional', elements: [ { - type: 'custom:advanced-camera-card-conditional', - elements: [ - { - type: 'icon', - icon: 'mdi:pig', - }, - ], - conditions: conditions, + type: 'icon', + icon: 'mdi:pig', }, ], - }).elements[0].conditions, - ).toEqual(conditions); + conditions: conditions, + }, + ]; + + expect( + createConfig({ + elements: elements, + }).elements, + ).toEqual(elements); }); it('should include all stock actions', () => { diff --git a/tests/test-utils.ts b/tests/test-utils.ts index 86e7d67e..439a7b8d 100644 --- a/tests/test-utils.ts +++ b/tests/test-utils.ts @@ -40,15 +40,16 @@ import { TriggersManager } from '../src/card-controller/triggers-manager'; import { ViewManager } from '../src/card-controller/view/view-manager'; import { SubmenuInteraction, SubmenuItem } from '../src/components/submenu/types'; import { ConditionStateManager } from '../src/conditions/state-manager'; +import { CameraConfig, cameraConfigSchema } from '../src/config/schema/cameras'; +import { + PerformanceConfig, + performanceConfigSchema, +} from '../src/config/schema/performance'; import { AdvancedCameraCardConfig, - CameraConfig, - PerformanceConfig, - RawAdvancedCameraCardConfig, advancedCameraCardConfigSchema, - cameraConfigSchema, - performanceConfigSchema, -} from '../src/config/types'; +} from '../src/config/schema/types'; +import { RawAdvancedCameraCardConfig } from '../src/config/types'; import { CurrentUser, HomeAssistant } from '../src/ha/types'; import { CapabilitiesRaw, Interaction, MediaLoadedInfo } from '../src/types'; import { HassStateDifference } from '../src/utils/ha'; diff --git a/tests/utils/action.test.ts b/tests/utils/action.test.ts index fcfb4c54..4b234dd0 100644 --- a/tests/utils/action.test.ts +++ b/tests/utils/action.test.ts @@ -1,6 +1,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; import { mock } from 'vitest-mock-extended'; -import { ActionConfig, INTERNAL_CALLBACK_ACTION } from '../../src/config/types.js'; +import { INTERNAL_CALLBACK_ACTION } from '../../src/config/schema/actions/custom/internal.js'; +import { ActionConfig } from '../../src/config/schema/actions/types.js'; import { createCameraAction, createDisplayModeAction, diff --git a/tests/utils/embla/plugins/auto-lazy-load/auto-lazy-load.test.ts b/tests/utils/embla/plugins/auto-lazy-load/auto-lazy-load.test.ts index 3282dc57..2a0225eb 100644 --- a/tests/utils/embla/plugins/auto-lazy-load/auto-lazy-load.test.ts +++ b/tests/utils/embla/plugins/auto-lazy-load/auto-lazy-load.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it, vi } from 'vitest'; -import { MEDIA_ACTION_NEGATIVE_CONDITIONS } from '../../../../../src/config/types'; +import { MEDIA_ACTION_NEGATIVE_CONDITIONS } from '../../../../../src/config/schema/common/media-actions'; import { AutoLazyLoad } from '../../../../../src/utils/embla/plugins/auto-lazy-load/auto-lazy-load'; import { callEmblaHandler, diff --git a/tests/utils/ptz.test.ts b/tests/utils/ptz.test.ts index 929f44bd..616e862f 100644 --- a/tests/utils/ptz.test.ts +++ b/tests/utils/ptz.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; import { Capabilities } from '../../src/camera-manager/capabilities'; -import { AdvancedCameraCardView } from '../../src/config/types'; +import { AdvancedCameraCardView } from '../../src/config/schema/common/const'; import { IMAGE_VIEW_ZOOM_TARGET_SENTINEL } from '../../src/const'; import { getPTZTarget, diff --git a/tests/view/view-to-cameras.test.ts b/tests/view/view-to-cameras.test.ts index b3cee6ae..390741bb 100644 --- a/tests/view/view-to-cameras.test.ts +++ b/tests/view/view-to-cameras.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it, vi } from 'vitest'; -import { AdvancedCameraCardView } from '../../src/config/types'; +import { AdvancedCameraCardView } from '../../src/config/schema/common/const'; import { CapabilityKey } from '../../src/types'; import { getCameraIDsForViewName } from '../../src/view/view-to-cameras'; import {