First version of gallery refactor to use thumbnails.

This commit is contained in:
Dermot Duffy
2022-04-16 14:59:23 -07:00
parent c37e031116
commit ba196ff003
11 changed files with 267 additions and 213 deletions
+111 -95
View File
@@ -1,22 +1,35 @@
// TODO: Add details & controls & size support
// TODO: Remove mini support if it's not actually needed?
// TODO: automatic upgrade for thumbnail sizes in px
// TODO: automatic remove of min_columns
/* eslint-disable @typescript-eslint/no-explicit-any */
import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit';
import {
CSSResultGroup,
LitElement,
PropertyValues,
TemplateResult,
css,
html,
unsafeCSS,
} from 'lit';
import { HomeAssistant } from 'custom-card-helpers';
import { customElement, property, state } from 'lit/decorators.js';
import { styleMap } from 'lit/directives/style-map.js';
import { customElement, property } from 'lit/decorators.js';
import {
CameraConfig,
ExtendedHomeAssistant,
GalleryConfig,
THUMBNAIL_WIDTH_MAX,
frigateCardConfigDefaults,
} from '../types.js';
import { BrowseMediaUtil } from '../browse-media-util.js';
import { View } from '../view.js';
import { localize } from '../localize/localize.js';
import { THUMBNAIL_DETAILS_WIDTH_MIN } from './thumbnail.js';
import { renderProgressIndicator } from './message.js';
import { stopEventFromActivatingCardWideActions } from '../common.js';
import './thumbnail.js';
import galleryStyle from '../scss/gallery.scss';
@customElement('frigate-card-gallery')
@@ -77,7 +90,13 @@ export class FrigateCardGallery extends LitElement {
* Get element styles.
*/
static get styles(): CSSResultGroup {
return unsafeCSS(galleryStyle);
return css`
:host {
display: block;
width: 100%;
height: 100%;
}
`;
}
}
@@ -94,9 +113,6 @@ export class FrigateCardGalleryCore extends LitElement {
protected _resizeObserver: ResizeObserver;
@state()
protected _columns = frigateCardConfigDefaults.event_gallery.min_columns;
constructor() {
super();
this._resizeObserver = new ResizeObserver(this._resizeHandler.bind(this));
@@ -122,10 +138,16 @@ export class FrigateCardGalleryCore extends LitElement {
* Handle gallery resize.
*/
protected _resizeHandler(): void {
this._columns = Math.max(
this.galleryConfig?.min_columns ??
frigateCardConfigDefaults.event_gallery.min_columns,
Math.ceil(this.clientWidth / THUMBNAIL_WIDTH_MAX),
const thumbnailSize =
this.galleryConfig?.controls.thumbnails.size ??
frigateCardConfigDefaults.event_gallery.controls.thumbnails.size;
this.style.setProperty(
'--frigate-card-gallery-columns',
String(
!this.galleryConfig?.controls.thumbnails.show_details
? Math.round(this.clientWidth / thumbnailSize)
: Math.max(1, Math.floor(this.clientWidth / THUMBNAIL_DETAILS_WIDTH_MIN)),
),
);
}
@@ -141,6 +163,26 @@ export class FrigateCardGalleryCore extends LitElement {
);
}
/**
* Called when an update will occur.
* @param changedProps The changed properties
*/
protected willUpdate(changedProps: PropertyValues): void {
if (changedProps.has('galleryConfig')) {
if (this.galleryConfig?.controls.thumbnails.show_details) {
this.setAttribute('details', '');
} else {
this.removeAttribute('details');
}
if (this.galleryConfig?.controls.thumbnails.size) {
this.style.setProperty(
'--frigate-card-thumbnail-size',
`${this.galleryConfig.controls.thumbnails.size}px`,
);
}
}
}
/**
* Master render method.
* @returns A rendered template.
@@ -156,93 +198,67 @@ export class FrigateCardGalleryCore extends LitElement {
return html``;
}
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` <ul class="mdc-image-list frigate-card-gallery">
return html`
${this._showBackArrow()
? html`<li class="mdc-image-list__item" style="${styleMap(itemStyle)}">
<div class="mdc-image-list__image-aspect-container">
<div class="mdc-image-list__image">
<ha-card
@click=${(ev) => {
if (this.view && this.view.previous) {
this.view.previous.dispatchChangeEvent(this);
}
stopEventFromActivatingCardWideActions(ev);
}}
outlined=""
class="frigate-card-gallery-folder"
>
<ha-icon .icon=${'mdi:arrow-left'}></ha-icon>
</ha-card>
</div>
</div>
</li>`
? html` <ha-card
@click=${(ev) => {
if (this.view && this.view.previous) {
this.view.previous.dispatchChangeEvent(this);
}
stopEventFromActivatingCardWideActions(ev);
}}
outlined=""
>
<ha-icon .icon=${'mdi:arrow-left'}></ha-icon>
</ha-card>`
: ''}
${this.view.target.children.map(
(child, index) =>
html` <li class="mdc-image-list__item" style="${styleMap(itemStyle)}">
<div class="mdc-image-list__image-aspect-container">
${child.can_expand
? html`<div class="mdc-image-list__image">
<ha-card
@click=${(ev) => {
if (this.hass && this.view) {
BrowseMediaUtil.fetchChildMediaAndDispatchViewChange(
this,
this.hass,
this.view,
child,
);
}
stopEventFromActivatingCardWideActions(ev);
}}
outlined=""
class="frigate-card-gallery-folder"
>
<div style="${styleMap(folderStyle)}">${child.title}</div>
</ha-card>
</div>`
: child.thumbnail
? html`<img
aria-label="${child.title}"
class="mdc-image-list__image"
src="${child.thumbnail}"
title="${child.title}"
@click=${(ev: Event) => {
if (this.view) {
this.view
.evolve({
view: this.view.is('clips') ? 'clip' : 'snapshot',
childIndex: index,
})
.dispatchChangeEvent(this);
}
stopEventFromActivatingCardWideActions(ev);
}}
/>${child.frigate?.event?.retain_indefinitely
? html`<ha-icon
class="favorite"
icon="mdi:star"
title=${localize('thumbnail.retain_indefinitely')}
/>`
: ``}`
: ``}
</div>
</li>`,
html`
${child.can_expand
? html`
<ha-card
@click=${(ev) => {
if (this.hass && this.view) {
BrowseMediaUtil.fetchChildMediaAndDispatchViewChange(
this,
this.hass,
this.view,
child,
);
}
stopEventFromActivatingCardWideActions(ev);
}}
outlined=""
class="foo"
>
<div>${child.title}</div>
</ha-card>
`
: child.thumbnail
? html`<frigate-card-thumbnail
.view=${this.view}
.target=${this.view?.target ?? null}
.childIndex=${index}
?details=${!!this.galleryConfig?.controls.thumbnails.show_details}
?controls=${!!this.galleryConfig?.controls.thumbnails.show_controls}
@click=${(ev: Event) => {
if (this.view) {
this.view
.evolve({
view: this.view.is('clips') ? 'clip' : 'snapshot',
childIndex: index,
})
.dispatchChangeEvent(this);
}
stopEventFromActivatingCardWideActions(ev);
}}
>
</frigate-card-thumbnail>`
: ``}
`,
)}
</ul>`;
`;
}
/**
+5 -8
View File
@@ -13,7 +13,7 @@ export class FrigateCardNextPreviousControl extends LitElement {
set controlConfig(controlConfig: NextPreviousControlConfig | undefined) {
if (controlConfig?.size) {
this.style.setProperty('--frigate-card-next-prev-size', controlConfig.size);
this.style.setProperty('--frigate-card-next-prev-size', `${controlConfig.size}px`);
}
this._controlConfig = controlConfig;
}
@@ -31,7 +31,7 @@ export class FrigateCardNextPreviousControl extends LitElement {
public disabled = false;
// Label that is used for ARIA support and as tooltip.
@property() label = "";
@property() label = '';
protected render(): TemplateResult {
if (this.disabled || !this._controlConfig || this._controlConfig.style == 'none') {
@@ -50,18 +50,15 @@ export class FrigateCardNextPreviousControl extends LitElement {
if (['chevrons', 'icons'].includes(this._controlConfig.style)) {
let icon: string;
if (this._controlConfig.style === 'chevrons') {
icon = this.direction == 'previous' ? 'mdi:chevron-left' : 'mdi:chevron-right';
icon = this.direction == 'previous' ? 'mdi:chevron-left' : 'mdi:chevron-right';
} else {
if (!this.icon) {
return html``;
}
icon = this.icon
icon = this.icon;
}
return html` <ha-icon-button
class="${classMap(classes)}"
.label=${this.label}
>
return html` <ha-icon-button class="${classMap(classes)}" .label=${this.label}>
<ha-icon icon=${icon}></ha-icon>
</ha-icon-button>`;
}
-1
View File
@@ -4,7 +4,6 @@ import { EmblaOptionsType } from 'embla-carousel';
import { classMap } from 'lit/directives/class-map.js';
import { customElement, property, state } from 'lit/decorators.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { isEqual } from 'lodash-es';
import type {
FrigateBrowseMediaSource,
+49 -24
View File
@@ -12,6 +12,48 @@ import {
import { localize } from '../localize/localize.js';
import thumbnailStyle from '../scss/thumbnail.scss';
import thumbnailDetailsStyle from '../scss/thumbnail-details.scss';
// The minimum width of a thumbnail with details enabled.
export const THUMBNAIL_DETAILS_WIDTH_MIN = 300;
@customElement('frigate-card-thumbnail-details')
export class FrigateCardThumbnailDetails extends LitElement {
@property({ attribute: false })
public event?: FrigateEvent;
protected render(): TemplateResult | void {
if (!this.event) {
return;
}
const score = (this.event.top_score * 100).toFixed(2) + '%';
return html`<div class="left">
<div class="larger">${prettifyFrigateName(this.event.label)}</div>
<div>
<span>
<span class="heading">${localize('event.start')}:</span>
${format(fromUnixTime(this.event.start_time), 'HH:mm:ss')}
</span>
</div>
<div>
<span>
<span class="heading">${localize('event.duration')}:</span>
${getEventDurationString(this.event)}
</span>
</div>
</div>
<div class="right">
<div class="larger">${score}</div>
</div>`;
}
/**
* Get element styles.
*/
static get styles(): CSSResult {
return unsafeCSS(thumbnailDetailsStyle);
}
}
@customElement('frigate-card-thumbnail')
export class FrigateCardThumbnail extends LitElement {
@@ -21,9 +63,9 @@ export class FrigateCardThumbnail extends LitElement {
@property({ attribute: true, type: Boolean })
public controls = false;
@property({ attribute: true })
set thumbnail_size(size: string) {
this.style.setProperty('--frigate-card-thumbnail-size', String(size));
@property({ attribute: true, type: Number })
set thumbnail_size(size: number) {
this.style.setProperty('--frigate-card-thumbnail-size', `${size}px`);
}
// ============================
@@ -33,7 +75,7 @@ export class FrigateCardThumbnail extends LitElement {
protected view?: Readonly<View>;
@property({ attribute: false })
public target?: FrigateBrowseMediaSource;
public target?: FrigateBrowseMediaSource | null;
@property({ attribute: false })
public childIndex?: number;
@@ -91,26 +133,9 @@ export class FrigateCardThumbnail extends LitElement {
/>`
: ``}
${this.details && event
? html` <div class="details">
<div class="left">
<div class="larger">${prettifyFrigateName(event.label)}</div>
<div>
<span>
<span class="heading">${localize('event.start')}:</span>
${format(fromUnixTime(event.start_time), 'HH:mm:ss')}
</span>
</div>
<div>
<span>
<span class="heading">${localize('event.duration')}:</span>
${getEventDurationString(event)}
</span>
</div>
</div>
<div class="right">
<div class="larger">${(event.top_score * 100).toFixed(2) + '%'}</div>
</div>
</div>`
? html`<frigate-card-thumbnail-details
.event=${event}
></frigate-card-thumbnail-details>`
: html``}
${this.controls
? html`<ha-icon