diff --git a/src/card.ts b/src/card.ts index 8f57cd9d..74ba6c81 100644 --- a/src/card.ts +++ b/src/card.ts @@ -63,6 +63,7 @@ import { MediaShowInfo, MEDIA_TYPE_IMAGE, MEDIA_TYPE_VIDEO, + MESSAGE_TYPE_PRIORITIES, MenuButton, Message, RawFrigateCardConfig, @@ -1115,7 +1116,7 @@ export class FrigateCard extends LitElement { { message: localize('error.reconnecting'), icon: 'mdi:lan-disconnect', - type: 'info', + type: 'connection', dotdotdot: true, }, true, @@ -1403,7 +1404,7 @@ export class FrigateCard extends LitElement { this._setMessageAndUpdate({ message: localize('error.diagnostics'), - type: 'info', + type: 'diagnostics', icon: 'mdi:information', context: { ha_version: this._hass.config.version, @@ -1562,8 +1563,12 @@ export class FrigateCard extends LitElement { * @param skipUpdate If true an update request is skipped. */ protected _setMessageAndUpdate(message: Message, skipUpdate?: boolean): void { - // Register the first message, or prioritize errors if there's pre-render competition. - if (!this._message || (message.type == 'error' && this._message.type != 'error')) { + const currentPriority = this._message + ? MESSAGE_TYPE_PRIORITIES[this._message.type] ?? 0 + : 0; + const newPriority = MESSAGE_TYPE_PRIORITIES[message.type] ?? 0; + + if (!this._message || newPriority >= currentPriority) { this._message = message; if (!skipUpdate) { this.requestUpdate(); @@ -1650,14 +1655,12 @@ export class FrigateCard extends LitElement { // - Aspect ratio enforcement is disabled. // - Aspect ratio enforcement is dynamic and it's a media view (i.e. not the // gallery) or timeline. - // - There is a message to display to the user. return !( (screenfull.isEnabled && screenfull.isFullscreen) || aspectRatioMode == 'unconstrained' || (aspectRatioMode == 'dynamic' && - (this._view?.isAnyMediaView() || this._view?.is('timeline'))) || - this._message != null + (this._view?.isAnyMediaView() || this._view?.is('timeline'))) ); } diff --git a/src/components/image.ts b/src/components/image.ts index d11f4260..ba21b97e 100644 --- a/src/components/image.ts +++ b/src/components/image.ts @@ -237,7 +237,7 @@ export class FrigateCardImage extends LitElement { dispatchErrorMessageEvent( this, localize('error.image_load_error'), - this.imageConfig, + { context: this.imageConfig }, ); } }} diff --git a/src/components/live.ts b/src/components/live.ts index 5eea9682..c017d908 100644 --- a/src/components/live.ts +++ b/src/components/live.ts @@ -15,7 +15,7 @@ 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 { ConditionState, getOverriddenConfig } from '../card-condition.js'; -import { renderProgressIndicator } from '../components/message.js'; +import { dispatchFrigateCardErrorEvent, renderProgressIndicator } from '../components/message.js'; import { localize } from '../localize/localize.js'; import liveFrigateStyle from '../scss/live-frigate.scss'; import liveJSMPEGStyle from '../scss/live-jsmpeg.scss'; @@ -751,7 +751,7 @@ export class FrigateCardLiveFrigate extends LitElement { return dispatchErrorMessageEvent( this, localize('error.no_live_camera'), - this.cameraConfig, + { context: this.cameraConfig }, ); } @@ -760,7 +760,7 @@ export class FrigateCardLiveFrigate extends LitElement { return dispatchErrorMessageEvent( this, localize('error.live_camera_unavailable'), - this.cameraConfig, + { context: this.cameraConfig }, ); } @@ -906,7 +906,7 @@ export class FrigateCardLiveWebRTCCard extends LitElement { e instanceof FrigateCardError ? e.message : localize('error.webrtc_card_reported_error') + ': ' + (e as Error).message, - (e as FrigateCardError).context, + { context: (e as FrigateCardError).context }, ); } if (webrtcElement) { @@ -924,7 +924,7 @@ export class FrigateCardLiveWebRTCCard extends LitElement { return html`${this._webrtcTask.render({ initial: () => renderProgressIndicator(localize('error.webrtc_card_waiting')), pending: () => renderProgressIndicator(localize('error.webrtc_card_waiting')), - error: (e: unknown) => dispatchErrorMessageEvent(this, (e as Error).message), + error: (e: unknown) => dispatchFrigateCardErrorEvent(this, e as Error), complete: () => render(), })}`; } @@ -1147,7 +1147,7 @@ export class FrigateCardLiveJSMPEG extends LitElement { return dispatchErrorMessageEvent( this, localize('error.no_camera_name'), - this.cameraConfig, + { context: this.cameraConfig }, ); } diff --git a/src/components/message.ts b/src/components/message.ts index e4f21fa5..6130eada 100644 --- a/src/components/message.ts +++ b/src/components/message.ts @@ -1,9 +1,10 @@ import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit'; import { customElement, property } from 'lit/decorators.js'; +import { classMap } from 'lit/directives/class-map.js'; import { TROUBLESHOOTING_URL } from '../const.js'; import { localize } from '../localize/localize.js'; import messageStyle from '../scss/message.scss'; -import { FrigateCardError, Message } from '../types.js'; +import { FrigateCardError, Message, MessageType } from '../types.js'; import { dispatchFrigateCardEvent } from '../utils/basic.js'; @customElement('frigate-card-message') @@ -23,14 +24,18 @@ export class FrigateCardMessage extends LitElement { // Render the menu. protected render(): TemplateResult { const icon = this.icon ? this.icon : 'mdi:information-outline'; + const classes = { + dotdotdot: !!this.dotdotdot, + }; return html`
- ${this.message ? html`${this.message}` : ''} - ${this.dotdotdot ? html`` : ``} + + ${this.message ? html`${this.message}` : ''} + ${this.context ? html`
${JSON.stringify(this.context, null, 2)}
` : ''} @@ -92,7 +97,7 @@ export function renderMessage(message: Message): TemplateResult { return html` `; - } else if (message.type === 'info') { + } else { return html` (element, 'message', { message: message, - type: 'info', - icon: icon, - context: context, + type: type, + icon: options?.icon, + context: options?.context, }); } @@ -134,16 +142,17 @@ export function dispatchMessageEvent( * 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. + * @param options Optional context to include. */ export function dispatchErrorMessageEvent( element: EventTarget, message: string, - context?: unknown, + options?: { + context?: unknown; + }, ): void { - dispatchFrigateCardEvent(element, 'message', { - message: message, - type: 'error', - context: context, + dispatchMessageEvent(element, message, 'error', { + context: options?.context, }); } @@ -156,11 +165,7 @@ export function dispatchFrigateCardErrorEvent( element: EventTarget, error: FrigateCardError, ): void { - dispatchFrigateCardEvent(element, 'message', { - message: error.message, - type: 'error', - context: error.context || '', - }); + dispatchErrorMessageEvent(element, error.message, { context: error.context }); } declare global { diff --git a/src/components/timeline.ts b/src/components/timeline.ts index f7c5add3..17271e04 100644 --- a/src/components/timeline.ts +++ b/src/components/timeline.ts @@ -1227,7 +1227,10 @@ export class FrigateCardTimelineCore extends LitElement { dispatchMessageEvent( this, localize('error.timeline_no_cameras'), - 'mdi:chart-gantt', + 'info', + { + icon: 'mdi:chart-gantt', + } ); return; } diff --git a/src/components/viewer.ts b/src/components/viewer.ts index ebfa8bfe..70cffcb0 100644 --- a/src/components/viewer.ts +++ b/src/components/viewer.ts @@ -13,7 +13,7 @@ import { customElement, property } from 'lit/decorators.js'; import { ifDefined } from 'lit/directives/if-defined.js'; import { ref } from 'lit/directives/ref.js'; import { - dispatchErrorMessageEvent, + dispatchFrigateCardErrorEvent, renderProgressIndicator } from '../components/message.js'; import viewerStyle from '../scss/viewer.scss'; @@ -632,7 +632,7 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel { return html`${this._mediaResolutionTask.render({ initial: () => renderProgressIndicator(), pending: () => renderProgressIndicator(), - error: (e: unknown) => dispatchErrorMessageEvent(this, (e as Error).message), + error: (e: unknown) => dispatchFrigateCardErrorEvent(this, e as Error), complete: () => this._render(), })}`; } diff --git a/src/scss/message.scss b/src/scss/message.scss index 1f721aca..550d7c9c 100644 --- a/src/scss/message.scss +++ b/src/scss/message.scss @@ -25,8 +25,11 @@ div.message { } div.message div.contents { + display: flex; + flex-direction: column; padding: 10px; - height: 100%; + margin-top: auto; + margin-bottom: auto; max-width: 100%; } @@ -49,7 +52,7 @@ div.message div.icon { word-break: break-all; } -.dotdotdot:before { +.dotdotdot:after { @keyframes dots { 0%, 20% { diff --git a/src/types.ts b/src/types.ts index 3cd534d7..29302125 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1188,9 +1188,18 @@ export interface MediaShowInfo { height: number; } +export const MESSAGE_TYPE_PRIORITIES = { + info: 10, + error: 20, + connection: 30, + diagnostics: 40, +} + +export type MessageType = 'info' | 'error' | 'connection' | 'diagnostics'; + export interface Message { message: string; - type: 'error' | 'info'; + type: MessageType, icon?: string; context?: unknown; dotdotdot?: boolean; diff --git a/src/utils/ha/browse-media.ts b/src/utils/ha/browse-media.ts index 4cf9b88c..dc411df7 100644 --- a/src/utils/ha/browse-media.ts +++ b/src/utils/ha/browse-media.ts @@ -3,13 +3,13 @@ import { differenceInHours, differenceInMinutes, differenceInSeconds, - fromUnixTime + fromUnixTime, } from 'date-fns'; import { homeAssistantWSRequest } from '.'; import { dispatchErrorMessageEvent, dispatchFrigateCardErrorEvent, - dispatchMessageEvent + dispatchMessageEvent, } from '../../components/message.js'; import { localize } from '../../localize/localize.js'; import { @@ -24,7 +24,7 @@ import { MEDIA_CLASS_PLAYLIST, MEDIA_CLASS_VIDEO, MEDIA_TYPE_PLAYLIST, - MEDIA_TYPE_VIDEO + MEDIA_TYPE_VIDEO, } from '../../types.js'; import { View } from '../../view.js'; import { getCameraTitle } from '../camera.js'; @@ -318,11 +318,9 @@ export const getFullDependentBrowseMediaQueryParametersOrDispatchError = ( mediaType, ); if (!params) { - dispatchErrorMessageEvent( - element, - localize('error.no_camera_name'), - cameras.get(camera), - ); + dispatchErrorMessageEvent(element, localize('error.no_camera_name'), { + context: cameras.get(camera), + }); return null; } return params; @@ -357,7 +355,10 @@ export const fetchLatestMediaAndDispatchViewChange = async ( view.isClipRelatedView() ? localize('common.no_clip') : localize('common.no_snapshot'), - view.isClipRelatedView() ? 'mdi:filmstrip-off' : 'mdi:camera-off', + 'info', + { + icon: view.isClipRelatedView() ? 'mdi:filmstrip-off' : 'mdi:camera-off', + }, ); }