Allow minimum gallery columns to be configured.

This commit is contained in:
Dermot Duffy
2022-01-18 21:50:10 -08:00
parent 585a8ebb6a
commit 317c5a0b89
8 changed files with 126 additions and 20 deletions
+1
View File
@@ -324,6 +324,7 @@ event_gallery:
| Option | Default | Overridable | Description | | Option | Default | Overridable | Description |
| - | - | - | - | | - | - | - | - |
| `min_columns` | `5` | :heavy_multiplication_x: | The minimum number of columns to show in the gallery -- smaller values will render fewer but larger thumbnail columns. Thumbnails will never be stretched beyond their intrinsic size (typically `175px`). All available space (in normal mode, or fullscreen mode) will be occupied by the gallery, so more columns than `min_columns` will often be rendered if space allows for more full-sized thumbnails. For normal sized Lovelace cards (`492px` wide), this typically means there'll never be fewer than 3 columns (as otherwise the thumbnails would need to stretch beyond their actual size). |
| `actions` | | :heavy_multiplication_x: | Actions to use for all views that use the `event_gallery` (e.g. `clips`, `snapshots`). See [actions](#actions) below.| | `actions` | | :heavy_multiplication_x: | Actions to use for all views that use the `event_gallery` (e.g. `clips`, `snapshots`). See [actions](#actions) below.|
### Image Options ### Image Options
+1
View File
@@ -1195,6 +1195,7 @@ export class FrigateCard extends LitElement {
.hass=${this._hass} .hass=${this._hass}
.view=${this._view} .view=${this._view}
.cameraConfig=${cameraConfig} .cameraConfig=${cameraConfig}
.galleryConfig=${this._getConfig().event_gallery}
> >
</frigate-card-gallery>` </frigate-card-gallery>`
: ``} : ``}
+48 -9
View File
@@ -4,7 +4,12 @@ import { HomeAssistant } from 'custom-card-helpers';
import { customElement, property, state } from 'lit/decorators.js'; import { customElement, property, state } from 'lit/decorators.js';
import { styleMap } from 'lit/directives/style-map.js'; import { styleMap } from 'lit/directives/style-map.js';
import type { CameraConfig, ExtendedHomeAssistant } from '../types.js'; import {
CameraConfig,
ExtendedHomeAssistant,
GalleryConfig,
frigateCardConfigDefaults,
} from '../types.js';
import { BrowseMediaUtil } from '../browse-media-util.js'; import { BrowseMediaUtil } from '../browse-media-util.js';
import { View } from '../view.js'; import { View } from '../view.js';
import { renderProgressIndicator } from './message.js'; import { renderProgressIndicator } from './message.js';
@@ -12,7 +17,6 @@ import { renderProgressIndicator } from './message.js';
import galleryStyle from '../scss/gallery.scss'; import galleryStyle from '../scss/gallery.scss';
const MAX_THUMBNAIL_WIDTH = 175; const MAX_THUMBNAIL_WIDTH = 175;
const DEFAULT_COLUMNS = 5;
@customElement('frigate-card-gallery') @customElement('frigate-card-gallery')
export class FrigateCardGallery extends LitElement { export class FrigateCardGallery extends LitElement {
@@ -25,6 +29,9 @@ export class FrigateCardGallery extends LitElement {
@property({ attribute: false }) @property({ attribute: false })
protected cameraConfig?: CameraConfig; protected cameraConfig?: CameraConfig;
@property({ attribute: false })
protected galleryConfig?: GalleryConfig;
/** /**
* Master render method. * Master render method.
* @returns A rendered template. * @returns A rendered template.
@@ -55,7 +62,11 @@ export class FrigateCardGallery extends LitElement {
} }
return html` return html`
<frigate-card-gallery-core .hass=${this.hass} .view=${this.view}> <frigate-card-gallery-core
.hass=${this.hass}
.view=${this.view}
.galleryConfig=${this.galleryConfig}
>
</frigate-card-gallery-core> </frigate-card-gallery-core>
`; `;
} }
@@ -76,33 +87,50 @@ export class FrigateCardGalleryCore extends LitElement {
@property({ attribute: false }) @property({ attribute: false })
protected view?: Readonly<View>; protected view?: Readonly<View>;
@property({ attribute: false })
protected galleryConfig?: GalleryConfig;
protected _resizeObserver: ResizeObserver; protected _resizeObserver: ResizeObserver;
@state() @state()
protected _columns = DEFAULT_COLUMNS; protected _columns = frigateCardConfigDefaults.event_gallery.min_columns;
constructor() { constructor() {
super(); super();
this._resizeObserver = new ResizeObserver(this._resizeHandler.bind(this)); this._resizeObserver = new ResizeObserver(this._resizeHandler.bind(this));
} }
/**
* Component connected callback.
*/
connectedCallback(): void { connectedCallback(): void {
super.connectedCallback(); super.connectedCallback();
this._resizeObserver?.observe(this); this._resizeObserver?.observe(this);
} }
/**
* Component disconnected callback.
*/
disconnectedCallback(): void { disconnectedCallback(): void {
this._resizeObserver.disconnect(); this._resizeObserver.disconnect();
super.disconnectedCallback(); super.disconnectedCallback();
} }
/**
* Handle gallery resize.
*/
protected _resizeHandler(): void { protected _resizeHandler(): void {
this._columns = Math.max( this._columns = Math.max(
DEFAULT_COLUMNS, this.galleryConfig?.min_columns ??
frigateCardConfigDefaults.event_gallery.min_columns,
Math.ceil(this.clientWidth / MAX_THUMBNAIL_WIDTH), Math.ceil(this.clientWidth / MAX_THUMBNAIL_WIDTH),
); );
} }
/**
* Master render method.
* @returns A rendered template.
*/
protected render(): TemplateResult | void { protected render(): TemplateResult | void {
if ( if (
!this.hass || !this.hass ||
@@ -114,14 +142,22 @@ export class FrigateCardGalleryCore extends LitElement {
return html``; return html``;
} }
const styles = { const itemStyle = {
// Controls the number of columns in the gallery (allows for 5px gutter). // Controls the number of columns in the gallery (allows for 5px gutter).
width: `calc(${100 / this._columns}% - 5.25px)`, width: `calc(${100 / this._columns}% - 5.25px)`,
}; };
const folderStyle = {
// Values derived from experimentation on typical Lovelace card sizes.
'font-size': `${Math.min(
1.1,
(0.6 * (this.clientWidth / this._columns)) / 50.0,
)}em`,
};
return html` <ul class="mdc-image-list frigate-card-gallery"> return html` <ul class="mdc-image-list frigate-card-gallery">
${this.view && this.view.previous ${this.view && this.view.previous
? html`<li class="mdc-image-list__item" style="${styleMap(styles)}"> ? html`<li class="mdc-image-list__item" style="${styleMap(itemStyle)}">
<div class="mdc-image-list__image-aspect-container"> <div class="mdc-image-list__image-aspect-container">
<div class="mdc-image-list__image"> <div class="mdc-image-list__image">
<ha-card <ha-card
@@ -141,7 +177,7 @@ export class FrigateCardGalleryCore extends LitElement {
: ''} : ''}
${this.view.target.children.map( ${this.view.target.children.map(
(child, index) => (child, index) =>
html` <li class="mdc-image-list__item" style="${styleMap(styles)}"> html` <li class="mdc-image-list__item" style="${styleMap(itemStyle)}">
<div class="mdc-image-list__image-aspect-container"> <div class="mdc-image-list__image-aspect-container">
${child.can_expand ${child.can_expand
? html`<div class="mdc-image-list__image"> ? html`<div class="mdc-image-list__image">
@@ -159,7 +195,7 @@ export class FrigateCardGalleryCore extends LitElement {
outlined="" outlined=""
class="frigate-card-gallery-folder" class="frigate-card-gallery-folder"
> >
<div>${child.title}</div> <div style="${styleMap(folderStyle)}">${child.title}</div>
</ha-card> </ha-card>
</div>` </div>`
: child.thumbnail : child.thumbnail
@@ -187,6 +223,9 @@ export class FrigateCardGalleryCore extends LitElement {
</ul>`; </ul>`;
} }
/**
* Get styles.
*/
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return unsafeCSS(galleryStyle); return unsafeCSS(galleryStyle);
} }
+3
View File
@@ -24,6 +24,9 @@ export const CONF_VIEW_TIMEOUT = `${CONF_VIEW}.timeout` as const;
export const CONF_VIEW_UPDATE_FORCE = `${CONF_VIEW}.update_force` as const; export const CONF_VIEW_UPDATE_FORCE = `${CONF_VIEW}.update_force` as const;
export const CONF_VIEW_UPDATE_ENTITIES = `${CONF_VIEW}.update_entities` as const; export const CONF_VIEW_UPDATE_ENTITIES = `${CONF_VIEW}.update_entities` as const;
export const CONF_EVENT_GALLERY = 'event_gallery' as const;
export const CONF_EVENT_GALLERY_MIN_COLUMNS = `${CONF_EVENT_GALLERY}.min_columns` as const;
export const CONF_EVENT_VIEWER = 'event_viewer' as const; export const CONF_EVENT_VIEWER = 'event_viewer' as const;
export const CONF_EVENT_VIEWER_AUTOPLAY_CLIP = export const CONF_EVENT_VIEWER_AUTOPLAY_CLIP =
`${CONF_EVENT_VIEWER}.autoplay_clip` as const; `${CONF_EVENT_VIEWER}.autoplay_clip` as const;
+50 -3
View File
@@ -27,6 +27,7 @@ import {
CONF_CAMERAS_ARRAY_ZONE, CONF_CAMERAS_ARRAY_ZONE,
CONF_DIMENSIONS_ASPECT_RATIO, CONF_DIMENSIONS_ASPECT_RATIO,
CONF_DIMENSIONS_ASPECT_RATIO_MODE, CONF_DIMENSIONS_ASPECT_RATIO_MODE,
CONF_EVENT_GALLERY_MIN_COLUMNS,
CONF_EVENT_VIEWER_AUTOPLAY_CLIP, CONF_EVENT_VIEWER_AUTOPLAY_CLIP,
CONF_EVENT_VIEWER_CONTROLS_NEXT_PREVIOUS_SIZE, CONF_EVENT_VIEWER_CONTROLS_NEXT_PREVIOUS_SIZE,
CONF_EVENT_VIEWER_CONTROLS_NEXT_PREVIOUS_STYLE, CONF_EVENT_VIEWER_CONTROLS_NEXT_PREVIOUS_STYLE,
@@ -125,6 +126,12 @@ const options: EditorOptions = {
secondary: localize('editor.event_viewer_secondary'), secondary: localize('editor.event_viewer_secondary'),
show: false, show: false,
}, },
event_gallery: {
icon: 'grid',
name: localize('editor.event_gallery'),
secondary: localize('editor.event_gallery_secondary'),
show: false,
},
image: { image: {
icon: 'image', icon: 'image',
name: localize('editor.image'), name: localize('editor.image'),
@@ -254,6 +261,30 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
`; `;
} }
protected _renderSlider(
configPath: string,
valueDefault: number,
icon: string,
min: number,
max: number,
): TemplateResult | void {
if (!this._config) {
return;
}
const value = Number(getConfigValue(this._config, configPath, valueDefault));
return html`<ha-labeled-slider
caption=${this._getLabel(configPath)}
icon=${icon}
max=${max}
min=${min}
?pin=${true}
value=${isNaN(value) ? valueDefault : value}
@value-changed=${this._valueChangedHandler.bind(this)}
.configValue=${configPath}
>
</ha-labeled-slider>`;
}
/** /**
* Render a camera header. * Render a camera header.
* @param cameraIndex The index of the camera to edit/add. * @param cameraIndex The index of the camera to edit/add.
@@ -506,10 +537,12 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
return html``; return html``;
} }
// The climate more-info has ha-switch and paper-dropdown-menu elements that // The climate more-info loads ha-switch and paper-dropdown-menu.
// are lazy loaded unless explicitly loaded via climate here.
this._helpers.importMoreInfoControl('climate'); this._helpers.importMoreInfoControl('climate');
// The light more-info loads ha-labeled-slider.
this._helpers.importMoreInfoControl('light');
const cameraEntities = this._getEntities('camera'); const cameraEntities = this._getEntities('camera');
const viewModes = { const viewModes = {
@@ -705,6 +738,18 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
</div> </div>
` `
: ''} : ''}
${this._renderOptionSetHeader('event_gallery')}
${options.event_gallery.show
? html` <div class="values">
${this._renderSlider(
CONF_EVENT_GALLERY_MIN_COLUMNS,
defaults.event_gallery.min_columns,
"mdi:view-column",
1,
10,
)}
</div>`
: ''}
${this._renderOptionSetHeader('event_viewer')} ${this._renderOptionSetHeader('event_viewer')}
${options.event_viewer.show ${options.event_viewer.show
? html` <div class="values"> ? html` <div class="values">
@@ -822,8 +867,10 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
let value; let value;
if ('checked' in target) { if ('checked' in target) {
value = target.checked; value = target.checked;
} else { } else if (typeof target.value === 'string') {
value = target.value?.trim(); value = target.value?.trim();
} else {
value = target.value;
} }
const key: string = target.configValue; const key: string = target.configValue;
if (!key) { if (!key) {
+6 -1
View File
@@ -44,6 +44,9 @@
"timeout": "View timeout secs (before returning to default, 0=never)", "timeout": "View timeout secs (before returning to default, 0=never)",
"update_force": "Force card updates (ignore media playing / interaction)" "update_force": "Force card updates (ignore media playing / interaction)"
}, },
"event_gallery": {
"min_columns": "Minimum number of columns"
},
"event_viewer": { "event_viewer": {
"autoplay_clip": "Autoplay clips", "autoplay_clip": "Autoplay clips",
"draggable": "Event Viewer can be dragged/swiped", "draggable": "Event Viewer can be dragged/swiped",
@@ -144,8 +147,10 @@
"menu_secondary": "Menu look & feel options", "menu_secondary": "Menu look & feel options",
"live": "Live", "live": "Live",
"live_secondary": "Live camera view options", "live_secondary": "Live camera view options",
"event_gallery": "Event gallery",
"event_gallery_secondary": "Snapshots & clips gallery options",
"event_viewer": "Event viewer", "event_viewer": "Event viewer",
"event_viewer_secondary": "Snapshots & clips gallery options", "event_viewer_secondary": "Snapshots & clips viewer options",
"image": "Image", "image": "Image",
"image_secondary": "Static image view options", "image_secondary": "Static image view options",
"dimensions": "Dimensions", "dimensions": "Dimensions",
+1
View File
@@ -42,4 +42,5 @@ ha-card.frigate-card-gallery-folder {
background-color: var(--primary-background-color, black); background-color: var(--primary-background-color, black);
padding: 10px; padding: 10px;
height: 100%; height: 100%;
line-height: 1;
} }
+15 -6
View File
@@ -611,8 +611,17 @@ export type ViewerConfig = z.infer<typeof viewerConfigSchema>;
/** /**
* Event gallery configuration section (clips, snapshots). * Event gallery configuration section (clips, snapshots).
*/ */
const galleryConfigDefault = {
min_columns: 5,
};
const galleryConfigSchema = actionsSchema.optional(); const galleryConfigSchema = z
.object({
min_columns: z.number().min(1).default(galleryConfigDefault.min_columns),
})
.merge(actionsSchema)
.default(galleryConfigDefault);
export type GalleryConfig = z.infer<typeof galleryConfigSchema>;
/** /**
* Dimensions configuration section. * Dimensions configuration section.
@@ -645,11 +654,10 @@ const dimensionsConfigSchema = z
*/ */
// Strip all defaults from the override schemas, to ensure values are only what // Strip all defaults from the override schemas, to ensure values are only what
// the user has specified. // the user has specified.
const overrideConfigurationSchema = const overrideConfigurationSchema = z.object({
z.object({ live: deepRemoveDefaults(liveOverridableConfigSchema).optional(),
live: deepRemoveDefaults(liveOverridableConfigSchema).optional(), menu: deepRemoveDefaults(menuConfigSchema).optional(),
menu: deepRemoveDefaults(menuConfigSchema).optional(), });
});
export type OverrideConfigurationKey = keyof z.infer<typeof overrideConfigurationSchema>; export type OverrideConfigurationKey = keyof z.infer<typeof overrideConfigurationSchema>;
const overridesSchema = z const overridesSchema = z
@@ -701,6 +709,7 @@ export const frigateCardConfigDefaults = {
menu: menuConfigDefault, menu: menuConfigDefault,
live: liveConfigDefault, live: liveConfigDefault,
event_viewer: viewerConfigDefault, event_viewer: viewerConfigDefault,
event_gallery: galleryConfigDefault,
}; };
const menuButtonSchema = z.union([ const menuButtonSchema = z.union([