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
+1 -2
View File
@@ -18,7 +18,6 @@
"@cycjimmy/jsmpeg-player": "^5.1.1", "@cycjimmy/jsmpeg-player": "^5.1.1",
"@egjs/hammerjs": "^2.0.17", "@egjs/hammerjs": "^2.0.17",
"@lit-labs/task": "^1.1.1", "@lit-labs/task": "^1.1.1",
"@material/image-list": "^13.0.0",
"@material/mwc-menu": "^0.25.3", "@material/mwc-menu": "^0.25.3",
"@material/rtl": "^13.0.0", "@material/rtl": "^13.0.0",
"@types/bluebird": "^3.5.36", "@types/bluebird": "^3.5.36",
@@ -32,7 +31,7 @@
"keycharm": "^0.4.0", "keycharm": "^0.4.0",
"lit": "^2.2.1", "lit": "^2.2.1",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"moment": "^2.29.1", "moment": "^2.29.2",
"propagating-hammerjs": "^2.0.1", "propagating-hammerjs": "^2.0.1",
"quick-lru": "^6.1.0", "quick-lru": "^6.1.0",
"screenfull": "^6.0.1", "screenfull": "^6.0.1",
+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 */ /* 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 { HomeAssistant } from 'custom-card-helpers';
import { customElement, property, state } from 'lit/decorators.js'; import { customElement, property } from 'lit/decorators.js';
import { styleMap } from 'lit/directives/style-map.js';
import { import {
CameraConfig, CameraConfig,
ExtendedHomeAssistant, ExtendedHomeAssistant,
GalleryConfig, GalleryConfig,
THUMBNAIL_WIDTH_MAX,
frigateCardConfigDefaults, frigateCardConfigDefaults,
} from '../types.js'; } 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 { localize } from '../localize/localize.js'; import { THUMBNAIL_DETAILS_WIDTH_MIN } from './thumbnail.js';
import { renderProgressIndicator } from './message.js'; import { renderProgressIndicator } from './message.js';
import { stopEventFromActivatingCardWideActions } from '../common.js'; import { stopEventFromActivatingCardWideActions } from '../common.js';
import './thumbnail.js';
import galleryStyle from '../scss/gallery.scss'; import galleryStyle from '../scss/gallery.scss';
@customElement('frigate-card-gallery') @customElement('frigate-card-gallery')
@@ -77,7 +90,13 @@ export class FrigateCardGallery extends LitElement {
* Get element styles. * Get element styles.
*/ */
static get styles(): CSSResultGroup { 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; protected _resizeObserver: ResizeObserver;
@state()
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));
@@ -122,10 +138,16 @@ export class FrigateCardGalleryCore extends LitElement {
* Handle gallery resize. * Handle gallery resize.
*/ */
protected _resizeHandler(): void { protected _resizeHandler(): void {
this._columns = Math.max( const thumbnailSize =
this.galleryConfig?.min_columns ?? this.galleryConfig?.controls.thumbnails.size ??
frigateCardConfigDefaults.event_gallery.min_columns, frigateCardConfigDefaults.event_gallery.controls.thumbnails.size;
Math.ceil(this.clientWidth / THUMBNAIL_WIDTH_MAX), 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. * Master render method.
* @returns A rendered template. * @returns A rendered template.
@@ -156,93 +198,67 @@ export class FrigateCardGalleryCore extends LitElement {
return html``; return html``;
} }
const itemStyle = { return html`
// 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">
${this._showBackArrow() ${this._showBackArrow()
? html`<li class="mdc-image-list__item" style="${styleMap(itemStyle)}"> ? html` <ha-card
<div class="mdc-image-list__image-aspect-container"> @click=${(ev) => {
<div class="mdc-image-list__image"> if (this.view && this.view.previous) {
<ha-card this.view.previous.dispatchChangeEvent(this);
@click=${(ev) => { }
if (this.view && this.view.previous) { stopEventFromActivatingCardWideActions(ev);
this.view.previous.dispatchChangeEvent(this); }}
} outlined=""
stopEventFromActivatingCardWideActions(ev); >
}} <ha-icon .icon=${'mdi:arrow-left'}></ha-icon>
outlined="" </ha-card>`
class="frigate-card-gallery-folder"
>
<ha-icon .icon=${'mdi:arrow-left'}></ha-icon>
</ha-card>
</div>
</div>
</li>`
: ''} : ''}
${this.view.target.children.map( ${this.view.target.children.map(
(child, index) => (child, index) =>
html` <li class="mdc-image-list__item" style="${styleMap(itemStyle)}"> html`
<div class="mdc-image-list__image-aspect-container"> ${child.can_expand
${child.can_expand ? html`
? html`<div class="mdc-image-list__image"> <ha-card
<ha-card @click=${(ev) => {
@click=${(ev) => { if (this.hass && this.view) {
if (this.hass && this.view) { BrowseMediaUtil.fetchChildMediaAndDispatchViewChange(
BrowseMediaUtil.fetchChildMediaAndDispatchViewChange( this,
this, this.hass,
this.hass, this.view,
this.view, child,
child, );
); }
} stopEventFromActivatingCardWideActions(ev);
stopEventFromActivatingCardWideActions(ev); }}
}} outlined=""
outlined="" class="foo"
class="frigate-card-gallery-folder" >
> <div>${child.title}</div>
<div style="${styleMap(folderStyle)}">${child.title}</div> </ha-card>
</ha-card> `
</div>` : child.thumbnail
: child.thumbnail ? html`<frigate-card-thumbnail
? html`<img .view=${this.view}
aria-label="${child.title}" .target=${this.view?.target ?? null}
class="mdc-image-list__image" .childIndex=${index}
src="${child.thumbnail}" ?details=${!!this.galleryConfig?.controls.thumbnails.show_details}
title="${child.title}" ?controls=${!!this.galleryConfig?.controls.thumbnails.show_controls}
@click=${(ev: Event) => { @click=${(ev: Event) => {
if (this.view) { if (this.view) {
this.view this.view
.evolve({ .evolve({
view: this.view.is('clips') ? 'clip' : 'snapshot', view: this.view.is('clips') ? 'clip' : 'snapshot',
childIndex: index, childIndex: index,
}) })
.dispatchChangeEvent(this); .dispatchChangeEvent(this);
} }
stopEventFromActivatingCardWideActions(ev); stopEventFromActivatingCardWideActions(ev);
}} }}
/>${child.frigate?.event?.retain_indefinitely >
? html`<ha-icon </frigate-card-thumbnail>`
class="favorite" : ``}
icon="mdi:star" `,
title=${localize('thumbnail.retain_indefinitely')}
/>`
: ``}`
: ``}
</div>
</li>`,
)} )}
</ul>`; `;
} }
/** /**
+5 -8
View File
@@ -13,7 +13,7 @@ export class FrigateCardNextPreviousControl extends LitElement {
set controlConfig(controlConfig: NextPreviousControlConfig | undefined) { set controlConfig(controlConfig: NextPreviousControlConfig | undefined) {
if (controlConfig?.size) { 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; this._controlConfig = controlConfig;
} }
@@ -31,7 +31,7 @@ export class FrigateCardNextPreviousControl extends LitElement {
public disabled = false; public disabled = false;
// Label that is used for ARIA support and as tooltip. // Label that is used for ARIA support and as tooltip.
@property() label = ""; @property() label = '';
protected render(): TemplateResult { protected render(): TemplateResult {
if (this.disabled || !this._controlConfig || this._controlConfig.style == 'none') { 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)) { if (['chevrons', 'icons'].includes(this._controlConfig.style)) {
let icon: string; let icon: string;
if (this._controlConfig.style === 'chevrons') { 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 { } else {
if (!this.icon) { if (!this.icon) {
return html``; return html``;
} }
icon = this.icon icon = this.icon;
} }
return html` <ha-icon-button return html` <ha-icon-button class="${classMap(classes)}" .label=${this.label}>
class="${classMap(classes)}"
.label=${this.label}
>
<ha-icon icon=${icon}></ha-icon> <ha-icon icon=${icon}></ha-icon>
</ha-icon-button>`; </ha-icon-button>`;
} }
-1
View File
@@ -4,7 +4,6 @@ import { EmblaOptionsType } from 'embla-carousel';
import { classMap } from 'lit/directives/class-map.js'; import { classMap } from 'lit/directives/class-map.js';
import { customElement, property, state } from 'lit/decorators.js'; import { customElement, property, state } from 'lit/decorators.js';
import { ifDefined } from 'lit/directives/if-defined.js'; import { ifDefined } from 'lit/directives/if-defined.js';
import { isEqual } from 'lodash-es';
import type { import type {
FrigateBrowseMediaSource, FrigateBrowseMediaSource,
+49 -24
View File
@@ -12,6 +12,48 @@ import {
import { localize } from '../localize/localize.js'; import { localize } from '../localize/localize.js';
import thumbnailStyle from '../scss/thumbnail.scss'; 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') @customElement('frigate-card-thumbnail')
export class FrigateCardThumbnail extends LitElement { export class FrigateCardThumbnail extends LitElement {
@@ -21,9 +63,9 @@ export class FrigateCardThumbnail extends LitElement {
@property({ attribute: true, type: Boolean }) @property({ attribute: true, type: Boolean })
public controls = false; public controls = false;
@property({ attribute: true }) @property({ attribute: true, type: Number })
set thumbnail_size(size: string) { set thumbnail_size(size: number) {
this.style.setProperty('--frigate-card-thumbnail-size', String(size)); this.style.setProperty('--frigate-card-thumbnail-size', `${size}px`);
} }
// ============================ // ============================
@@ -33,7 +75,7 @@ export class FrigateCardThumbnail extends LitElement {
protected view?: Readonly<View>; protected view?: Readonly<View>;
@property({ attribute: false }) @property({ attribute: false })
public target?: FrigateBrowseMediaSource; public target?: FrigateBrowseMediaSource | null;
@property({ attribute: false }) @property({ attribute: false })
public childIndex?: number; public childIndex?: number;
@@ -91,26 +133,9 @@ export class FrigateCardThumbnail extends LitElement {
/>` />`
: ``} : ``}
${this.details && event ${this.details && event
? html` <div class="details"> ? html`<frigate-card-thumbnail-details
<div class="left"> .event=${event}
<div class="larger">${prettifyFrigateName(event.label)}</div> ></frigate-card-thumbnail-details>`
<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``} : html``}
${this.controls ${this.controls
? html`<ha-icon ? html`<ha-icon
+2 -1
View File
@@ -235,7 +235,8 @@
"event": { "event": {
"start": "Start", "start": "Start",
"duration": "Duration", "duration": "Duration",
"in_progress": "In Progress" "in_progress": "In Progress",
"score": "Score"
}, },
"thumbnail": { "thumbnail": {
"retain_indefinitely": "Event will be indefinitely retained", "retain_indefinitely": "Event will be indefinitely retained",
-14
View File
@@ -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
View File
@@ -1,9 +1,4 @@
@use '@material/image-list/mdc-image-list';
@use '@material/image-list';
@use './favorite.scss';
:host { :host {
display: block;
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow: auto; overflow: auto;
@@ -13,6 +8,15 @@
// Hide scrollbar: Firefox // Hide scrollbar: Firefox
scrollbar-width: none; 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 // Hide scrollbar for Chrome, Safari and Opera
@@ -20,21 +24,7 @@
display: none; display: none;
} }
.frigate-card-gallery { ha-card {
// 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 {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@@ -46,10 +36,11 @@ ha-card.frigate-card-gallery-folder {
border-radius: 5px; border-radius: 5px;
background-color: var(--primary-background-color, black); background-color: var(--primary-background-color, black);
padding: 10px; padding: 10px;
height: 100%;
line-height: 1; line-height: 1;
overflow: hidden;
} }
ha-icon.favorite { :host(:not([details])) ha-card,
opacity: 0.8; :host(:not([details])) frigate-card-thumbnail {
aspect-ratio: 1 / 1;
} }
+26
View File
@@ -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
View File
@@ -1,15 +1,12 @@
@use './favorite.scss';
:host { :host {
display: flex; display: flex;
flex-direction: row; 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 --frigate-card-max-thumbnail-size: 175px;
// 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);
} }
:host([details]) { :host([details]) {
@@ -31,10 +28,10 @@ img {
object-fit: cover; object-fit: cover;
// Restrict images to a maximum of thumbnail size. // Restrict images to a maximum of thumbnail size.
max-width: var(--frigate-card-thumbnail-size); aspect-ratio: 1 / 1;
max-height: var(--frigate-card-thumbnail-size);
min-width: var(--frigate-card-thumbnail-size); max-width: var(--frigate-card-max-thumbnail-size);
min-height: var(--frigate-card-thumbnail-size); max-height: var(--frigate-card-max-thumbnail-size);
transition: transform 0.2s linear; transition: transform 0.2s linear;
} }
@@ -42,27 +39,18 @@ img:hover {
transform: scale(1.04); transform: scale(1.04);
} }
div.details { ha-icon.favorite {
display: flex; position: absolute;
color: gold;
width: 200px; background: rgba(0, 0, 0, 0.2);
margin-left: 5px; border-radius: 50%;
padding: 8px; opacity: 0.8;
color: var(--primary-text-color);
}
div.details div {
display: flex;
flex-direction: column;
justify-content: center;
}
div.details div.left {
flex: 1;
} }
div.larger { ha-icon.timeline {
font-size: 1.5rem; position: absolute;
} color: var(--primary-color);
right: 0px;
span.heading { background: rgba(0, 0, 0, 0.2);
font-weight: bold; border-radius: 50%;
} }
+39 -13
View File
@@ -14,6 +14,9 @@ import { z } from 'zod';
import { deepRemoveDefaults } from './zod-util'; import { deepRemoveDefaults } from './zod-util';
// The maximum width thumbnail Frigate returns
export const THUMBNAIL_WIDTH_MAX = 175;
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
'frigate-card-editor': LovelaceCardEditor; 'frigate-card-editor': LovelaceCardEditor;
@@ -66,9 +69,6 @@ export type LiveProvider = typeof LIVE_PROVIDERS[number];
export class FrigateCardError extends Error {} export class FrigateCardError extends Error {}
// The maximum width thumbnail Frigate returns
export const THUMBNAIL_WIDTH_MAX = 175;
/** /**
* Action Types (for "Picture Elements" / Menu) * Action Types (for "Picture Elements" / Menu)
*/ */
@@ -442,7 +442,7 @@ export type ImageViewConfig = z.infer<typeof imageConfigSchema>;
const thumbnailsControlSchema = z.object({ const thumbnailsControlSchema = z.object({
mode: z.enum(['none', 'above', 'below', 'left', 'right']), 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_details: z.boolean().optional(),
show_controls: z.boolean().optional(), show_controls: z.boolean().optional(),
}); });
@@ -454,7 +454,7 @@ export type ThumbnailsControlConfig = z.infer<typeof thumbnailsControlSchema>;
const nextPreviousControlConfigSchema = z.object({ const nextPreviousControlConfigSchema = z.object({
style: z.enum(['none', 'chevrons', 'icons', 'thumbnails']), style: z.enum(['none', 'chevrons', 'icons', 'thumbnails']),
size: z.string(), size: z.number().min(1),
}); });
export type NextPreviousControlConfig = z.infer<typeof nextPreviousControlConfigSchema>; export type NextPreviousControlConfig = z.infer<typeof nextPreviousControlConfigSchema>;
@@ -491,12 +491,12 @@ const liveConfigDefault = {
transition_effect: 'slide' as const, transition_effect: 'slide' as const,
controls: { controls: {
next_previous: { next_previous: {
size: '48px', size: 48,
style: 'chevrons' as const, style: 'chevrons' as const,
}, },
thumbnails: { thumbnails: {
media: 'clips' as const, media: 'clips' as const,
size: '100px', size: 100,
show_details: false, show_details: false,
show_controls: true, show_controls: true,
mode: 'none' as const, mode: 'none' as const,
@@ -653,11 +653,11 @@ const viewerConfigDefault = {
transition_effect: 'slide' as const, transition_effect: 'slide' as const,
controls: { controls: {
next_previous: { next_previous: {
size: '48px', size: 48,
style: 'thumbnails' as const, style: 'thumbnails' as const,
}, },
thumbnails: { thumbnails: {
size: '100px', size: 100,
mode: 'none' as const, mode: 'none' as const,
show_details: false, show_details: false,
show_controls: true, show_controls: true,
@@ -672,7 +672,9 @@ const viewerNextPreviousControlConfigSchema = nextPreviousControlConfigSchema.ex
style: z style: z
.enum(['none', 'thumbnails', 'chevrons']) .enum(['none', 'thumbnails', 'chevrons'])
.default(viewerConfigDefault.controls.next_previous.style), .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< export type ViewerNextPreviousControlConfig = z.infer<
typeof viewerNextPreviousControlConfigSchema typeof viewerNextPreviousControlConfigSchema
@@ -729,12 +731,36 @@ export type ViewerConfig = z.infer<typeof viewerConfigSchema>;
* Event gallery configuration section (clips, snapshots). * Event gallery configuration section (clips, snapshots).
*/ */
const galleryConfigDefault = { const galleryConfigDefault = {
min_columns: 5, controls: {
thumbnails: {
size: 100,
show_details: false,
show_controls: false,
},
},
}; };
const galleryConfigSchema = z const galleryConfigSchema = z
.object({ .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) .merge(actionsSchema)
.default(galleryConfigDefault); .default(galleryConfigDefault);
@@ -776,7 +802,7 @@ const timelineConfigDefault = {
controls: { controls: {
thumbnails: { thumbnails: {
mode: 'left' as const, mode: 'left' as const,
size: '100px' as const, size: 100,
show_details: true, show_details: true,
show_controls: true, show_controls: true,
}, },