import yaml from 'js-yaml'; 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 { TROUBLESHOOTING_URL } from '../const.js'; import { localize } from '../localize/localize.js'; import messageStyle from '../scss/message.scss'; import { FrigateCardError, Message, MessageType } from '../types.js'; import { dispatchFrigateCardEvent } from '../utils/basic.js'; import './icon.js'; @customElement('frigate-card-message') export class FrigateCardMessage extends LitElement { @property({ attribute: false }) public message: string | TemplateResult<1> = ''; @property({ attribute: false }) public context?: unknown; @property({ attribute: false }) public icon?: string; @property({ attribute: true, type: Boolean }) public dotdotdot?: boolean; // Render the menu. protected render(): TemplateResult { const icon = this.icon ? this.icon : 'mdi:information-outline'; const classes = { dotdotdot: !!this.dotdotdot, }; const renderContext = (contextItem: unknown): TemplateResult => { return html`
${yaml.dump(contextItem)}
`; }; return html`
${this.message ? html`${this.message}${this.context && typeof this.context === 'string' ? ': ' + this.context : ''}` : ''} ${this.context && Array.isArray(this.context) ? this.context.map((contextItem) => renderContext(contextItem)) : typeof this.context === 'object' ? renderContext(this.context) : ''}
`; } static get styles(): CSSResultGroup { return unsafeCSS(messageStyle); } } @customElement('frigate-card-error-message') export class FrigateCardErrorMessage extends LitElement { @property({ attribute: false }) public message?: Message; protected render(): TemplateResult | void { if (!this.message) { return; } return html` ${localize('error.troubleshooting')}.`} .icon=${this.message.icon ?? 'mdi:alert-circle'} .context=${this.message.context} .dotdotdot=${this.message.dotdotdot} > `; } static get styles(): CSSResultGroup { return unsafeCSS(messageStyle); } } type FrigateCardProgressIndicatorSize = 'tiny' | 'small' | 'medium' | 'large'; @customElement('frigate-card-progress-indicator') export class FrigateCardProgressIndicator extends LitElement { @property({ attribute: false }) public message: string | TemplateResult = ''; @property({ attribute: false }) public animated = false; @property({ attribute: false }) public size: FrigateCardProgressIndicatorSize = 'large'; protected render(): TemplateResult { return html`
${this.animated ? html` ` : html``} ${this.message ? html`${this.message}` : html``}
`; } static get styles(): CSSResultGroup { return unsafeCSS(messageStyle); } } export function renderMessage(message: Message | null): TemplateResult { if (message?.type === 'error') { return html` `; } else if (message) { return html` `; } return html``; } export function renderProgressIndicator(options?: { message?: string; cardWideConfig?: CardWideConfig | null; componentRef?: Ref; classes?: ClassInfo; size?: FrigateCardProgressIndicatorSize; }): TemplateResult { return html` `; } /** * Dispatch an event with a message to show to the user. Calling this method * will grind the card to a halt, so should only be used for "global" / critical * errors (i.e. not for individual errors with a given camera, since there may * be multiple correctly functioning cameras in a grid). * @param element The element to send the event. * @param message The message to show. * @param options Optional icon and context to include. */ function dispatchMessageEvent( element: EventTarget, message: string, type: MessageType, options?: { icon?: string; context?: unknown; }, ): void { dispatchFrigateCardEvent(element, 'message', { message: message, type: type, icon: options?.icon, context: options?.context, }); } /** * Dispatch an event with an error message to show to the user. Calling this * method will grind the card to a halt, so should only be used for "global" / * critical errors (i.e. not for individual errors with a given camera, since * there may be multiple correctly functioning cameras in a grid). * @param element The element to send the event. * @param message The message to show. * @param options Optional context to include. */ function dispatchErrorMessageEvent( element: EventTarget, message: string, options?: { context?: unknown; }, ): void { dispatchMessageEvent(element, message, 'error', { context: options?.context, }); } /** * Dispatch an event with an error message to show to the user. Calling this * method will grind the card to a halt, so should only be used for "global" / * critical errors (i.e. not for individual errors with a given camera, since * there may be multiple correctly functioning cameras in a grid). * @param element The element to send the event. * @param message The message to show. */ export function dispatchFrigateCardErrorEvent( element: EventTarget, error: unknown, ): void { if (error instanceof Error) { dispatchErrorMessageEvent(element, error.message, { ...(error instanceof FrigateCardError && { context: error.context }), }); } } // Facilitates correct typing of event handlers. export interface FrigateCardMessageEventTarget extends EventTarget { addEventListener( event: 'frigate-card:message', listener: (this: FrigateCardMessageEventTarget, ev: CustomEvent) => void, options?: AddEventListenerOptions | boolean, ): void; addEventListener( type: string, callback: EventListenerOrEventListenerObject, options?: AddEventListenerOptions | boolean, ): void; removeEventListener( event: 'frigate-card:message', listener: (this: FrigateCardMessageEventTarget, ev: CustomEvent) => void, options?: boolean | EventListenerOptions, ): void; removeEventListener( type: string, callback: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions, ): void; } declare global { interface HTMLElementTagNameMap { 'frigate-card-progress-indicator': FrigateCardProgressIndicator; 'frigate-card-error-message': FrigateCardErrorMessage; 'frigate-card-message': FrigateCardMessage; } }