Componentize messages, error messages and progress indicator.
This commit is contained in:
@@ -27,6 +27,7 @@
|
|||||||
"@babel/plugin-proposal-class-properties": "^7.14.5",
|
"@babel/plugin-proposal-class-properties": "^7.14.5",
|
||||||
"@babel/plugin-proposal-decorators": "^7.15.4",
|
"@babel/plugin-proposal-decorators": "^7.15.4",
|
||||||
"@rollup/plugin-json": "^4.1.0",
|
"@rollup/plugin-json": "^4.1.0",
|
||||||
|
"@rollup/plugin-multi-entry": "^4.1.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.30.0",
|
"@typescript-eslint/eslint-plugin": "^4.30.0",
|
||||||
"@typescript-eslint/parser": "^4.30.0",
|
"@typescript-eslint/parser": "^4.30.0",
|
||||||
"eslint": "^7.32.0",
|
"eslint": "^7.32.0",
|
||||||
|
|||||||
+4
-2
@@ -6,6 +6,7 @@ import { terser } from 'rollup-plugin-terser';
|
|||||||
import serve from 'rollup-plugin-serve';
|
import serve from 'rollup-plugin-serve';
|
||||||
import json from '@rollup/plugin-json';
|
import json from '@rollup/plugin-json';
|
||||||
import styles from 'rollup-plugin-styles';
|
import styles from 'rollup-plugin-styles';
|
||||||
|
import multi from '@rollup/plugin-multi-entry';
|
||||||
|
|
||||||
const dev = process.env.ROLLUP_WATCH;
|
const dev = process.env.ROLLUP_WATCH;
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ const serveopts = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const plugins = [
|
const plugins = [
|
||||||
|
multi(),
|
||||||
styles({
|
styles({
|
||||||
modules: false,
|
modules: false,
|
||||||
// Behavior of inject mode, without actually injecting style
|
// Behavior of inject mode, without actually injecting style
|
||||||
@@ -42,9 +44,9 @@ const plugins = [
|
|||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
input: 'src/frigate-hass-card.ts',
|
input: ['src/main.ts'],
|
||||||
output: {
|
output: {
|
||||||
dir: 'dist',
|
file: 'dist/frigate-hass-card.js',
|
||||||
format: 'es',
|
format: 'es',
|
||||||
},
|
},
|
||||||
plugins: [...plugins],
|
plugins: [...plugins],
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import { customElement, property, query, state } from 'lit/decorators';
|
|||||||
import { classMap } from 'lit/directives/class-map.js';
|
import { classMap } from 'lit/directives/class-map.js';
|
||||||
import { until } from 'lit/directives/until.js';
|
import { until } from 'lit/directives/until.js';
|
||||||
|
|
||||||
|
import { renderMessage, renderErrorMessage, renderProgressIndicator } from './message';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
HomeAssistant,
|
HomeAssistant,
|
||||||
LovelaceCardEditor,
|
LovelaceCardEditor,
|
||||||
@@ -50,9 +52,6 @@ import { MessageBase } from 'home-assistant-js-websocket';
|
|||||||
|
|
||||||
import JSMpeg from '@cycjimmy/jsmpeg-player';
|
import JSMpeg from '@cycjimmy/jsmpeg-player';
|
||||||
|
|
||||||
const URL_TROUBLESHOOTING =
|
|
||||||
'https://github.com/dermotduffy/frigate-hass-card#troubleshooting';
|
|
||||||
|
|
||||||
// Load dayjs plugin(s).
|
// Load dayjs plugin(s).
|
||||||
dayjs.extend(dayjs_custom_parse_format);
|
dayjs.extend(dayjs_custom_parse_format);
|
||||||
|
|
||||||
@@ -520,28 +519,6 @@ export class FrigateCard extends LitElement {
|
|||||||
return this._makeWSRequest(resolvedMediaSchema, request);
|
return this._makeWSRequest(resolvedMediaSchema, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render an attention grabbing icon.
|
|
||||||
protected _renderAttentionIcon(
|
|
||||||
icon: string,
|
|
||||||
message: string | TemplateResult | null = null,
|
|
||||||
): TemplateResult {
|
|
||||||
return html` <div class="attention">
|
|
||||||
<span>
|
|
||||||
<ha-icon icon="${icon}"> </ha-icon>
|
|
||||||
${message ? html` ${message}` : ''}
|
|
||||||
</span>
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render an embedded error situation.
|
|
||||||
protected _renderError(error: string): TemplateResult {
|
|
||||||
return this._renderAttentionIcon(
|
|
||||||
'mdi:alert-circle',
|
|
||||||
html`${error ? `${error}.` : `${localize('error.unknown_error')}.`}
|
|
||||||
<a href="${URL_TROUBLESHOOTING}">${localize('error.troubleshooting')}</a>.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render Frigate events into a card gallery.
|
// Render Frigate events into a card gallery.
|
||||||
protected async _renderEvents(): Promise<TemplateResult> {
|
protected async _renderEvents(): Promise<TemplateResult> {
|
||||||
let parent;
|
let parent;
|
||||||
@@ -552,15 +529,15 @@ export class FrigateCard extends LitElement {
|
|||||||
parent = await this._browseMediaQuery(this._view.is('clips'));
|
parent = await this._browseMediaQuery(this._view.is('clips'));
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
return this._renderError(e.message);
|
return renderErrorMessage(e.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._getFirstTrueMediaChildIndex(parent) == null) {
|
if (this._getFirstTrueMediaChildIndex(parent) == null) {
|
||||||
return this._renderAttentionIcon(
|
return renderMessage(
|
||||||
this._view.is('clips') ? 'mdi:filmstrip-off' : 'mdi:camera-off',
|
|
||||||
this._view.is('clips')
|
this._view.is('clips')
|
||||||
? localize('common.no_clips')
|
? localize('common.no_clips')
|
||||||
: localize('common.no_snapshots'),
|
: localize('common.no_snapshots'),
|
||||||
|
this._view.is('clips') ? 'mdi:filmstrip-off' : 'mdi:camera-off',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -625,13 +602,6 @@ export class FrigateCard extends LitElement {
|
|||||||
</ul>`;
|
</ul>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render a progress spinner while content loads.
|
|
||||||
protected _renderProgressIndicator(): TemplateResult {
|
|
||||||
return html` <div class="attention">
|
|
||||||
<ha-circular-progress active="true" size="large"></ha-circular-progress>
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected _menuActionHandler(name: string): void {
|
protected _menuActionHandler(name: string): void {
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case 'frigate':
|
case 'frigate':
|
||||||
@@ -802,15 +772,15 @@ export class FrigateCard extends LitElement {
|
|||||||
try {
|
try {
|
||||||
parent = await this._browseMediaQuery(this._view.is('clip'));
|
parent = await this._browseMediaQuery(this._view.is('clip'));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
return this._renderError(e.message);
|
return renderErrorMessage(e.message);
|
||||||
}
|
}
|
||||||
childIndex = this._getFirstTrueMediaChildIndex(parent);
|
childIndex = this._getFirstTrueMediaChildIndex(parent);
|
||||||
if (!parent || !parent.children || childIndex == null) {
|
if (!parent || !parent.children || childIndex == null) {
|
||||||
return this._renderAttentionIcon(
|
return renderMessage(
|
||||||
this._view.is('clip') ? 'mdi:filmstrip-off' : 'mdi:camera-off',
|
|
||||||
this._view.is('clip')
|
this._view.is('clip')
|
||||||
? localize('common.no_clip')
|
? localize('common.no_clip')
|
||||||
: localize('common.no_snapshot'),
|
: localize('common.no_snapshot'),
|
||||||
|
this._view.is('clip') ? 'mdi:filmstrip-off' : 'mdi:camera-off',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
mediaToRender = parent.children[childIndex];
|
mediaToRender = parent.children[childIndex];
|
||||||
@@ -825,7 +795,7 @@ export class FrigateCard extends LitElement {
|
|||||||
const resolvedMedia = await this._resolveMedia(mediaToRender);
|
const resolvedMedia = await this._resolveMedia(mediaToRender);
|
||||||
if (!mediaToRender || !resolvedMedia) {
|
if (!mediaToRender || !resolvedMedia) {
|
||||||
// Home Assistant could not resolve media item.
|
// Home Assistant could not resolve media item.
|
||||||
return this._renderError(localize('error.could_not_resolve'));
|
return renderErrorMessage(localize('error.could_not_resolve'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const neighbors = this._getMediaNeighbors(parent, childIndex);
|
const neighbors = this._getMediaNeighbors(parent, childIndex);
|
||||||
@@ -998,7 +968,7 @@ export class FrigateCard extends LitElement {
|
|||||||
const jsmpeg_url = await this._getJSMPEGURL();
|
const jsmpeg_url = await this._getJSMPEGURL();
|
||||||
|
|
||||||
if (!jsmpeg_url) {
|
if (!jsmpeg_url) {
|
||||||
return this._renderError('Could not retrieve or sign JSMPEG websocket path');
|
return renderErrorMessage('Could not retrieve or sign JSMPEG websocket path');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the html canvas node only after the JSMPEG video has loaded and
|
// Return the html canvas node only after the JSMPEG video has loaded and
|
||||||
@@ -1029,9 +999,9 @@ export class FrigateCard extends LitElement {
|
|||||||
// is always rendered (but sometimes hidden).
|
// is always rendered (but sometimes hidden).
|
||||||
protected async _renderLiveViewer(): Promise<TemplateResult> {
|
protected async _renderLiveViewer(): Promise<TemplateResult> {
|
||||||
if (!this._hass || !(this.config.camera_entity in this._hass.states)) {
|
if (!this._hass || !(this.config.camera_entity in this._hass.states)) {
|
||||||
return this._renderAttentionIcon(
|
return renderMessage(
|
||||||
'mdi:camera-off',
|
|
||||||
localize('error.no_live_camera'),
|
localize('error.no_live_camera'),
|
||||||
|
'mdi:camera-off',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (this._webrtcElement) {
|
if (this._webrtcElement) {
|
||||||
@@ -1091,13 +1061,13 @@ export class FrigateCard extends LitElement {
|
|||||||
<div class="container_16_9 outer">
|
<div class="container_16_9 outer">
|
||||||
<div class="frigate-card-contents">
|
<div class="frigate-card-contents">
|
||||||
${this._view.is('clips') || this._view.is('snapshots')
|
${this._view.is('clips') || this._view.is('snapshots')
|
||||||
? until(this._renderEvents(), this._renderProgressIndicator())
|
? until(this._renderEvents(), renderProgressIndicator())
|
||||||
: ``}
|
: ``}
|
||||||
${this._view.is('clip') || this._view.is('snapshot')
|
${this._view.is('clip') || this._view.is('snapshot')
|
||||||
? until(this._renderViewer(), this._renderProgressIndicator())
|
? until(this._renderViewer(), renderProgressIndicator())
|
||||||
: ``}
|
: ``}
|
||||||
${this._view.is('live')
|
${this._view.is('live')
|
||||||
? until(this._renderLiveViewer(), this._renderProgressIndicator())
|
? until(this._renderLiveViewer(), renderProgressIndicator())
|
||||||
: ``}
|
: ``}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit';
|
||||||
|
import { customElement, property } from 'lit/decorators';
|
||||||
|
import { localize } from './localize/localize';
|
||||||
|
|
||||||
|
import frigate_card_message_style from './scss/message.scss';
|
||||||
|
|
||||||
|
const URL_TROUBLESHOOTING =
|
||||||
|
'https://github.com/dermotduffy/frigate-hass-card#troubleshooting';
|
||||||
|
|
||||||
|
@customElement('frigate-card-message')
|
||||||
|
export class FrigateCardMessage extends LitElement {
|
||||||
|
@property({ attribute: false })
|
||||||
|
protected message = '';
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
protected icon = 'mdi:information-outline';
|
||||||
|
|
||||||
|
// Render the menu.
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html` <div class="message">
|
||||||
|
<span>
|
||||||
|
<ha-icon icon="${this.icon}"> </ha-icon>
|
||||||
|
${this.message ? html` ${this.message}` : ''}
|
||||||
|
</span>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return unsafeCSS(frigate_card_message_style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement('frigate-card-error-message')
|
||||||
|
export class FrigateCardErrorMessage extends LitElement {
|
||||||
|
@property({ attribute: false })
|
||||||
|
protected error = '';
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html` <frigate-card-message
|
||||||
|
.message=${html` ${this.error}.
|
||||||
|
<a href="${URL_TROUBLESHOOTING}"> ${localize('error.troubleshooting')} </a>.`}
|
||||||
|
.icon=${'mdi:alert-circle'}
|
||||||
|
>
|
||||||
|
</frigate-card-message>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement('frigate-card-progress-indicator')
|
||||||
|
export class FrigateCardProgressIndicator extends LitElement {
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html` <div class="message">
|
||||||
|
<ha-circular-progress active="true" size="large"> </ha-circular-progress>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return unsafeCSS(frigate_card_message_style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function renderErrorMessage(error: string): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<frigate-card-error-message .error=${error}></frigate-card-error-message>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function renderMessage(message: string, icon: string): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<frigate-card-message .message=${message} .icon=${icon}></frigate-card-message>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function renderProgressIndicator(): TemplateResult {
|
||||||
|
return html` <frigate-card-progress-indicator> </frigate-card-progress-indicator> `;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
.message {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 10%;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user