diff --git a/package.json b/package.json
index 0313b1fa..50ff7ff0 100644
--- a/package.json
+++ b/package.json
@@ -27,6 +27,7 @@
"@babel/plugin-proposal-class-properties": "^7.14.5",
"@babel/plugin-proposal-decorators": "^7.15.4",
"@rollup/plugin-json": "^4.1.0",
+ "@rollup/plugin-multi-entry": "^4.1.0",
"@typescript-eslint/eslint-plugin": "^4.30.0",
"@typescript-eslint/parser": "^4.30.0",
"eslint": "^7.32.0",
diff --git a/rollup.config.js b/rollup.config.js
index dc2c7e3a..ab52b946 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -6,6 +6,7 @@ import { terser } from 'rollup-plugin-terser';
import serve from 'rollup-plugin-serve';
import json from '@rollup/plugin-json';
import styles from 'rollup-plugin-styles';
+import multi from '@rollup/plugin-multi-entry';
const dev = process.env.ROLLUP_WATCH;
@@ -20,6 +21,7 @@ const serveopts = {
};
const plugins = [
+ multi(),
styles({
modules: false,
// Behavior of inject mode, without actually injecting style
@@ -42,9 +44,9 @@ const plugins = [
export default [
{
- input: 'src/frigate-hass-card.ts',
+ input: ['src/main.ts'],
output: {
- dir: 'dist',
+ file: 'dist/frigate-hass-card.js',
format: 'es',
},
plugins: [...plugins],
diff --git a/src/frigate-hass-card.ts b/src/main.ts
similarity index 95%
rename from src/frigate-hass-card.ts
rename to src/main.ts
index 8a30d99a..bfffab27 100644
--- a/src/frigate-hass-card.ts
+++ b/src/main.ts
@@ -11,6 +11,8 @@ import { customElement, property, query, state } from 'lit/decorators';
import { classMap } from 'lit/directives/class-map.js';
import { until } from 'lit/directives/until.js';
+import { renderMessage, renderErrorMessage, renderProgressIndicator } from './message';
+
import {
HomeAssistant,
LovelaceCardEditor,
@@ -50,9 +52,6 @@ import { MessageBase } from 'home-assistant-js-websocket';
import JSMpeg from '@cycjimmy/jsmpeg-player';
-const URL_TROUBLESHOOTING =
- 'https://github.com/dermotduffy/frigate-hass-card#troubleshooting';
-
// Load dayjs plugin(s).
dayjs.extend(dayjs_custom_parse_format);
@@ -520,28 +519,6 @@ export class FrigateCard extends LitElement {
return this._makeWSRequest(resolvedMediaSchema, request);
}
- // Render an attention grabbing icon.
- protected _renderAttentionIcon(
- icon: string,
- message: string | TemplateResult | null = null,
- ): TemplateResult {
- return html`
-
-
- ${message ? html` ${message}` : ''}
-
-
`;
- }
-
- // Render an embedded error situation.
- protected _renderError(error: string): TemplateResult {
- return this._renderAttentionIcon(
- 'mdi:alert-circle',
- html`${error ? `${error}.` : `${localize('error.unknown_error')}.`}
- ${localize('error.troubleshooting')}.`,
- );
- }
-
// Render Frigate events into a card gallery.
protected async _renderEvents(): Promise {
let parent;
@@ -552,15 +529,15 @@ export class FrigateCard extends LitElement {
parent = await this._browseMediaQuery(this._view.is('clips'));
}
} catch (e: any) {
- return this._renderError(e.message);
+ return renderErrorMessage(e.message);
}
if (this._getFirstTrueMediaChildIndex(parent) == null) {
- return this._renderAttentionIcon(
- this._view.is('clips') ? 'mdi:filmstrip-off' : 'mdi:camera-off',
+ return renderMessage(
this._view.is('clips')
- ? localize('common.no_clips')
- : localize('common.no_snapshots'),
+ ? localize('common.no_clips')
+ : localize('common.no_snapshots'),
+ this._view.is('clips') ? 'mdi:filmstrip-off' : 'mdi:camera-off',
);
}
@@ -625,13 +602,6 @@ export class FrigateCard extends LitElement {
`;
}
- // Render a progress spinner while content loads.
- protected _renderProgressIndicator(): TemplateResult {
- return html`
-
-
`;
- }
-
protected _menuActionHandler(name: string): void {
switch (name) {
case 'frigate':
@@ -802,15 +772,15 @@ export class FrigateCard extends LitElement {
try {
parent = await this._browseMediaQuery(this._view.is('clip'));
} catch (e: any) {
- return this._renderError(e.message);
+ return renderErrorMessage(e.message);
}
childIndex = this._getFirstTrueMediaChildIndex(parent);
if (!parent || !parent.children || childIndex == null) {
- return this._renderAttentionIcon(
- this._view.is('clip') ? 'mdi:filmstrip-off' : 'mdi:camera-off',
+ return renderMessage(
this._view.is('clip')
? localize('common.no_clip')
: localize('common.no_snapshot'),
+ this._view.is('clip') ? 'mdi:filmstrip-off' : 'mdi:camera-off',
);
}
mediaToRender = parent.children[childIndex];
@@ -825,7 +795,7 @@ export class FrigateCard extends LitElement {
const resolvedMedia = await this._resolveMedia(mediaToRender);
if (!mediaToRender || !resolvedMedia) {
// 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);
@@ -998,7 +968,7 @@ export class FrigateCard extends LitElement {
const jsmpeg_url = await this._getJSMPEGURL();
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
@@ -1029,9 +999,9 @@ export class FrigateCard extends LitElement {
// is always rendered (but sometimes hidden).
protected async _renderLiveViewer(): Promise {
if (!this._hass || !(this.config.camera_entity in this._hass.states)) {
- return this._renderAttentionIcon(
- 'mdi:camera-off',
+ return renderMessage(
localize('error.no_live_camera'),
+ 'mdi:camera-off',
);
}
if (this._webrtcElement) {
@@ -1091,13 +1061,13 @@ export class FrigateCard extends LitElement {
${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')
- ? until(this._renderViewer(), this._renderProgressIndicator())
+ ? until(this._renderViewer(), renderProgressIndicator())
: ``}
${this._view.is('live')
- ? until(this._renderLiveViewer(), this._renderProgressIndicator())
+ ? until(this._renderLiveViewer(), renderProgressIndicator())
: ``}
diff --git a/src/message.ts b/src/message.ts
new file mode 100644
index 00000000..34ddd999
--- /dev/null
+++ b/src/message.ts
@@ -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`
+
+
+ ${this.message ? html` ${this.message}` : ''}
+
+
`;
+ }
+
+ 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` ${localize('error.troubleshooting')} .`}
+ .icon=${'mdi:alert-circle'}
+ >
+ `;
+ }
+}
+
+@customElement('frigate-card-progress-indicator')
+export class FrigateCardProgressIndicator extends LitElement {
+ protected render(): TemplateResult {
+ return html`
+
+
`;
+ }
+
+ static get styles(): CSSResultGroup {
+ return unsafeCSS(frigate_card_message_style);
+ }
+}
+
+export function renderErrorMessage(error: string): TemplateResult {
+ return html`
+
+ `;
+}
+
+export function renderMessage(message: string, icon: string): TemplateResult {
+ return html`
+
+ `;
+}
+
+export function renderProgressIndicator(): TemplateResult {
+ return html` `;
+}
diff --git a/src/scss/message.scss b/src/scss/message.scss
new file mode 100644
index 00000000..8752733f
--- /dev/null
+++ b/src/scss/message.scss
@@ -0,0 +1,8 @@
+.message {
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ box-sizing: border-box;
+ padding: 10%;
+}
\ No newline at end of file