First version of gallery refactor to use thumbnails.
This commit is contained in:
+1
-2
@@ -18,7 +18,6 @@
|
||||
"@cycjimmy/jsmpeg-player": "^5.1.1",
|
||||
"@egjs/hammerjs": "^2.0.17",
|
||||
"@lit-labs/task": "^1.1.1",
|
||||
"@material/image-list": "^13.0.0",
|
||||
"@material/mwc-menu": "^0.25.3",
|
||||
"@material/rtl": "^13.0.0",
|
||||
"@types/bluebird": "^3.5.36",
|
||||
@@ -32,7 +31,7 @@
|
||||
"keycharm": "^0.4.0",
|
||||
"lit": "^2.2.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"moment": "^2.29.1",
|
||||
"moment": "^2.29.2",
|
||||
"propagating-hammerjs": "^2.0.1",
|
||||
"quick-lru": "^6.1.0",
|
||||
"screenfull": "^6.0.1",
|
||||
|
||||
+111
-95
@@ -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>`;
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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>`;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -235,7 +235,8 @@
|
||||
"event": {
|
||||
"start": "Start",
|
||||
"duration": "Duration",
|
||||
"in_progress": "In Progress"
|
||||
"in_progress": "In Progress",
|
||||
"score": "Score"
|
||||
},
|
||||
"thumbnail": {
|
||||
"retain_indefinitely": "Event will be indefinitely retained",
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
ha-icon.favorite {
|
||||
position: absolute;
|
||||
color: gold;
|
||||
background: rgba(0,0,0,0.2);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
ha-icon.timeline {
|
||||
position: absolute;
|
||||
color: var(--primary-color);
|
||||
right: 0px;
|
||||
background: rgba(0,0,0,0.2);
|
||||
border-radius: 50%;
|
||||
}
|
||||
+14
-23
@@ -1,9 +1,4 @@
|
||||
@use '@material/image-list/mdc-image-list';
|
||||
@use '@material/image-list';
|
||||
@use './favorite.scss';
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
@@ -13,6 +8,15 @@
|
||||
|
||||
// Hide scrollbar: Firefox
|
||||
scrollbar-width: none;
|
||||
|
||||
--frigate-card-gallery-gap: 3px;
|
||||
--frigate-card-gallery-columns: 4;
|
||||
--frigate-card-thumbnail-size: 100px;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--frigate-card-gallery-columns), minmax(0, 1fr));
|
||||
grid-auto-rows: var(--frigate-card-thumbnail-size);
|
||||
grid-column-gap: var(--frigate-card-gallery-gap);
|
||||
}
|
||||
|
||||
// Hide scrollbar for Chrome, Safari and Opera
|
||||
@@ -20,21 +24,7 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.frigate-card-gallery {
|
||||
// Note: In fullscreen, number of columns is overwritten in Javascript based
|
||||
// on dimensions.
|
||||
@include image-list.standard-columns(4, 5px);
|
||||
@include image-list.shape-radius(5px);
|
||||
}
|
||||
|
||||
.frigate-card-gallery .mdc-image-list__image {
|
||||
transition: transform 0.2s linear;
|
||||
}
|
||||
.frigate-card-gallery .mdc-image-list__image:hover {
|
||||
transform: scale(1.04);
|
||||
}
|
||||
|
||||
ha-card.frigate-card-gallery-folder {
|
||||
ha-card {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@@ -46,10 +36,11 @@ ha-card.frigate-card-gallery-folder {
|
||||
border-radius: 5px;
|
||||
background-color: var(--primary-background-color, black);
|
||||
padding: 10px;
|
||||
height: 100%;
|
||||
line-height: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
ha-icon.favorite {
|
||||
opacity: 0.8;
|
||||
:host(:not([details])) ha-card,
|
||||
:host(:not([details])) frigate-card-thumbnail {
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
:host {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin-left: 5px;
|
||||
padding: 15px;
|
||||
color: var(--primary-text-color);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
div.left {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
div.larger {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
span.heading {
|
||||
font-weight: bold;
|
||||
}
|
||||
+20
-32
@@ -1,15 +1,12 @@
|
||||
@use './favorite.scss';
|
||||
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
box-sizing: border-box;
|
||||
|
||||
--frigate-card-thumbnail-size: 100px;
|
||||
// Ensure control icons are relative to the thumbnail.
|
||||
position: relative;
|
||||
|
||||
// Do not let the contain expand to fill more height than the height of the
|
||||
// thumbnail. Without this the thumbnail carousel will allow the thumbnail to
|
||||
// expand to the full height of the viewport on a vertical carousel.
|
||||
max-height: var(--frigate-card-thumbnail-size);
|
||||
--frigate-card-max-thumbnail-size: 175px;
|
||||
}
|
||||
|
||||
:host([details]) {
|
||||
@@ -31,10 +28,10 @@ img {
|
||||
object-fit: cover;
|
||||
|
||||
// Restrict images to a maximum of thumbnail size.
|
||||
max-width: var(--frigate-card-thumbnail-size);
|
||||
max-height: var(--frigate-card-thumbnail-size);
|
||||
min-width: var(--frigate-card-thumbnail-size);
|
||||
min-height: var(--frigate-card-thumbnail-size);
|
||||
aspect-ratio: 1 / 1;
|
||||
|
||||
max-width: var(--frigate-card-max-thumbnail-size);
|
||||
max-height: var(--frigate-card-max-thumbnail-size);
|
||||
|
||||
transition: transform 0.2s linear;
|
||||
}
|
||||
@@ -42,27 +39,18 @@ img:hover {
|
||||
transform: scale(1.04);
|
||||
}
|
||||
|
||||
div.details {
|
||||
display: flex;
|
||||
|
||||
width: 200px;
|
||||
margin-left: 5px;
|
||||
padding: 8px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
div.details div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
div.details div.left {
|
||||
flex: 1;
|
||||
ha-icon.favorite {
|
||||
position: absolute;
|
||||
color: gold;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 50%;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
div.larger {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
span.heading {
|
||||
font-weight: bold;
|
||||
ha-icon.timeline {
|
||||
position: absolute;
|
||||
color: var(--primary-color);
|
||||
right: 0px;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
+39
-13
@@ -14,6 +14,9 @@ import { z } from 'zod';
|
||||
|
||||
import { deepRemoveDefaults } from './zod-util';
|
||||
|
||||
// The maximum width thumbnail Frigate returns
|
||||
export const THUMBNAIL_WIDTH_MAX = 175;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'frigate-card-editor': LovelaceCardEditor;
|
||||
@@ -66,9 +69,6 @@ export type LiveProvider = typeof LIVE_PROVIDERS[number];
|
||||
|
||||
export class FrigateCardError extends Error {}
|
||||
|
||||
// The maximum width thumbnail Frigate returns
|
||||
export const THUMBNAIL_WIDTH_MAX = 175;
|
||||
|
||||
/**
|
||||
* Action Types (for "Picture Elements" / Menu)
|
||||
*/
|
||||
@@ -442,7 +442,7 @@ export type ImageViewConfig = z.infer<typeof imageConfigSchema>;
|
||||
|
||||
const thumbnailsControlSchema = z.object({
|
||||
mode: z.enum(['none', 'above', 'below', 'left', 'right']),
|
||||
size: z.string().optional(),
|
||||
size: z.number().min(1).max(THUMBNAIL_WIDTH_MAX).optional(),
|
||||
show_details: z.boolean().optional(),
|
||||
show_controls: z.boolean().optional(),
|
||||
});
|
||||
@@ -454,7 +454,7 @@ export type ThumbnailsControlConfig = z.infer<typeof thumbnailsControlSchema>;
|
||||
|
||||
const nextPreviousControlConfigSchema = z.object({
|
||||
style: z.enum(['none', 'chevrons', 'icons', 'thumbnails']),
|
||||
size: z.string(),
|
||||
size: z.number().min(1),
|
||||
});
|
||||
export type NextPreviousControlConfig = z.infer<typeof nextPreviousControlConfigSchema>;
|
||||
|
||||
@@ -491,12 +491,12 @@ const liveConfigDefault = {
|
||||
transition_effect: 'slide' as const,
|
||||
controls: {
|
||||
next_previous: {
|
||||
size: '48px',
|
||||
size: 48,
|
||||
style: 'chevrons' as const,
|
||||
},
|
||||
thumbnails: {
|
||||
media: 'clips' as const,
|
||||
size: '100px',
|
||||
size: 100,
|
||||
show_details: false,
|
||||
show_controls: true,
|
||||
mode: 'none' as const,
|
||||
@@ -653,11 +653,11 @@ const viewerConfigDefault = {
|
||||
transition_effect: 'slide' as const,
|
||||
controls: {
|
||||
next_previous: {
|
||||
size: '48px',
|
||||
size: 48,
|
||||
style: 'thumbnails' as const,
|
||||
},
|
||||
thumbnails: {
|
||||
size: '100px',
|
||||
size: 100,
|
||||
mode: 'none' as const,
|
||||
show_details: false,
|
||||
show_controls: true,
|
||||
@@ -672,7 +672,9 @@ const viewerNextPreviousControlConfigSchema = nextPreviousControlConfigSchema.ex
|
||||
style: z
|
||||
.enum(['none', 'thumbnails', 'chevrons'])
|
||||
.default(viewerConfigDefault.controls.next_previous.style),
|
||||
size: z.string().default(viewerConfigDefault.controls.next_previous.size),
|
||||
size: nextPreviousControlConfigSchema.shape.size.default(
|
||||
viewerConfigDefault.controls.next_previous.size,
|
||||
),
|
||||
});
|
||||
export type ViewerNextPreviousControlConfig = z.infer<
|
||||
typeof viewerNextPreviousControlConfigSchema
|
||||
@@ -729,12 +731,36 @@ export type ViewerConfig = z.infer<typeof viewerConfigSchema>;
|
||||
* Event gallery configuration section (clips, snapshots).
|
||||
*/
|
||||
const galleryConfigDefault = {
|
||||
min_columns: 5,
|
||||
controls: {
|
||||
thumbnails: {
|
||||
size: 100,
|
||||
show_details: false,
|
||||
show_controls: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const galleryConfigSchema = z
|
||||
.object({
|
||||
min_columns: z.number().min(1).max(10).default(galleryConfigDefault.min_columns),
|
||||
controls: z
|
||||
.object({
|
||||
thumbnails: thumbnailsControlSchema
|
||||
// Gallery shows thumbnails "centrally" so no need for the mode.
|
||||
.omit({ mode: true })
|
||||
.extend({
|
||||
size: thumbnailsControlSchema.shape.size.default(
|
||||
galleryConfigDefault.controls.thumbnails.size,
|
||||
),
|
||||
show_details: thumbnailsControlSchema.shape.show_details.default(
|
||||
galleryConfigDefault.controls.thumbnails.show_details,
|
||||
),
|
||||
show_controls: thumbnailsControlSchema.shape.show_controls.default(
|
||||
galleryConfigDefault.controls.thumbnails.show_controls,
|
||||
),
|
||||
})
|
||||
.default(galleryConfigDefault.controls.thumbnails),
|
||||
})
|
||||
.default(galleryConfigDefault.controls),
|
||||
})
|
||||
.merge(actionsSchema)
|
||||
.default(galleryConfigDefault);
|
||||
@@ -776,7 +802,7 @@ const timelineConfigDefault = {
|
||||
controls: {
|
||||
thumbnails: {
|
||||
mode: 'left' as const,
|
||||
size: '100px' as const,
|
||||
size: 100,
|
||||
show_details: true,
|
||||
show_controls: true,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user