From 317c5a0b89284054ed91495697880de7c139a7e4 Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Tue, 18 Jan 2022 21:50:10 -0800 Subject: [PATCH] Allow minimum gallery columns to be configured. --- README.md | 1 + src/card.ts | 1 + src/components/gallery.ts | 57 ++++++++++++++++++++++++++++------ src/const.ts | 3 ++ src/editor.ts | 53 +++++++++++++++++++++++++++++-- src/localize/languages/en.json | 7 ++++- src/scss/gallery.scss | 1 + src/types.ts | 23 +++++++++----- 8 files changed, 126 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 9ba445d3..7982bdd7 100644 --- a/README.md +++ b/README.md @@ -324,6 +324,7 @@ event_gallery: | 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.| ### Image Options diff --git a/src/card.ts b/src/card.ts index c9a1bd87..7232cbe3 100644 --- a/src/card.ts +++ b/src/card.ts @@ -1195,6 +1195,7 @@ export class FrigateCard extends LitElement { .hass=${this._hass} .view=${this._view} .cameraConfig=${cameraConfig} + .galleryConfig=${this._getConfig().event_gallery} > ` : ``} diff --git a/src/components/gallery.ts b/src/components/gallery.ts index 278dea11..fe64f56d 100644 --- a/src/components/gallery.ts +++ b/src/components/gallery.ts @@ -4,7 +4,12 @@ import { HomeAssistant } from 'custom-card-helpers'; import { customElement, property, state } from 'lit/decorators.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 { View } from '../view.js'; import { renderProgressIndicator } from './message.js'; @@ -12,7 +17,6 @@ import { renderProgressIndicator } from './message.js'; import galleryStyle from '../scss/gallery.scss'; const MAX_THUMBNAIL_WIDTH = 175; -const DEFAULT_COLUMNS = 5; @customElement('frigate-card-gallery') export class FrigateCardGallery extends LitElement { @@ -25,6 +29,9 @@ export class FrigateCardGallery extends LitElement { @property({ attribute: false }) protected cameraConfig?: CameraConfig; + @property({ attribute: false }) + protected galleryConfig?: GalleryConfig; + /** * Master render method. * @returns A rendered template. @@ -55,7 +62,11 @@ export class FrigateCardGallery extends LitElement { } return html` - + `; } @@ -76,33 +87,50 @@ export class FrigateCardGalleryCore extends LitElement { @property({ attribute: false }) protected view?: Readonly; + @property({ attribute: false }) + protected galleryConfig?: GalleryConfig; + protected _resizeObserver: ResizeObserver; @state() - protected _columns = DEFAULT_COLUMNS; + protected _columns = frigateCardConfigDefaults.event_gallery.min_columns; constructor() { super(); this._resizeObserver = new ResizeObserver(this._resizeHandler.bind(this)); } + /** + * Component connected callback. + */ connectedCallback(): void { super.connectedCallback(); this._resizeObserver?.observe(this); } + /** + * Component disconnected callback. + */ disconnectedCallback(): void { this._resizeObserver.disconnect(); super.disconnectedCallback(); } + /** + * Handle gallery resize. + */ protected _resizeHandler(): void { this._columns = Math.max( - DEFAULT_COLUMNS, + this.galleryConfig?.min_columns ?? + frigateCardConfigDefaults.event_gallery.min_columns, Math.ceil(this.clientWidth / MAX_THUMBNAIL_WIDTH), ); } + /** + * Master render method. + * @returns A rendered template. + */ protected render(): TemplateResult | void { if ( !this.hass || @@ -114,14 +142,22 @@ export class FrigateCardGalleryCore extends LitElement { return html``; } - const styles = { + const itemStyle = { // Controls the number of columns in the gallery (allows for 5px gutter). 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` `; } + /** + * Get styles. + */ static get styles(): CSSResultGroup { return unsafeCSS(galleryStyle); } diff --git a/src/const.ts b/src/const.ts index a270cc8e..5b70c54b 100644 --- a/src/const.ts +++ b/src/const.ts @@ -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_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_AUTOPLAY_CLIP = `${CONF_EVENT_VIEWER}.autoplay_clip` as const; diff --git a/src/editor.ts b/src/editor.ts index 63c0d2a8..7241c481 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -27,6 +27,7 @@ import { CONF_CAMERAS_ARRAY_ZONE, CONF_DIMENSIONS_ASPECT_RATIO, CONF_DIMENSIONS_ASPECT_RATIO_MODE, + CONF_EVENT_GALLERY_MIN_COLUMNS, CONF_EVENT_VIEWER_AUTOPLAY_CLIP, CONF_EVENT_VIEWER_CONTROLS_NEXT_PREVIOUS_SIZE, CONF_EVENT_VIEWER_CONTROLS_NEXT_PREVIOUS_STYLE, @@ -125,6 +126,12 @@ const options: EditorOptions = { secondary: localize('editor.event_viewer_secondary'), show: false, }, + event_gallery: { + icon: 'grid', + name: localize('editor.event_gallery'), + secondary: localize('editor.event_gallery_secondary'), + show: false, + }, image: { icon: '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` + `; + } + /** * Render a camera header. * @param cameraIndex The index of the camera to edit/add. @@ -506,10 +537,12 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor return html``; } - // The climate more-info has ha-switch and paper-dropdown-menu elements that - // are lazy loaded unless explicitly loaded via climate here. + // The climate more-info loads ha-switch and paper-dropdown-menu. this._helpers.importMoreInfoControl('climate'); + // The light more-info loads ha-labeled-slider. + this._helpers.importMoreInfoControl('light'); + const cameraEntities = this._getEntities('camera'); const viewModes = { @@ -705,6 +738,18 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor ` : ''} + ${this._renderOptionSetHeader('event_gallery')} + ${options.event_gallery.show + ? html`
+ ${this._renderSlider( + CONF_EVENT_GALLERY_MIN_COLUMNS, + defaults.event_gallery.min_columns, + "mdi:view-column", + 1, + 10, + )} +
` + : ''} ${this._renderOptionSetHeader('event_viewer')} ${options.event_viewer.show ? html`
@@ -822,8 +867,10 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor let value; if ('checked' in target) { value = target.checked; - } else { + } else if (typeof target.value === 'string') { value = target.value?.trim(); + } else { + value = target.value; } const key: string = target.configValue; if (!key) { diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json index f9f7b294..499ec34d 100644 --- a/src/localize/languages/en.json +++ b/src/localize/languages/en.json @@ -44,6 +44,9 @@ "timeout": "View timeout secs (before returning to default, 0=never)", "update_force": "Force card updates (ignore media playing / interaction)" }, + "event_gallery": { + "min_columns": "Minimum number of columns" + }, "event_viewer": { "autoplay_clip": "Autoplay clips", "draggable": "Event Viewer can be dragged/swiped", @@ -144,8 +147,10 @@ "menu_secondary": "Menu look & feel options", "live": "Live", "live_secondary": "Live camera view options", + "event_gallery": "Event gallery", + "event_gallery_secondary": "Snapshots & clips gallery options", "event_viewer": "Event viewer", - "event_viewer_secondary": "Snapshots & clips gallery options", + "event_viewer_secondary": "Snapshots & clips viewer options", "image": "Image", "image_secondary": "Static image view options", "dimensions": "Dimensions", diff --git a/src/scss/gallery.scss b/src/scss/gallery.scss index 57cefaa2..5f367850 100644 --- a/src/scss/gallery.scss +++ b/src/scss/gallery.scss @@ -42,4 +42,5 @@ ha-card.frigate-card-gallery-folder { background-color: var(--primary-background-color, black); padding: 10px; height: 100%; + line-height: 1; } \ No newline at end of file diff --git a/src/types.ts b/src/types.ts index 0f564965..56a27213 100644 --- a/src/types.ts +++ b/src/types.ts @@ -611,8 +611,17 @@ export type ViewerConfig = z.infer; /** * 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; /** * Dimensions configuration section. @@ -644,12 +653,11 @@ const dimensionsConfigSchema = z * Configuration overrides */ // Strip all defaults from the override schemas, to ensure values are only what -// the user has specified. -const overrideConfigurationSchema = - z.object({ - live: deepRemoveDefaults(liveOverridableConfigSchema).optional(), - menu: deepRemoveDefaults(menuConfigSchema).optional(), - }); +// the user has specified. +const overrideConfigurationSchema = z.object({ + live: deepRemoveDefaults(liveOverridableConfigSchema).optional(), + menu: deepRemoveDefaults(menuConfigSchema).optional(), +}); export type OverrideConfigurationKey = keyof z.infer; const overridesSchema = z @@ -701,6 +709,7 @@ export const frigateCardConfigDefaults = { menu: menuConfigDefault, live: liveConfigDefault, event_viewer: viewerConfigDefault, + event_gallery: galleryConfigDefault, }; const menuButtonSchema = z.union([