Merge pull request #131 from dermotduffy/image-view

Add 'image' view to serve static image / "screensaver"
This commit is contained in:
Dermot Duffy
2021-10-17 08:34:08 -07:00
committed by GitHub
12 changed files with 118 additions and 7 deletions
+5 -2
View File
@@ -23,8 +23,9 @@ A full-featured Frigate Lovelace card:
* Automatic updating to continually show latest clip / snapshot.
* Support for filtering events by zone and label.
* Arbitrary entity access via menu (e.g. motion sensor access).
* Fullscreen event gallery & media viewing.
* Full Lovelace editing support.
* Fullscreen mode.
* Lovelace visual editing support.
* Full [Picture Elements](https://www.home-assistant.io/lovelace/picture-elements/) support.
* Theme friendly.
* **Advanced**: Support for [WebRTC](https://github.com/AlexxIT/WebRTC) live viewing by embedding the WebRTC card.
@@ -100,6 +101,7 @@ lovelace:
| `controls.nextprev` | `thumbnails` | When viewing media, what kind of controls to show to move to the previous/next media item. Acceptable values: `thumbnails`, `chevrons`, `none` . |
| `dimensions.aspect_ratio_mode` | `dynamic` | The aspect ratio mode to use. Acceptable values: `dynamic`, `static`, `unconstrained`. See [aspect ratios](#aspect-ratios) below.|
| `dimensions.aspect_ratio` | `16:9` | The aspect ratio to use. Acceptable values: `<W>:<H>` or `<W>/<H>`. See [aspect ratios](#aspect-ratios) below.|
| `image` | [embedded image](https://www.flickr.com/photos/dianasch/47543120431) | A static image URL for use with the `image` [view](#views).|
<a name="aspect-ratios"></a>
@@ -417,6 +419,7 @@ This card supports several different views.
|`snapshot`|Shows the most recent snapshot for this camera/zone/label.|
|`clips`|Shows the clip gallery for this camera/zone/label.|
|`clip`|Shows the most recent clip for this camera/zone/label.|
|`image`|Shows a static image specified by the `image` parameter, can be used as a discrete default view or a screensaver (via `view_timeout`).|
### Automatic updates in the `clip` or `snapshot` view
+2 -1
View File
@@ -21,12 +21,13 @@
"home-assistant-js-websocket": "^5.11.1",
"lit": "^2.0.2",
"screenfull": "^5.1.0",
"zod": "^3.9.8"
"zod": "^3.10.0-beta.1"
},
"devDependencies": {
"@babel/core": "^7.15.8",
"@babel/plugin-proposal-class-properties": "^7.14.5",
"@babel/plugin-proposal-decorators": "^7.15.8",
"@rollup/plugin-image": "^2.1.1",
"@rollup/plugin-json": "^4.1.0",
"@typescript-eslint/eslint-plugin": "^4.33.0",
"@typescript-eslint/parser": "^4.33.0",
+2
View File
@@ -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 image from '@rollup/plugin-image';
const dev = process.env.ROLLUP_WATCH;
@@ -29,6 +30,7 @@ const plugins = [
includePaths: ['./node_modules/'],
},
}),
image(),
nodeResolve({}),
commonjs(),
typescript(),
+25 -2
View File
@@ -31,6 +31,7 @@ import type {
} from './types';
import { CARD_VERSION, REPO_URL } from './const';
import { FrigateCardElements } from './components/elements';
import { FrigateCardMenu, MENU_HEIGHT } from './components/menu';
import { View } from './view';
import { homeAssistantWSRequest, shouldUpdateBasedOnHass } from './common';
@@ -40,6 +41,7 @@ import { renderMessage, renderProgressIndicator } from './components/message';
import './editor';
import './components/elements';
import './components/gallery';
import './components/image';
import './components/live';
import './components/menu';
import './components/message';
@@ -48,7 +50,6 @@ import './patches/ha-camera-stream';
import './patches/ha-hls-player';
import cardStyle from './scss/card.scss';
import { FrigateCardElements } from './components/elements';
const MEDIA_HEIGHT_CUTOFF = 50;
const MEDIA_WIDTH_CUTOFF = MEDIA_HEIGHT_CUTOFF;
@@ -112,7 +113,7 @@ export class FrigateCard extends LitElement {
// A small cache to avoid needing to create a new list of entities every time
// a hass update arrives.
protected _entitiesToMonitor: string[] | null = null;
protected _entitiesToMonitor: string[] = [];
// Information about the most recently loaded media item.
protected _mediaInfo: MediaLoadInfo | null = null;
@@ -197,6 +198,15 @@ export class FrigateCard extends LitElement {
emphasize: this._view.is('snapshots'),
});
}
if (this.config.menu_buttons?.image ?? false) {
buttons.push({
type: 'internal-menu-icon',
card_action: 'image',
title: localize('menu.image'),
icon: 'mdi:image',
emphasize: this._view.is('image'),
});
}
if ((this.config.menu_buttons?.frigate_ui ?? true) && this.config.frigate_url) {
buttons.push({
type: 'internal-menu-icon',
@@ -357,6 +367,7 @@ export class FrigateCard extends LitElement {
case 'frigate':
this._changeView();
break;
case 'image':
case 'live':
case 'clips':
case 'snapshots':
@@ -625,10 +636,22 @@ export class FrigateCard extends LitElement {
const liveClasses = {
hidden: this.config.live_preload && this._view.view != 'live',
};
const imageClasses = {
hidden: this.config.live_preload && this._view.view != 'image',
};
return html`
<div class="${classMap(pictureElementsClasses)}">
${this._message ? renderMessage(this._message) : ``}
${!this._message && this._view.is('image')
? html` <frigate-card-image
.image=${this.config.image}
class="${classMap(imageClasses)}"
@frigate-card:media-load=${this._mediaLoadHandler}
@frigate-card:message=${this._messageHandler}
>
</frigate-card-image>`
: ``}
${!this._message && this._view.isGalleryView()
? html` <frigate-card-gallery
.hass=${this._hass}
+26
View File
@@ -0,0 +1,26 @@
import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit';
import { customElement, property } from 'lit/decorators';
import { dispatchMediaLoadEvent } from '../common';
import imageStyle from '../scss/image.scss';
import defaultImage from '../images/frigate-bird-in-sky.jpg'
@customElement('frigate-card-image')
export class FrigateCardImage extends LitElement {
@property({ attribute: false })
protected image?: string;
protected render(): TemplateResult | void {
return html` <img
src=${this.image || defaultImage}
@load=${(e) => {
dispatchMediaLoadEvent(this, e);
}}
>`;
}
static get styles(): CSSResultGroup {
return unsafeCSS(imageStyle);
}
}
+1
View File
@@ -1 +1,2 @@
declare module '*.scss';
declare module '*.jpg';
+22
View File
@@ -102,6 +102,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
snapshots: localize('menu.snapshots'),
clip: localize('menu.clip'),
snapshot: localize('menu.snapshot'),
image: localize('menu.image'),
};
const menuModes = {
@@ -338,6 +339,15 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
.configValue=${'dimensions.aspect_ratio'}
@value-changed=${this._valueChanged}
></paper-input>
<paper-input
label=${localize('editor.image')}
prevent-invalid-input
.value=${this._config?.image
? String(this._config?.image)
: ''}
.configValue=${'image'}
@value-changed=${this._valueChanged}
></paper-input>
<ha-formfield
.label=${localize('editor.show_button') +
': ' +
@@ -382,6 +392,18 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
></ha-switch>
</ha-formfield>
<br />
<ha-formfield
.label=${localize('editor.show_button') +
': ' +
localize('menu.image')}
>
<ha-switch
.checked=${this._config?.menu_buttons?.image ?? false}
.configValue=${'menu_buttons.image'}
@change=${this._valueChanged}
></ha-switch>
</ha-formfield>
<br />
<ha-formfield
.label=${localize('editor.show_button') +
': ' +
+23
View File
@@ -0,0 +1,23 @@
# Image Copyright
## frigate-bird-in-sky.jpg
**Link**: https://www.flickr.com/photos/dianasch/47543120431
**Description**: A Frigate bird with a very large wingspan (7 ft) soaring overhead on Sanibel Island, Florida.
**Copyright**: Diana Robinson
**License**: https://creativecommons.org/licenses/by-nc-nd/2.0/
**Modifications**:
* Cropped for 16:9
* Scaled down to 492x277
* Gaussian blur 0.01
* Quality @ 85%
```sh
$ convert -extent 2048x1152 -gravity center -47543120431_b285c45ac8_k.jpg output.jpg
$ mogrify -strip -interlace Plane -gaussian-blur 0.01 -quality 85% -scale 492x277 output.jpg
```
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

+4 -2
View File
@@ -33,7 +33,8 @@
"zone": "Zone",
"label": "Frigate label/object filter (Optional)",
"live_provider": "Live view provider (Optional)",
"live_preload": "Preload live view (Optional)"
"live_preload": "Preload live view",
"image": "Static image URL for image view (Optional)"
},
"menu": {
"frigate": "Frigate Menu / Default View",
@@ -43,7 +44,8 @@
"clip": "Latest Clip",
"snapshot": "Latest Snapshot",
"frigate_ui": "Frigate User Interface",
"fullscreen": "Fullscreen"
"fullscreen": "Fullscreen",
"image": "Static Image"
},
"control": {
"nextprev": "Media Next & Previous Controls",
+5
View File
@@ -0,0 +1,5 @@
img {
max-width: 100%;
display: block;
margin: auto;
}
+3
View File
@@ -26,6 +26,7 @@ export const FRIGATE_CARD_VIEWS = [
'clips',
'snapshot',
'snapshots',
'image'
] as const;
export type FrigateCardView = typeof FRIGATE_CARD_VIEWS[number];
@@ -263,6 +264,7 @@ export const frigateCardConfigSchema = z.object({
.default(180),
live_provider: z.enum(LIVE_PROVIDERS).default('frigate'),
live_preload: z.boolean().default(false),
image: z.string().optional(),
webrtc: z
.object({
entity: z.string().optional(),
@@ -280,6 +282,7 @@ export const frigateCardConfigSchema = z.object({
live: z.boolean().default(true),
clips: z.boolean().default(true),
snapshots: z.boolean().default(true),
image: z.boolean().default(false),
frigate_ui: z.boolean().default(true),
fullscreen: z.boolean().default(true),
})