From 6b30487229842728a5e0d99e56c138f04742a49a Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Thu, 12 May 2022 20:42:02 -0700 Subject: [PATCH] Initial common.js refactor. --- src/action-handler-directive.ts | 15 +- src/card.ts | 126 ++- src/common.ts | 723 ------------------ src/components/carousel.ts | 13 +- src/components/elements.ts | 29 +- src/components/gallery.ts | 25 +- src/components/image.ts | 27 +- src/components/live.ts | 86 +-- src/components/media-carousel.ts | 21 +- src/components/menu.ts | 24 +- src/components/message.ts | 67 +- src/components/submenu.ts | 22 +- src/components/surround-thumbnails.ts | 24 +- src/components/thumbnail-carousel.ts | 24 +- src/components/thumbnail.ts | 30 +- src/components/timeline.ts | 48 +- src/components/viewer.ts | 41 +- src/editor.ts | 61 +- src/icons/alarm-panel-icon.ts | 42 - src/icons/binary-sensor-icon.ts | 58 -- src/icons/cover-icon.ts | 113 --- src/icons/domain-icon.ts | 166 ---- src/icons/sensor-icon.ts | 112 --- src/icons/state-icon.ts | 18 - src/icons/update.ts | 32 - src/patches/ha-camera-stream.ts | 5 +- src/patches/ha-hls-player.ts | 6 +- src/patches/ha-web-rtc-player.ts | 6 +- src/types.ts | 9 +- src/utils/action.ts | 185 +++++ src/utils/basic.ts | 62 ++ src/utils/camera.ts | 66 ++ src/utils/frigate.ts | 33 + .../ha/browse-media.ts} | 29 +- src/utils/ha/index.ts | 256 +++++++ src/utils/ha/update.ts | 36 + src/{ => utils}/icons/LICENSE | 0 src/{ => utils}/icons/README.md | 0 src/utils/icons/alarm-panel-icon.ts | 42 + src/utils/icons/binary-sensor-icon.ts | 58 ++ src/utils/icons/cover-icon.ts | 113 +++ src/utils/icons/domain-icon.ts | 166 ++++ src/utils/icons/sensor-icon.ts | 118 +++ src/utils/icons/state-icon.ts | 18 + src/utils/media-info.ts | 75 ++ src/{ => utils}/resolved-media.ts | 19 +- src/{zod-util.ts => utils/zod.ts} | 10 + src/view.ts | 2 +- 48 files changed, 1604 insertions(+), 1657 deletions(-) delete mode 100644 src/common.ts delete mode 100644 src/icons/alarm-panel-icon.ts delete mode 100644 src/icons/binary-sensor-icon.ts delete mode 100644 src/icons/cover-icon.ts delete mode 100644 src/icons/domain-icon.ts delete mode 100644 src/icons/sensor-icon.ts delete mode 100644 src/icons/state-icon.ts delete mode 100644 src/icons/update.ts create mode 100644 src/utils/action.ts create mode 100644 src/utils/basic.ts create mode 100644 src/utils/camera.ts create mode 100644 src/utils/frigate.ts rename src/{browse-media-util.ts => utils/ha/browse-media.ts} (96%) create mode 100644 src/utils/ha/index.ts create mode 100644 src/utils/ha/update.ts rename src/{ => utils}/icons/LICENSE (100%) rename src/{ => utils}/icons/README.md (100%) create mode 100644 src/utils/icons/alarm-panel-icon.ts create mode 100644 src/utils/icons/binary-sensor-icon.ts create mode 100644 src/utils/icons/cover-icon.ts create mode 100644 src/utils/icons/domain-icon.ts create mode 100644 src/utils/icons/sensor-icon.ts create mode 100644 src/utils/icons/state-icon.ts create mode 100644 src/utils/media-info.ts rename src/{ => utils}/resolved-media.ts (83%) rename src/{zod-util.ts => utils/zod.ts} (80%) diff --git a/src/action-handler-directive.ts b/src/action-handler-directive.ts index 25fcd421..b381996d 100644 --- a/src/action-handler-directive.ts +++ b/src/action-handler-directive.ts @@ -1,17 +1,16 @@ +import { fireEvent } from 'custom-card-helpers'; +import type { + ActionHandlerDetail, + ActionHandlerOptions +} from 'custom-card-helpers/dist/types.d.js'; import { noChange } from 'lit'; import { AttributePart, directive, Directive, - DirectiveParameters, + DirectiveParameters } from 'lit/directive.js'; - -import type { - ActionHandlerDetail, - ActionHandlerOptions, -} from 'custom-card-helpers/dist/types.d.js'; -import { fireEvent } from 'custom-card-helpers'; -import { stopEventFromActivatingCardWideActions } from './common'; +import { stopEventFromActivatingCardWideActions } from './utils/action.js'; interface ActionHandler extends HTMLElement { holdTime: number; diff --git a/src/card.ts b/src/card.ts index 8d33002b..89646db0 100644 --- a/src/card.ts +++ b/src/card.ts @@ -1,98 +1,94 @@ +import { getLovelace, HomeAssistant, LovelaceCardEditor } from 'custom-card-helpers'; import { CSSResultGroup, + html, LitElement, PropertyValues, TemplateResult, - html, - unsafeCSS, + unsafeCSS } from 'lit'; -import { HomeAssistant, LovelaceCardEditor, getLovelace } from 'custom-card-helpers'; -import { StyleInfo, styleMap } from 'lit/directives/style-map.js'; import { customElement, property, state } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import { createRef, ref, Ref } from 'lit/directives/ref.js'; -import screenfull from 'screenfull'; -import { throttle } from 'lodash-es'; +import { StyleInfo, styleMap } from 'lit/directives/style-map.js'; import { until } from 'lit/directives/until.js'; +import { throttle } from 'lodash-es'; +import screenfull from 'screenfull'; import { z } from 'zod'; - +import { actionHandler } from './action-handler-directive.js'; import { - Actions, - ActionType, - CameraConfig, - FrigateCardView, - FrigateCardCustomAction, - FRIGATE_CARD_VIEWS_USER_SPECIFIED, - RawFrigateCardConfig, - entitySchema, - frigateCardConfigSchema, -} from './types.js'; + ConditionState, + conditionStateRequestHandler, + getOverriddenConfig, + getOverridesByKey +} from './card-condition.js'; +import './components/elements.js'; +import { FrigateCardElements } from './components/elements.js'; +import './components/gallery.js'; +import './components/image.js'; +import { FrigateCardImage } from './components/image.js'; +import './components/live.js'; +import './components/menu.js'; +import { FrigateCardMenu, FRIGATE_BUTTON_MENU_ICON } from './components/menu.js'; +import './components/message.js'; +import { renderMessage, renderProgressIndicator } from './components/message.js'; +import './components/thumbnail-carousel.js'; +import './components/timeline.js'; +import './components/viewer.js'; +import { isConfigUpgradeable } from './config-mgmt.js'; +import { + CAMERA_BIRDSEYE, + CARD_VERSION, + MEDIA_PLAYER_SUPPORT_BROWSE_MEDIA, + REPO_URL +} from './const.js'; +import './editor.js'; +import { localize } from './localize/localize.js'; +import './patches/ha-camera-stream.js'; +import './patches/ha-hls-player.js'; +import './patches/ha-web-rtc-player.ts'; +import cardStyle from './scss/card.scss'; import type { Entity, ExtendedHomeAssistant, FrigateCardConfig, MediaShowInfo, MenuButton, - Message, + Message } from './types.js'; - import { - CAMERA_BIRDSEYE, - CARD_VERSION, - MEDIA_PLAYER_SUPPORT_BROWSE_MEDIA, - REPO_URL, -} from './const.js'; -import { FrigateCardElements } from './components/elements.js'; -import { FrigateCardImage } from './components/image.js'; -import { FRIGATE_BUTTON_MENU_ICON, FrigateCardMenu } from './components/menu.js'; -import { View } from './view.js'; + Actions, + ActionType, + CameraConfig, + entitySchema, + frigateCardConfigSchema, + FrigateCardCustomAction, + FrigateCardView, + FRIGATE_CARD_VIEWS_USER_SPECIFIED, + RawFrigateCardConfig +} from './types.js'; import { - contentsChanged, convertActionToFrigateCardCustomAction, createFrigateCardCustomAction, frigateCardHandleAction, frigateCardHasAction, - getActionConfigGivenAction, - getCameraIcon, - getCameraID, - getCameraTitle, + getActionConfigGivenAction +} from './utils/action.js'; +import { contentsChanged } from './utils/basic.js'; +import { getCameraIcon, getCameraID, getCameraTitle } from './utils/camera.js'; +import { getEntityIcon, getEntityTitle, homeAssistantSignPath, homeAssistantWSRequest, - isValidMediaShowInfo, shouldUpdateBasedOnHass, - sideLoadHomeAssistantElements, -} from './common.js'; -import { localize } from './localize/localize.js'; -import { renderMessage, renderProgressIndicator } from './components/message.js'; - -import './editor.js'; -import './components/elements.js'; -import './components/gallery.js'; -import './components/image.js'; -import './components/live.js'; -import './components/menu.js'; -import './components/message.js'; -import './components/viewer.js'; -import './components/thumbnail-carousel.js'; -import './components/timeline.js'; -import './patches/ha-camera-stream.js'; -import './patches/ha-hls-player.js'; -import './patches/ha-web-rtc-player.ts'; - -import cardStyle from './scss/card.scss'; -import { ResolvedMediaCache } from './resolved-media.js'; -import { BrowseMediaUtil } from './browse-media-util.js'; -import { isConfigUpgradeable } from './config-mgmt.js'; -import { actionHandler } from './action-handler-directive.js'; -import { - ConditionState, - conditionStateRequestHandler, - getOverriddenConfig, - getOverridesByKey, -} from './card-condition.js'; -import { supportsFeature } from './icons/update.js'; + sideLoadHomeAssistantElements +} from './utils/ha'; +import { BrowseMediaUtil } from './utils/ha/browse-media.js'; +import { supportsFeature } from './utils/ha/update.js'; +import { isValidMediaShowInfo } from './utils/media-info.js'; +import { ResolvedMediaCache } from './utils/resolved-media.js'; +import { View } from './view.js'; /** A note on media callbacks: * diff --git a/src/common.ts b/src/common.ts deleted file mode 100644 index d3873370..00000000 --- a/src/common.ts +++ /dev/null @@ -1,723 +0,0 @@ -import { HassEntity, MessageBase } from 'home-assistant-js-websocket'; -import { - HomeAssistant, - computeStateDomain, - handleActionConfig, - hasAction, - ActionConfig, -} from 'custom-card-helpers'; -import { StyleInfo } from 'lit/directives/style-map.js'; -import { ZodSchema, z } from 'zod'; -import { - differenceInSeconds, - differenceInMinutes, - differenceInHours, - fromUnixTime, -} from 'date-fns'; -import { isEqual } from 'lodash-es'; -import { localize } from './localize/localize.js'; -import { - Actions, - ActionsConfig, - ActionType, - CameraConfig, - CardHelpers, - ExtendedHomeAssistant, - FrigateCardAction, - FrigateCardCustomAction, - frigateCardCustomActionSchema, - FrigateEvent, - MediaShowInfo, - Message, - RawFrigateCardConfig, - SignedPath, - signedPathSchema, - StateParameters, -} from './types.js'; -import { stateIcon } from './icons/state-icon.js'; - -const MEDIA_INFO_HEIGHT_CUTOFF = 50; -const MEDIA_INFO_WIDTH_CUTOFF = MEDIA_INFO_HEIGHT_CUTOFF; - -/** - * Get the keys that didn't parse from a ZodError. - * @param error The zoderror to extract the keys from. - * @returns An array of error keys. - */ -export function getParseErrorKeys(error: z.ZodError): string[] { - const errors = error.format(); - return Object.keys(errors).filter((v) => !v.startsWith('_')); -} - -/** - * Make a HomeAssistant websocket request. May throw. - * @param hass The HomeAssistant object to send the request with. - * @param schema The expected Zod schema of the response. - * @param request The request to make. - * @returns The parsed valid response or null on malformed. - */ -export async function homeAssistantWSRequest( - hass: HomeAssistant, - schema: ZodSchema, - request: MessageBase, -): Promise { - const response = await hass.callWS(request); - - if (!response) { - const error_message = `${localize('error.empty_response')}: ${JSON.stringify( - request, - )}`; - console.warn(error_message); - throw new Error(error_message); - } - const parseResult = schema.safeParse(response); - if (!parseResult.success) { - const keys = getParseErrorKeys(parseResult.error); - const error_message = - `${localize('error.invalid_response')}: ${JSON.stringify(request)}. ` + - localize('error.invalid_keys') + - `: '${keys}'`; - console.warn(error_message); - throw new Error(error_message); - } - return parseResult.data; -} - -/** - * Request that HA sign a path. May throw. - * @param hass The HomeAssistant object used to request the signature. - * @param path The path to sign. - * @param expires An optional number of seconds to sign the path for. - * @returns The signed URL, or null if the response was malformed. - */ -export async function homeAssistantSignPath( - hass: ExtendedHomeAssistant, - path: string, - expires?: number, -): Promise { - const request = { - type: 'auth/sign_path', - path: path, - expires: expires, - }; - const response = await homeAssistantWSRequest( - hass, - signedPathSchema, - request, - ); - if (!response) { - return null; - } - return hass.hassUrl(response.path); -} - -/** - * Dispatch a Frigate Card event. - * @param element The element to send the event. - * @param name The name of the Frigate card event to send. - * @param detail An optional detail object to attach. - */ -export function dispatchFrigateCardEvent( - target: EventTarget, - name: string, - detail?: T, -): void { - target.dispatchEvent( - new CustomEvent(`frigate-card:${name}`, { - bubbles: true, - composed: true, - detail: detail, - }), - ); -} - -/** - * Create a MediaShowInfo object. - * @param source An event or HTMLElement that should be used as a source. - * @returns A new MediaShowInfo object or null if one could not be created. - */ -export function createMediaShowInfo(source: Event | HTMLElement): MediaShowInfo | null { - let target: HTMLElement | EventTarget; - if (source instanceof Event) { - target = source.composedPath()[0]; - } else { - target = source; - } - - if (target instanceof HTMLImageElement) { - return { - width: (target as HTMLImageElement).naturalWidth, - height: (target as HTMLImageElement).naturalHeight, - }; - } else if (target instanceof HTMLVideoElement) { - return { - width: (target as HTMLVideoElement).videoWidth, - height: (target as HTMLVideoElement).videoHeight, - }; - } else if (target instanceof HTMLCanvasElement) { - return { - width: (target as HTMLCanvasElement).width, - height: (target as HTMLCanvasElement).height, - }; - } - return null; -} - -/** - * Dispatch a Frigate card media show event. - * @param element The element to send the event. - * @param source An event or HTMLElement that should be used as a source. - */ -export function dispatchMediaShowEvent( - element: HTMLElement, - source: Event | HTMLElement, -): void { - const mediaShowInfo = createMediaShowInfo(source); - if (mediaShowInfo) { - dispatchExistingMediaShowInfoAsEvent(element, mediaShowInfo); - } -} - -/** - * Dispatch a pre-existing MediaShowInfo object as an event. - * @param element The element to send the event. - * @param mediaShowInfo The MediaShowInfo object to send. - */ -export function dispatchExistingMediaShowInfoAsEvent( - element: HTMLElement, - mediaShowInfo: MediaShowInfo, -): void { - dispatchFrigateCardEvent(element, 'media-show', mediaShowInfo); -} - -/** - * Dispatch an event with a message to show to the user. - * @param element The element to send the event. - * @param message The message to show. - * @param icon An optional icon to attach to the message. - */ -export function dispatchMessageEvent( - element: HTMLElement, - message: string, - icon?: string, - context?: unknown, -): void { - dispatchFrigateCardEvent(element, 'message', { - message: message, - type: 'info', - icon: icon, - context: context, - }); -} - -/** - * Dispatch an event with an error message to show to the user. - * @param element The element to send the event. - * @param message The message to show. - */ -export function dispatchErrorMessageEvent( - element: HTMLElement, - message: string, - context?: unknown, -): void { - dispatchFrigateCardEvent(element, 'message', { - message: message, - type: 'error', - context: context, - }); -} - -/** - * Determine whether the card should be updated based on Home Assistant changes. - * @param newHass The new HA object. - * @param oldHass The old HA object. - * @param entities The entities to examine for changes. - * @returns A boolean indicating whether or not to allow an update. - */ -export function shouldUpdateBasedOnHass( - newHass: HomeAssistant | undefined | null, - oldHass: HomeAssistant | undefined | null, - entities: string[] | null, -): boolean { - if (!newHass || !entities || !entities.length) { - return false; - } - if (!oldHass) { - return true; - } - - for (let i = 0; i < entities.length; i++) { - const entity = entities[i]; - if (entity && oldHass.states[entity] !== newHass.states[entity]) { - return true; - } - } - return false; -} - -/** - * Determine if a MediaShowInfo object is valid/acceptable. - * @param info The MediaShowInfo object. - * @returns True if the object is valid, false otherwise. - */ -export function isValidMediaShowInfo(info: MediaShowInfo): boolean { - return ( - info.height >= MEDIA_INFO_HEIGHT_CUTOFF && info.width >= MEDIA_INFO_WIDTH_CUTOFF - ); -} - -/** - * Convert a generic Action to a FrigateCardCustomAction if it parses correctly. - * @param action The generic action configuration. - * @returns A FrigateCardCustomAction or null if it cannot be converted. - */ -export function convertActionToFrigateCardCustomAction( - action: ActionType | null, -): FrigateCardCustomAction | null { - if (!action) { - return null; - } - // Parse a custom event as other things could generate ll-custom events that - // are not related to Frigate Card. - const parseResult = frigateCardCustomActionSchema.safeParse(action); - return parseResult.success ? parseResult.data : null; -} - -/** - * Create a Frigate card custom action. - * @param action The Frigate card action string (e.g. 'fullscreen') - * @returns A FrigateCardCustomAction for that action string or null. - */ -export function createFrigateCardCustomAction( - action: FrigateCardAction, - args?: { - camera?: string; - media_player?: string; - media_player_action?: 'play' | 'stop'; - }, -): FrigateCardCustomAction | null { - if (action === 'camera_select') { - if (!args?.camera) { - return null; - } - return { - action: 'fire-dom-event', - frigate_card_action: action, - camera: args.camera as string, - }; - } - if (action === 'media_player') { - if (!args?.media_player || !args.media_player_action) { - return null; - } - return { - action: 'fire-dom-event', - frigate_card_action: action, - media_player: args.media_player, - media_player_action: args.media_player_action, - }; - } - return { - action: 'fire-dom-event', - frigate_card_action: action, - }; -} - -/** - * Get an action configuration given a config and an interaction (e.g. 'tap'). - * @param interaction The interaction: `tap`, `hold` or `double_tap` - * @param config The configuration containing multiple actions. - * @returns The relevant action configuration or null if none found. - */ -export function getActionConfigGivenAction( - interaction?: string, - config?: Actions, -): ActionType | ActionType[] | undefined { - if (!interaction || !config) { - return undefined; - } - if (interaction == 'tap' && config.tap_action) { - return config.tap_action; - } else if (interaction == 'hold' && config.hold_action) { - return config.hold_action; - } else if (interaction == 'double_tap' && config.double_tap_action) { - return config.double_tap_action; - } else if (interaction == 'end_tap' && config.end_tap_action) { - return config.end_tap_action; - } else if (interaction == 'start_tap' && config.start_tap_action) { - return config.start_tap_action; - } - return undefined; -} - -/** - * Calculate a style brightness from a hass state. - * Inspired by https://github.com/home-assistant/frontend/blob/7d5b5663123bb16d1da0c5bac3f2fc26d5f69ae8/src/panels/lovelace/cards/hui-button-card.ts#L296 - * @param state The hass state object. - * @returns A CSS brightness string. - */ -function computeBrightnessFromState(state: HassEntity): string { - if (state.state === 'off' || !state.attributes.brightness) { - return ''; - } - const brightness = state.attributes.brightness; - return `brightness(${(brightness + 245) / 5}%)`; -} - -/** - * Calculate a style color from a hass state. - * Inspired by https://github.com/home-assistant/frontend/blob/7d5b5663123bb16d1da0c5bac3f2fc26d5f69ae8/src/panels/lovelace/cards/hui-button-card.ts#L304 - * @param state The hass state object. - * @returns A CSS color string. - */ -function computeColorFromState(state: HassEntity): string { - if (state.state === 'off') { - return ''; - } - return state.attributes.rgb_color - ? `rgb(${state.attributes.rgb_color.join(',')})` - : ''; -} - -/** - * Get the style of emphasized menu items. - * @returns A StyleInfo. - */ -function computeStyle(state: HassEntity): StyleInfo { - return { - color: computeColorFromState(state), - filter: computeBrightnessFromState(state), - }; -} - -/** - * Determine the string state of a given stateObj. - * From: https://github.com/home-assistant/frontend/blob/dev/src/common/entity/compute_active_state.ts - * @param stateObj The HassEntity object from `hass.states`. - * @returns A string state, e.g. 'on'. - */ -export const computeActiveState = (stateObj: HassEntity): string => { - const domain = stateObj.entity_id.split('.')[0]; - let state = stateObj.state; - - if (domain === 'climate') { - state = stateObj.attributes.hvac_action; - } - - return state; -}; - -/** - * Use Home Assistant state to refresh state parameters for an item to be rendered. - * @param hass Home Assistant object. - * @param params A StateParameters object to modify in place. - * @returns A StateParameters object updated based on HASS state. - */ -export function refreshDynamicStateParameters( - hass: HomeAssistant, - params: StateParameters, -): StateParameters { - if (!params.entity) { - return params; - } - const state = hass.states[params.entity]; - if (!!state && !!params.state_color) { - params.style = { ...computeStyle(state), ...params.style }; - } - params.title = params.title ?? (state?.attributes?.friendly_name || params.entity); - params.icon = params.icon ?? stateIcon(state); - - const domain = state ? computeStateDomain(state) : undefined; - params.data_domain = - params.state_color || (domain === 'light' && params.state_color !== false) - ? domain - : undefined; - if (state) { - params.data_state = computeActiveState(state); - } - return params; -} - -/** - * Prettify a Frigate name by converting '_' to spaces and capitalizing words. - * @param input The input Frigate (camera/label/zone) name. - * @returns A prettified name. - */ -export function prettifyFrigateName(input?: string): string | undefined { - if (!input) { - return undefined; - } - const words = input.split(/[_\s]+/); - return words - .map((word) => { - return word[0].toUpperCase() + word.substring(1); - }) - .join(' '); -} - -/** - * Get the title of an entity. - * @param entity The entity id. - * @param hass The Home Assistant object. - * @returns The title or undefined. - */ -export function getEntityTitle( - hass?: HomeAssistant, - entity?: string, -): string | undefined { - return entity ? hass?.states[entity]?.attributes?.friendly_name : undefined; -} - -/** - * Get the icon of an entity. - * @param entity The entity id. - * @param hass The Home Assistant object. - * @returns The icon or undefined. - */ -export function getEntityIcon(hass?: HomeAssistant, entity?: string): string { - return stateIcon(entity ? hass?.states[entity] : null); -} - -/** - * Get a camera id. - * @param config The camera config (either parsed or raw). - * @returns A camera id. - */ -export function getCameraID( - config?: CameraConfig | RawFrigateCardConfig | null, -): string { - return ( - (typeof config?.id === 'string' && config.id) || - (typeof config?.camera_entity === 'string' && config.camera_entity) || - (typeof config?.webrtc_card === 'object' && - config.webrtc_card && - typeof config.webrtc_card['entity'] === 'string' && - config.webrtc_card['entity']) || - (typeof config?.camera_name === 'string' && config.camera_name) || - '' - ); -} - -/** - * Get a camera text title. - * @param hass The Home Assistant object. - * @param config The camera config (either parsed or raw). - * @returns A title string. - */ -export function getCameraTitle( - hass?: HomeAssistant, - config?: CameraConfig | RawFrigateCardConfig | null, -): string { - // Attempt to render a recognizable name for the camera, - // starting with the most likely to be useful and working our - // ways towards the least useful. Extra type checking here since this is also - // used on raw configuration in the editor. - return ( - (typeof config?.title === 'string' && config.title) || - (typeof config?.camera_entity === 'string' - ? getEntityTitle(hass, config.camera_entity) - : '') || - (typeof config?.webrtc_card === 'object' && - config.webrtc_card && - typeof config.webrtc_card['entity'] === 'string' && - config.webrtc_card['entity']) || - (typeof config?.camera_name === 'string' - ? prettifyFrigateName(config.camera_name) - : '') || - (typeof config?.id === 'string' && config.id) || - '' - ); -} - -/** - * Get a camera icon. - * @param hass The Home Assistant object. - * @param config The camera config. - * @returns An icon string. - */ -export function getCameraIcon( - hass?: HomeAssistant, - config?: CameraConfig | null, -): string { - return config?.icon || getEntityIcon(hass, config?.camera_entity) || 'mdi:video'; -} - -/** - * Move an element within an array. - * @param target Target array. - * @param from From index. - * @param to To index. - */ -export function arrayMove(target: unknown[], from: number, to: number): void { - const element = target[from]; - target.splice(from, 1); - target.splice(to, 0, element); -} - -/** - * Determine if the contents of the n(ew) and o(ld) values have changed. For use - * in lit web components that may have a value that changes address but not - * contents -- and for which a re-render is expensive/jarring. - * @param n The new value. - * @param o The old value. - * @returns `true` is the contents have changed. - */ -export function contentsChanged(n: unknown, o: unknown): boolean { - return !isEqual(n, o); -} - -/** - * Frigate card custom version of handleAction - * (https://github.com/custom-cards/custom-card-helpers/blob/master/src/handle-action.ts) - * that handles the custom action events the card supports. - * @param node The node that fired the event. - * @param hass The Home Assistant object. - * @param config The multi-action configuration. - * @param action The action string (e.g. 'hold') - * @returns Whether or not an action was executed. - */ -export const frigateCardHandleAction = ( - node: HTMLElement, - hass: HomeAssistant, - config: ActionsConfig, - action: string, -): boolean => { - return frigateCardHandleActionConfig( - node, - hass, - config, - action, - getActionConfigGivenAction(action, config), - ); -}; - -/** - * Handle an ActionConfig or array of ActionConfigs. - * @param node The node that fired the event. - * @param hass The Home Assistant object. - * @param actionConfig A single action config, array of action configs or - * undefined for the default action config for 'tap'. - * @param action The action string (e.g. 'hold') - * @returns Whether or not an action was executed. - */ -export const frigateCardHandleActionConfig = ( - node: HTMLElement, - hass: HomeAssistant, - config: { - camera_image?: string; - entity?: string; - }, - action: string, - actionConfig: ActionType | ActionType[] | undefined, -): boolean => { - // Only allow a tap action to use a default non-config (the more-info config). - if (actionConfig || action == 'tap') { - // ActionConfig vs ActionType: - // There is a slight typing (but not functional) difference between - // ActionType in this card and ActionConfig in `custom-card-helpers`. See - // `ExtendedConfirmationRestrictionConfig` in `types.ts` for the source and - // reason behind this difference. - if (Array.isArray(actionConfig)) { - actionConfig.forEach((action) => - handleActionConfig(node, hass, config, action as ActionConfig | undefined), - ); - } else { - handleActionConfig(node, hass, config, actionConfig as ActionConfig | undefined); - } - return true; - } - return false; -}; - -/** - * Determine if an action config has a real action. A modified version of - * custom-card-helpers hasAction to also work with arrays of action configs. - * @param config The action config in question. - * @returns `true` if there's a real action defined, `false` otherwise. - */ -export const frigateCardHasAction = ( - config?: ActionType | ActionType[] | undefined, -): boolean => { - // See note above on 'ActionConfig vs ActionType' for why this cast is - // necessary and harmless. - if (Array.isArray(config)) { - return !!config.find((item) => hasAction(item as ActionConfig | undefined)); - } - return hasAction(config as ActionConfig | undefined); -}; - -/** - * Stop an event from activating card wide actions. - */ -export const stopEventFromActivatingCardWideActions = (ev: Event): void => { - ev.stopPropagation(); -}; - -/** - * Convenience function to convert a timestamp to hours, minutes and seconds - * string. Heavily inspired by, and returning the same format as, the Frigate - * UI: https://github.com/blakeblackshear/frigate/blob/master/web/src/components/RecordingPlaylist.jsx#L97 - * @param event The Frigate event. - * @returns A duration string. - */ -export function getEventDurationString(event: FrigateEvent): string { - if (!event.end_time) { - return localize('event.in_progress'); - } - const start = fromUnixTime(event.start_time); - const end = fromUnixTime(event.end_time); - const hours = differenceInHours(end, start); - const minutes = differenceInMinutes(end, start) - hours * 60; - const seconds = differenceInSeconds(end, start) - hours * 60 * 60 - minutes * 60; - let duration = ''; - - if (hours) { - duration += `${hours}h `; - } - if (minutes) { - duration += `${minutes}m `; - } - duration += `${seconds}s`; - return duration; -} - -/** - * Side loads the HA elements this card needs. This trickery is unfortunate - * necessary, see: - * - https://github.com/thomasloven/hass-config/wiki/PreLoading-Lovelace-Elements - * @returns `true` if the load is successful, `false` otherwise. - */ -export const sideLoadHomeAssistantElements = async (): Promise => { - const neededElements = [ - 'ha-selector', - 'ha-menu-button', - 'ha-camera-stream', - 'ha-hls-player', - 'ha-web-rtc-player', - 'ha-icon', - 'ha-circular-progress', - 'ha-icon-button', - 'ha-card', - 'ha-svg-icon', - 'ha-button-menu', - ]; - - if (neededElements.every((element) => customElements.get(element))) { - return true; - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const helpers: CardHelpers = await (window as any).loadCardHelpers(); - - // The picture-glance editor loads everything this card needs. - const pictureGlance = await helpers.createCardElement({ - type: 'picture-glance', - entities: [], - camera_image: 'dummy-to-load-editor-components', - }); - if (pictureGlance.constructor.getConfigElement) { - await pictureGlance.constructor.getConfigElement(); - return true; - } - return false; -}; diff --git a/src/components/carousel.ts b/src/components/carousel.ts index 9cd9b923..9da0102f 100644 --- a/src/components/carousel.ts +++ b/src/components/carousel.ts @@ -1,17 +1,14 @@ -import { CSSResultGroup, LitElement, unsafeCSS, PropertyValues } from 'lit'; -import { property } from 'lit/decorators.js'; - import EmblaCarousel, { EmblaCarouselType, EmblaOptionsType, - EmblaPluginType, + EmblaPluginType } from 'embla-carousel'; import { WheelGesturesPlugin } from 'embla-carousel-wheel-gestures'; - -import { TransitionEffect } from '../types'; -import { dispatchFrigateCardEvent } from '../common'; - +import { CSSResultGroup, LitElement, PropertyValues, unsafeCSS } from 'lit'; +import { property } from 'lit/decorators.js'; import carouselStyle from '../scss/carousel.scss'; +import { TransitionEffect } from '../types'; +import { dispatchFrigateCardEvent } from '../utils/basic.js'; export interface CarouselSelect { index: number; diff --git a/src/components/elements.ts b/src/components/elements.ts index 2eada158..e815a5fe 100644 --- a/src/components/elements.ts +++ b/src/components/elements.ts @@ -1,28 +1,27 @@ -import { - LitElement, - TemplateResult, - html, - CSSResultGroup, - unsafeCSS, - PropertyValues, -} from 'lit'; import { HomeAssistant } from 'custom-card-helpers'; +import { + CSSResultGroup, + html, + LitElement, + PropertyValues, + TemplateResult, + unsafeCSS +} from 'lit'; import { customElement, property } from 'lit/decorators.js'; - +import { ConditionState, fetchStateAndEvaluateCondition } from '../card-condition.js'; +import { localize } from '../localize/localize.js'; +import elementsStyle from '../scss/elements.scss'; import { FrigateConditional, MenuButton, MenuIcon, MenuStateIcon, - PictureElements, MenuSubmenu, MenuSubmenuSelect, + PictureElements } from '../types.js'; -import { dispatchErrorMessageEvent, dispatchFrigateCardEvent } from '../common.js'; - -import elementsStyle from '../scss/elements.scss'; -import { localize } from '../localize/localize.js'; -import { ConditionState, fetchStateAndEvaluateCondition } from '../card-condition.js'; +import { dispatchFrigateCardEvent } from '../utils/basic.js'; +import { dispatchErrorMessageEvent } from './message.js'; /* A note on picture element rendering: * diff --git a/src/components/gallery.ts b/src/components/gallery.ts index 1bdcf2a7..4f03f0b9 100644 --- a/src/components/gallery.ts +++ b/src/components/gallery.ts @@ -1,26 +1,23 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +import { HomeAssistant } from 'custom-card-helpers'; import { + css, CSSResultGroup, + html, LitElement, PropertyValues, TemplateResult, - css, - html, - unsafeCSS, + unsafeCSS } from 'lit'; -import { HomeAssistant } from 'custom-card-helpers'; import { customElement, property } from 'lit/decorators.js'; - -import { CameraConfig, GalleryConfig, frigateCardConfigDefaults } from '../types.js'; -import { BrowseMediaUtil } from '../browse-media-util.js'; -import { View } from '../view.js'; -import { THUMBNAIL_DETAILS_WIDTH_MIN } from './thumbnail.js'; -import { renderProgressIndicator } from './message.js'; -import { stopEventFromActivatingCardWideActions } from '../common.js'; - -import './thumbnail.js'; - import galleryStyle from '../scss/gallery.scss'; +import { CameraConfig, frigateCardConfigDefaults, GalleryConfig } from '../types.js'; +import { stopEventFromActivatingCardWideActions } from '../utils/action.js'; +import { BrowseMediaUtil } from '../utils/ha/browse-media.js'; +import { View } from '../view.js'; +import { renderProgressIndicator } from './message.js'; +import './thumbnail.js'; +import { THUMBNAIL_DETAILS_WIDTH_MIN } from './thumbnail.js'; @customElement('frigate-card-gallery') export class FrigateCardGallery extends LitElement { diff --git a/src/components/image.ts b/src/components/image.ts index 28d1c0de..0ba8f042 100644 --- a/src/components/image.ts +++ b/src/components/image.ts @@ -1,28 +1,23 @@ +import { HomeAssistant } from 'custom-card-helpers'; import { CSSResultGroup, + html, LitElement, PropertyValues, TemplateResult, - html, - unsafeCSS, + unsafeCSS } from 'lit'; -import { HomeAssistant } from 'custom-card-helpers'; -import { createRef, ref, Ref } from 'lit/directives/ref.js'; import { customElement, property, state } from 'lit/decorators.js'; - +import { createRef, ref, Ref } from 'lit/directives/ref.js'; import { CachedValueController } from '../cached-value-controller.js'; -import { CameraConfig, ImageViewConfig } from '../types.js'; -import { View } from '../view.js'; -import { - dispatchErrorMessageEvent, - dispatchMediaShowEvent, - shouldUpdateBasedOnHass, -} from '../common.js'; -import { localize } from '../localize/localize.js'; - import defaultImage from '../images/frigate-bird-in-sky.jpg'; - +import { localize } from '../localize/localize.js'; import imageStyle from '../scss/image.scss'; +import { CameraConfig, ImageViewConfig } from '../types.js'; +import { shouldUpdateBasedOnHass } from '../utils/ha'; +import { dispatchMediaShowEvent } from '../utils/media-info.js'; +import { View } from '../view.js'; +import { dispatchErrorMessageEvent } from './message.js'; // See: https://github.com/home-assistant/core/blob/dev/homeassistant/components/camera/__init__.py#L101 const HASS_REJECTION_CUTOFF_MS = 5 * 60 * 1000; @@ -41,7 +36,7 @@ export class FrigateCardImage extends LitElement { @state() protected _imageConfig?: ImageViewConfig; - protected _refImage: Ref = createRef() ; + protected _refImage: Ref = createRef(); protected _cachedValueController?: CachedValueController; protected _boundVisibilityHandler = this._visibilityHandler.bind(this); diff --git a/src/components/live.ts b/src/components/live.ts index 7c1d52ce..adb2c111 100644 --- a/src/components/live.ts +++ b/src/components/live.ts @@ -1,60 +1,56 @@ +import JSMpeg from '@cycjimmy/jsmpeg-player'; +import { Task } from '@lit-labs/task'; +import { HomeAssistant } from 'custom-card-helpers'; +import { EmblaOptionsType, EmblaPluginType } from 'embla-carousel'; import { CSSResultGroup, - LitElement, - TemplateResult, html, - unsafeCSS, + LitElement, PropertyValues, + TemplateResult, + unsafeCSS } from 'lit'; -import { - ExtendedHomeAssistant, - CameraConfig, - JSMPEGConfig, - LiveConfig, - MediaShowInfo, - WebRTCCardConfig, - FrigateCardError, - FrigateCardMediaPlayer, - LiveOverrides, - LiveProvider, - TransitionEffect, - frigateCardConfigDefaults, -} from '../types.js'; -import { EmblaOptionsType, EmblaPluginType } from 'embla-carousel'; -import { HomeAssistant } from 'custom-card-helpers'; -import JSMpeg from '@cycjimmy/jsmpeg-player'; -import { Ref, createRef, ref } from 'lit/directives/ref.js'; -import { Task } from '@lit-labs/task'; import { customElement, property, state } from 'lit/decorators.js'; +import { createRef, Ref, ref } from 'lit/directives/ref.js'; import { until } from 'lit/directives/until.js'; - -import { AutoMediaPlugin, AutoMediaPluginType } from './embla-plugins/automedia.js'; -import { BrowseMediaUtil } from '../browse-media-util.js'; import { ConditionState, getOverriddenConfig } from '../card-condition.js'; -import { FrigateCardMediaCarousel } from './media-carousel.js'; -import { FrigateCardNextPreviousControl } from './next-prev-control.js'; -import { Lazyload } from './embla-plugins/lazyload.js'; -import { View } from '../view.js'; -import { localize } from '../localize/localize.js'; -import { - contentsChanged, - dispatchErrorMessageEvent, - dispatchExistingMediaShowInfoAsEvent, - dispatchMediaShowEvent, - getCameraIcon, - getCameraTitle, - homeAssistantSignPath, - stopEventFromActivatingCardWideActions, -} from '../common.js'; import { renderProgressIndicator } from '../components/message.js'; - -import './next-prev-control.js'; -import './title-control.js'; - -import liveStyle from '../scss/live.scss'; +import { localize } from '../localize/localize.js'; import liveFrigateStyle from '../scss/live-frigate.scss'; import liveJSMPEGStyle from '../scss/live-jsmpeg.scss'; import liveWebRTCStyle from '../scss/live-webrtc.scss'; +import liveStyle from '../scss/live.scss'; +import { + CameraConfig, + ExtendedHomeAssistant, + frigateCardConfigDefaults, + FrigateCardError, + FrigateCardMediaPlayer, + JSMPEGConfig, + LiveConfig, + LiveOverrides, + LiveProvider, + MediaShowInfo, + TransitionEffect, + WebRTCCardConfig +} from '../types.js'; +import { stopEventFromActivatingCardWideActions } from '../utils/action.js'; +import { contentsChanged } from '../utils/basic.js'; +import { getCameraIcon, getCameraTitle } from '../utils/camera.js'; +import { homeAssistantSignPath } from '../utils/ha'; +import { BrowseMediaUtil } from '../utils/ha/browse-media.js'; +import { + dispatchExistingMediaShowInfoAsEvent, + dispatchMediaShowEvent +} from '../utils/media-info.js'; +import { View } from '../view.js'; +import { AutoMediaPlugin, AutoMediaPluginType } from './embla-plugins/automedia.js'; +import { Lazyload } from './embla-plugins/lazyload.js'; +import { FrigateCardMediaCarousel } from './media-carousel.js'; +import { dispatchErrorMessageEvent } from './message.js'; +import './next-prev-control.js'; +import { FrigateCardNextPreviousControl } from './next-prev-control.js'; +import './title-control.js'; // Number of seconds a signed URL is valid for. const URL_SIGN_EXPIRY_SECONDS = 24 * 60 * 60; diff --git a/src/components/media-carousel.ts b/src/components/media-carousel.ts index fa6afa4f..2ff28371 100644 --- a/src/components/media-carousel.ts +++ b/src/components/media-carousel.ts @@ -1,21 +1,18 @@ -import { CSSResultGroup, unsafeCSS } from 'lit'; import { EmblaCarouselType } from 'embla-carousel'; -import { createRef, Ref } from 'lit/directives/ref.js'; +import { CSSResultGroup, unsafeCSS } from 'lit'; import { customElement } from 'lit/decorators.js'; - -import { AutoMediaPluginType } from './embla-plugins/automedia.js'; -import { FrigateCardCarousel } from './carousel.js'; -import { FrigateCardNextPreviousControl } from './next-prev-control.js'; -import { FrigateCardTitleControl } from './title-control.js'; +import { createRef, Ref } from 'lit/directives/ref.js'; +import mediaCarouselStyle from '../scss/media-carousel.scss'; import type { MediaShowInfo } from '../types.js'; import { dispatchExistingMediaShowInfoAsEvent, - isValidMediaShowInfo, -} from '../common.js'; - + isValidMediaShowInfo +} from '../utils/media-info.js'; +import { FrigateCardCarousel } from './carousel.js'; +import { AutoMediaPluginType } from './embla-plugins/automedia.js'; import './next-prev-control.js'; - -import mediaCarouselStyle from '../scss/media-carousel.scss'; +import { FrigateCardNextPreviousControl } from './next-prev-control.js'; +import { FrigateCardTitleControl } from './title-control.js'; const getEmptyImageSrc = (width: number, height: number) => `data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}"%3E%3C/svg%3E`; diff --git a/src/components/menu.ts b/src/components/menu.ts index 07888542..a58b07e3 100644 --- a/src/components/menu.ts +++ b/src/components/menu.ts @@ -1,38 +1,34 @@ +import { HASSDomEvent, HomeAssistant } from 'custom-card-helpers'; import { CSSResultGroup, - LitElement, - TemplateResult, html, - unsafeCSS, + LitElement, PropertyValues, + TemplateResult, + unsafeCSS } from 'lit'; -import { HASSDomEvent, HomeAssistant } from 'custom-card-helpers'; import { customElement, property, state } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import { ifDefined } from 'lit/directives/if-defined.js'; import { styleMap } from 'lit/directives/style-map.js'; - import { actionHandler } from '../action-handler-directive.js'; - -import './submenu.js'; - +import menuStyle from '../scss/menu.scss'; import type { ActionsConfig, ActionType, MenuButton, MenuConfig, MenuItem, - StateParameters, + StateParameters } from '../types.js'; import { convertActionToFrigateCardCustomAction, frigateCardHandleActionConfig, frigateCardHasAction, - getActionConfigGivenAction, - refreshDynamicStateParameters, -} from '../common.js'; - -import menuStyle from '../scss/menu.scss'; + getActionConfigGivenAction +} from '../utils/action.js'; +import { refreshDynamicStateParameters } from '../utils/ha'; +import './submenu.js'; export const FRIGATE_BUTTON_MENU_ICON = 'frigate'; export const FRIGATE_ICON_FILLED = diff --git a/src/components/message.ts b/src/components/message.ts index cc947653..12547788 100644 --- a/src/components/message.ts +++ b/src/components/message.ts @@ -1,11 +1,10 @@ -import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit'; +import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit'; import { customElement, property } from 'lit/decorators.js'; - -import { Message } from '../types.js'; import { TROUBLESHOOTING_URL } from '../const.js'; import { localize } from '../localize/localize.js'; - import messageStyle from '../scss/message.scss'; +import { Message } from '../types.js'; +import { dispatchFrigateCardEvent } from '../utils/basic.js'; @customElement('frigate-card-message') export class FrigateCardMessage extends LitElement { @@ -21,18 +20,19 @@ export class FrigateCardMessage extends LitElement { // Render the menu. protected render(): TemplateResult { const icon = this.icon ? this.icon : 'mdi:information-outline'; - return html` -
-
-
- -
-
- ${this.message ? html`${this.message}` : ''} - ${this.context ? html`
${JSON.stringify(this.context, null, 2)}
` : ''} -
+ return html`
+
+
+
-
`; +
+ ${this.message ? html`${this.message}` : ''} + ${this.context + ? html`
${JSON.stringify(this.context, null, 2)}
` + : ''} +
+
+
`; } static get styles(): CSSResultGroup { @@ -103,3 +103,40 @@ export function renderProgressIndicator(message?: string): TemplateResult { `; } + +/** + * Dispatch an event with a message to show to the user. + * @param element The element to send the event. + * @param message The message to show. + * @param icon An optional icon to attach to the message. + */ +export function dispatchMessageEvent( + element: HTMLElement, + message: string, + icon?: string, + context?: unknown, +): void { + dispatchFrigateCardEvent(element, 'message', { + message: message, + type: 'info', + icon: icon, + context: context, + }); +} + +/** + * Dispatch an event with an error message to show to the user. + * @param element The element to send the event. + * @param message The message to show. + */ +export function dispatchErrorMessageEvent( + element: HTMLElement, + message: string, + context?: unknown, +): void { + dispatchFrigateCardEvent(element, 'message', { + message: message, + type: 'error', + context: context, + }); +} diff --git a/src/components/submenu.ts b/src/components/submenu.ts index df601177..f7a77562 100644 --- a/src/components/submenu.ts +++ b/src/components/submenu.ts @@ -1,29 +1,25 @@ import type { Corner } from '@material/mwc-menu'; +import { HomeAssistant } from 'custom-card-helpers'; import { CSSResultGroup, html, LitElement, PropertyValues, TemplateResult, - unsafeCSS, + unsafeCSS } from 'lit'; -import { HomeAssistant } from 'custom-card-helpers'; import { customElement, property } from 'lit/decorators.js'; - -import { - frigateCardHasAction, - refreshDynamicStateParameters, - shouldUpdateBasedOnHass, - stopEventFromActivatingCardWideActions, -} from '../common.js'; import { ifDefined } from 'lit/directives/if-defined.js'; import { styleMap } from 'lit/directives/style-map.js'; - -import { MenuSubmenu, MenuSubmenuItem, MenuSubmenuSelect } from '../types.js'; import { actionHandler } from '../action-handler-directive.js'; -import { domainIcon } from '../icons/domain-icon.js'; - import submenuStyle from '../scss/submenu.scss'; +import { MenuSubmenu, MenuSubmenuItem, MenuSubmenuSelect } from '../types.js'; +import { + frigateCardHasAction, + stopEventFromActivatingCardWideActions +} from '../utils/action.js'; +import { refreshDynamicStateParameters, shouldUpdateBasedOnHass } from '../utils/ha'; +import { domainIcon } from '../utils/icons/domain-icon.js'; @customElement('frigate-card-submenu') export class FrigateCardSubmenu extends LitElement { diff --git a/src/components/surround-thumbnails.ts b/src/components/surround-thumbnails.ts index 9bd707f9..3128005a 100644 --- a/src/components/surround-thumbnails.ts +++ b/src/components/surround-thumbnails.ts @@ -1,23 +1,20 @@ -import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit'; -import { HomeAssistant } from 'custom-card-helpers'; import { Task } from '@lit-labs/task'; +import { HomeAssistant } from 'custom-card-helpers'; +import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit'; import { customElement, property } from 'lit/decorators.js'; - -import { BrowseMediaUtil } from '../browse-media-util.js'; +import surroundThumbnailsStyle from '../scss/surround.scss'; import { BrowseMediaQueryParameters, FrigateBrowseMediaSource, FrigateCardView, - ThumbnailsControlConfig, + ThumbnailsControlConfig } from '../types.js'; - -import { ThumbnailCarouselTap } from './thumbnail-carousel.js'; +import { dispatchFrigateCardEvent } from '../utils/basic.js'; +import { BrowseMediaUtil } from '../utils/ha/browse-media.js'; import { View } from '../view.js'; -import { dispatchErrorMessageEvent, dispatchFrigateCardEvent } from '../common.js'; - +import { dispatchErrorMessageEvent } from './message.js'; import './surround.js'; - -import surroundThumbnailsStyle from '../scss/surround.scss'; +import { ThumbnailCarouselTap } from './thumbnail-carousel.js'; @customElement('frigate-card-surround-thumbnails') export class FrigateCardSurround extends LitElement { @@ -71,7 +68,10 @@ export class FrigateCardSurround extends LitElement { } let parent: FrigateBrowseMediaSource | null; try { - parent = await BrowseMediaUtil.multipleBrowseMediaQueryMerged(this.hass, browseMediaParams); + parent = await BrowseMediaUtil.multipleBrowseMediaQueryMerged( + this.hass, + browseMediaParams, + ); } catch (e) { return dispatchErrorMessageEvent(this, (e as Error).message); } diff --git a/src/components/thumbnail-carousel.ts b/src/components/thumbnail-carousel.ts index bfa0b36b..6310be56 100644 --- a/src/components/thumbnail-carousel.ts +++ b/src/components/thumbnail-carousel.ts @@ -1,22 +1,16 @@ -import { BrowseMediaUtil } from '../browse-media-util.js'; -import { CSSResultGroup, TemplateResult, html, unsafeCSS, PropertyValues } from 'lit'; import { EmblaOptionsType } from 'embla-carousel'; -import { classMap } from 'lit/directives/class-map.js'; +import { CSSResultGroup, html, PropertyValues, TemplateResult, unsafeCSS } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; +import { classMap } from 'lit/directives/class-map.js'; import { ifDefined } from 'lit/directives/if-defined.js'; - -import type { FrigateBrowseMediaSource, ThumbnailsControlConfig } from '../types.js'; -import { FrigateCardCarousel } from './carousel.js'; -import { View } from '../view.js'; -import { - contentsChanged, - dispatchFrigateCardEvent, - stopEventFromActivatingCardWideActions, -} from '../common.js'; - -import './thumbnail.js'; - import thumbnailCarouselStyle from '../scss/thumbnail-carousel.scss'; +import type { FrigateBrowseMediaSource, ThumbnailsControlConfig } from '../types.js'; +import { stopEventFromActivatingCardWideActions } from '../utils/action.js'; +import { contentsChanged, dispatchFrigateCardEvent } from '../utils/basic.js'; +import { BrowseMediaUtil } from '../utils/ha/browse-media.js'; +import { View } from '../view.js'; +import { FrigateCardCarousel } from './carousel.js'; +import './thumbnail.js'; export interface ThumbnailCarouselTap { slideIndex: number; diff --git a/src/components/thumbnail.ts b/src/components/thumbnail.ts index 4a4480a4..e7265e4b 100644 --- a/src/components/thumbnail.ts +++ b/src/components/thumbnail.ts @@ -1,18 +1,14 @@ -import { CSSResult, TemplateResult, html, unsafeCSS, LitElement } from 'lit'; -import { customElement, property } from 'lit/decorators.js'; import { format, fromUnixTime } from 'date-fns'; - -import type { FrigateBrowseMediaSource, FrigateEvent } from '../types.js'; -import { View } from '../view.js'; -import { - getEventDurationString, - prettifyFrigateName, - stopEventFromActivatingCardWideActions, -} from '../common.js'; +import { CSSResult, html, LitElement, TemplateResult, unsafeCSS } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; import { localize } from '../localize/localize.js'; - -import thumbnailStyle from '../scss/thumbnail.scss'; import thumbnailDetailsStyle from '../scss/thumbnail-details.scss'; +import thumbnailStyle from '../scss/thumbnail.scss'; +import type { FrigateBrowseMediaSource, FrigateEvent } from '../types.js'; +import { stopEventFromActivatingCardWideActions } from '../utils/action.js'; +import { prettifyTitle } from '../utils/basic.js'; +import { getEventDurationString } from '../utils/frigate.js'; +import { View } from '../view.js'; // The minimum width of a thumbnail with details enabled. export const THUMBNAIL_DETAILS_WIDTH_MIN = 300; @@ -28,14 +24,14 @@ export class FrigateCardThumbnailDetails extends LitElement { } const score = (this.event.top_score * 100).toFixed(2) + '%'; return html`
-
${prettifyFrigateName(this.event.label)}
+
${prettifyTitle(this.event.label)}
- ${localize('event.start')}: - ${format(fromUnixTime(this.event.start_time), 'HH:mm:ss')} + ${localize('event.start')}: + ${format(fromUnixTime(this.event.start_time), 'HH:mm:ss')}
- ${localize('event.duration')}: - ${getEventDurationString(this.event)} + ${localize('event.duration')}: + ${getEventDurationString(this.event)}
diff --git a/src/components/timeline.ts b/src/components/timeline.ts index 108aa6f7..ce219bcc 100644 --- a/src/components/timeline.ts +++ b/src/components/timeline.ts @@ -1,11 +1,17 @@ +import { HomeAssistant } from 'custom-card-helpers'; +import { add, fromUnixTime, sub } from 'date-fns'; import { CSSResultGroup, - LitElement, - TemplateResult, html, - unsafeCSS, + LitElement, PropertyValues, + TemplateResult, + unsafeCSS } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { classMap } from 'lit/directives/class-map.js'; +import { createRef, ref, Ref } from 'lit/directives/ref.js'; +import { isEqual } from 'lodash-es'; import { DataSet } from 'vis-data/esnext'; import { DataGroupCollectionType, @@ -14,38 +20,26 @@ import { TimelineItem, TimelineOptions, TimelineOptionsCluster, - TimelineWindow, + TimelineWindow } from 'vis-timeline/esnext'; -import { HomeAssistant } from 'custom-card-helpers'; -import { classMap } from 'lit/directives/class-map.js'; -import { customElement, property } from 'lit/decorators.js'; -import { createRef, ref, Ref } from 'lit/directives/ref.js'; -import { add, fromUnixTime, sub } from 'date-fns'; -import { isEqual } from 'lodash-es'; - -import { BrowseMediaUtil } from '../browse-media-util'; +import { CAMERA_BIRDSEYE } from '../const'; +import { localize } from '../localize/localize'; +import timelineCoreStyle from '../scss/timeline-core.scss'; +import timelineStyle from '../scss/timeline.scss'; import { BrowseMediaQueryParameters, CameraConfig, FrigateBrowseMediaSource, - TimelineConfig, - FrigateEvent, frigateCardConfigDefaults, + FrigateEvent, + TimelineConfig } from '../types'; -import { CAMERA_BIRDSEYE } from '../const'; +import { stopEventFromActivatingCardWideActions } from '../utils/action.js'; +import { dispatchFrigateCardEvent } from '../utils/basic.js'; +import { getCameraTitle } from '../utils/camera.js'; +import { BrowseMediaUtil } from '../utils/ha/browse-media.js'; import { View, ViewContext } from '../view'; -import { - dispatchErrorMessageEvent, - dispatchFrigateCardEvent, - dispatchMessageEvent, - getCameraTitle, - stopEventFromActivatingCardWideActions, -} from '../common.js'; -import { localize } from '../localize/localize'; - -import timelineCoreStyle from '../scss/timeline-core.scss'; -import timelineStyle from '../scss/timeline.scss'; - +import { dispatchErrorMessageEvent, dispatchMessageEvent } from './message.js'; import './surround-thumbnails.js'; const TIMELINE_EVENT_MANAGER_MAX_AGE_SECONDS = 10; diff --git a/src/components/viewer.ts b/src/components/viewer.ts index c9923252..ab332aeb 100644 --- a/src/components/viewer.ts +++ b/src/components/viewer.ts @@ -1,19 +1,21 @@ +import { Task } from '@lit-labs/task'; +import { EmblaOptionsType, EmblaPluginType } from 'embla-carousel'; import { CSSResultGroup, + html, LitElement, PropertyValues, TemplateResult, - html, - unsafeCSS, + unsafeCSS } from 'lit'; -import { BrowseMediaUtil } from '../browse-media-util.js'; -import { EmblaOptionsType, EmblaPluginType } from 'embla-carousel'; -import { Task } from '@lit-labs/task'; import { customElement, property } from 'lit/decorators.js'; import { ifDefined } from 'lit/directives/if-defined.js'; import { ref } from 'lit/directives/ref.js'; - -import { AutoMediaPlugin } from './embla-plugins/automedia.js'; +import { + dispatchErrorMessageEvent, + renderProgressIndicator +} from '../components/message.js'; +import viewerStyle from '../scss/viewer.scss'; import type { BrowseMediaNeighbors, BrowseMediaQueryParameters, @@ -22,26 +24,21 @@ import type { FrigateBrowseMediaSource, MediaShowInfo, TransitionEffect, - ViewerConfig, + ViewerConfig } from '../types.js'; -import { FrigateCardMediaCarousel, IMG_EMPTY } from './media-carousel.js'; -import { FrigateCardNextPreviousControl } from './next-prev-control.js'; -import { Lazyload, LazyloadType } from './embla-plugins/lazyload.js'; -import { ResolvedMediaCache, ResolvedMediaUtil } from '../resolved-media.js'; +import { stopEventFromActivatingCardWideActions } from '../utils/action.js'; +import { contentsChanged } from '../utils/basic.js'; +import { BrowseMediaUtil } from '../utils/ha/browse-media.js'; +import { createMediaShowInfo } from '../utils/media-info.js'; +import { ResolvedMediaCache, ResolvedMediaUtil } from '../utils/resolved-media.js'; import { View } from '../view.js'; -import { - contentsChanged, - createMediaShowInfo, - dispatchErrorMessageEvent, - stopEventFromActivatingCardWideActions, -} from '../common.js'; -import { renderProgressIndicator } from '../components/message.js'; - +import { AutoMediaPlugin } from './embla-plugins/automedia.js'; +import { Lazyload, LazyloadType } from './embla-plugins/lazyload.js'; +import { FrigateCardMediaCarousel, IMG_EMPTY } from './media-carousel.js'; import './next-prev-control.js'; +import { FrigateCardNextPreviousControl } from './next-prev-control.js'; import './title-control.js'; -import viewerStyle from '../scss/viewer.scss'; - @customElement('frigate-card-viewer') export class FrigateCardViewer extends LitElement { @property({ attribute: false }) diff --git a/src/editor.ts b/src/editor.ts index b003f60b..981ef41c 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -1,18 +1,15 @@ -import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit'; +import { fireEvent, HomeAssistant, LovelaceCardEditor } from 'custom-card-helpers'; +import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; - -import { HomeAssistant, LovelaceCardEditor, fireEvent } from 'custom-card-helpers'; -import { localize } from './localize/localize.js'; import { - BUTTON_SIZE_MIN, - FRIGATE_MENU_PRIORITY_MAX, - RawFrigateCardConfig, - RawFrigateCardConfigArray, - THUMBNAIL_WIDTH_MAX, - THUMBNAIL_WIDTH_MIN, - frigateCardConfigDefaults, -} from './types.js'; - + copyConfig, + deleteConfigValue, + getArrayConfigPath, + getConfigValue, + isConfigUpgradeable, + setConfigValue, + upgradeConfig, +} from './config-mgmt.js'; import { CONF_CAMERAS, CONF_CAMERAS_ARRAY_CAMERA_ENTITY, @@ -89,23 +86,20 @@ import { CONF_VIEW_UPDATE_FORCE, CONF_VIEW_UPDATE_SECONDS, } from './const.js'; -import { - arrayMove, - getCameraID, - getCameraTitle, - sideLoadHomeAssistantElements, -} from './common.js'; -import { - copyConfig, - deleteConfigValue, - getArrayConfigPath, - getConfigValue, - isConfigUpgradeable, - setConfigValue, - upgradeConfig, -} from './config-mgmt.js'; - +import { localize } from './localize/localize.js'; import frigate_card_editor_style from './scss/editor.scss'; +import { + BUTTON_SIZE_MIN, + frigateCardConfigDefaults, + FRIGATE_MENU_PRIORITY_MAX, + RawFrigateCardConfig, + RawFrigateCardConfigArray, + THUMBNAIL_WIDTH_MAX, + THUMBNAIL_WIDTH_MIN, +} from './types.js'; +import { arrayMove } from './utils/basic.js'; +import { getCameraID, getCameraTitle } from './utils/camera.js'; +import { sideLoadHomeAssistantElements } from './utils/ha'; const MENU_BUTTONS = 'buttons'; const MENU_CAMERAS = 'cameras'; @@ -1006,12 +1000,9 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor })}