Merge pull request #302 from dermotduffy/add-title-popups

Add media title popups for live view and event viewer
This commit is contained in:
Dermot Duffy
2022-01-30 17:21:31 -08:00
committed by GitHub
12 changed files with 336 additions and 62 deletions
+31
View File
@@ -269,6 +269,22 @@ live:
| `style` | `chevrons` | :white_check_mark: | When viewing live cameras, what kind of controls to show to move to the previous/next camera. Acceptable values: `chevrons`, `icons`, `none` . | | `style` | `chevrons` | :white_check_mark: | When viewing live cameras, what kind of controls to show to move to the previous/next camera. Acceptable values: `chevrons`, `icons`, `none` . |
| `size` | `48px` | :white_check_mark: | The size of the next/previous controls [in CSS Units](https://www.w3schools.com/cssref/css_units.asp).| | `size` | `48px` | :white_check_mark: | The size of the next/previous controls [in CSS Units](https://www.w3schools.com/cssref/css_units.asp).|
<a name="live-controls-title"></a>
#### Live Controls: Title
All configuration is under:
```yaml
live:
controls:
title:
```
| Option | Default | Overridable | Description |
| - | - | - | - |
| `mode` | `popup-bottom-right` | :white_check_mark: | How to display the live camera title. Acceptable values: `none`, `popup-top-left`, `popup-top-right`, `popup-bottom-left`, `popup-bottom-right` . |
| `duration_seconds` | `2` | :white_check_mark: | The number of seconds to display the title popup. `0` implies forever.|
### Event Viewer Options ### Event Viewer Options
@@ -318,6 +334,21 @@ event_viewer:
| `mode` | `none` | :heavy_multiplication_x: | Whether to show the thumbnail carousel `below` the media, `above` the media or to hide it entirely (`none`).| | `mode` | `none` | :heavy_multiplication_x: | Whether to show the thumbnail carousel `below` the media, `above` the media or to hide it entirely (`none`).|
| `size` | `100px` | :heavy_multiplication_x: | The size of the thumbnails in the thumbnail carousel [in CSS Units](https://www.w3schools.com/cssref/css_units.asp).| | `size` | `100px` | :heavy_multiplication_x: | The size of the thumbnails in the thumbnail carousel [in CSS Units](https://www.w3schools.com/cssref/css_units.asp).|
#### Event Viewer Controls: Title
All configuration is under:
```yaml
event_viewer:
controls:
title:
```
| Option | Default | Overridable | Description |
| - | - | - | - |
| `mode` | `popup-bottom-right` | :heavy_multiplication_x: | How to display the event viewer media title. Acceptable values: `none`, `popup-top-left`, `popup-top-right`, `popup-bottom-left`, `popup-bottom-right` . |
| `duration_seconds` | `2` | :heavy_multiplication_x: | The number of seconds to display the title popup. `0` implies forever.|
### Event Gallery Options ### Event Gallery Options
The `event_gallery` is used for providing an overview of all `clips` and `snapshots` in a thumbnail gallery. The `event_gallery` is used for providing an overview of all `clips` and `snapshots` in a thumbnail gallery.
+2 -2
View File
@@ -143,10 +143,10 @@ export class FrigateCard extends LitElement {
protected _conditionState?: ConditionState; protected _conditionState?: ConditionState;
@query('frigate-card-menu') @query('frigate-card-menu')
_menu!: FrigateCardMenu; protected _menu!: FrigateCardMenu;
@query('frigate-card-elements') @query('frigate-card-elements')
_elements?: FrigateCardElements; protected _elements?: FrigateCardElements;
// user interaction timer ("screensaver" functionality, return to default // user interaction timer ("screensaver" functionality, return to default
// view after user interaction). // view after user interaction).
+15 -2
View File
@@ -21,6 +21,7 @@ import {
} from '../types.js'; } from '../types.js';
import { EmblaOptionsType, EmblaPluginType } from 'embla-carousel'; import { EmblaOptionsType, EmblaPluginType } from 'embla-carousel';
import { HomeAssistant } from 'custom-card-helpers'; import { HomeAssistant } from 'custom-card-helpers';
import JSMpeg from '@cycjimmy/jsmpeg-player';
import { Ref, createRef, ref } from 'lit/directives/ref.js'; import { Ref, createRef, ref } from 'lit/directives/ref.js';
import { Task } from '@lit-labs/task'; import { Task } from '@lit-labs/task';
import { customElement, property, state } from 'lit/decorators.js'; import { customElement, property, state } from 'lit/decorators.js';
@@ -47,7 +48,8 @@ import {
} from '../common.js'; } from '../common.js';
import { renderProgressIndicator } from '../components/message.js'; import { renderProgressIndicator } from '../components/message.js';
import JSMpeg from '@cycjimmy/jsmpeg-player'; import './next-prev-control.js';
import './title-control.js';
import liveStyle from '../scss/live.scss'; import liveStyle from '../scss/live.scss';
import liveFrigateStyle from '../scss/live-frigate.scss'; import liveFrigateStyle from '../scss/live-frigate.scss';
@@ -471,7 +473,7 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel {
protected render(): TemplateResult | void { protected render(): TemplateResult | void {
const [slides, cameraToSlide] = this._getSlides(); const [slides, cameraToSlide] = this._getSlides();
this._cameraToSlide = cameraToSlide; this._cameraToSlide = cameraToSlide;
if (!slides || !this.liveConfig) { if (!slides || !this.liveConfig || !this.cameras || !this.view) {
return; return;
} }
@@ -482,6 +484,8 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel {
) as LiveConfig; ) as LiveConfig;
const [prev, next] = this._getCameraNeighbors(); const [prev, next] = this._getCameraNeighbors();
const title = getCameraTitle(this.hass, this.cameras.get(this.view.camera));
return html` return html`
<div class="embla"> <div class="embla">
<frigate-card-next-previous-control <frigate-card-next-previous-control
@@ -512,6 +516,15 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel {
> >
</frigate-card-next-previous-control> </frigate-card-next-previous-control>
</div> </div>
<frigate-card-title-control
${ref(this._titleControlRef)}
.config=${config.controls.title}
.text="${title
? `${localize('common.live')}: ${title}`
: ''}"
.fitInto=${this as HTMLElement}
>
</frigate-card-title-control>
`; `;
} }
} }
+32 -2
View File
@@ -15,6 +15,7 @@ import './next-prev-control.js';
import mediaCarouselStyle from '../scss/media-carousel.scss'; import mediaCarouselStyle from '../scss/media-carousel.scss';
import { FrigateCardNextPreviousControl } from './next-prev-control.js'; import { FrigateCardNextPreviousControl } from './next-prev-control.js';
import { FrigateCardTitleControl } from './title-control.js';
import { MediaAutoPlayPauseType } from './embla-plugins/media-autoplay.js'; import { MediaAutoPlayPauseType } from './embla-plugins/media-autoplay.js';
const getEmptyImageSrc = (width: number, height: number) => const getEmptyImageSrc = (width: number, height: number) =>
@@ -27,7 +28,8 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel {
protected _mediaShowInfo: Record<number, MediaShowInfo> = {}; protected _mediaShowInfo: Record<number, MediaShowInfo> = {};
protected _nextControlRef: Ref<FrigateCardNextPreviousControl> = createRef(); protected _nextControlRef: Ref<FrigateCardNextPreviousControl> = createRef();
protected _previousControlRef: Ref<FrigateCardNextPreviousControl> = createRef(); protected _previousControlRef: Ref<FrigateCardNextPreviousControl> = createRef();
protected _titleControlRef: Ref<FrigateCardTitleControl> = createRef();
protected _titleTimerID: number | null = null;
/** /**
* Play the media on the selected slide. May be overridden to control when * Play the media on the selected slide. May be overridden to control when
* autoplay should happen. * autoplay should happen.
@@ -36,6 +38,32 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel {
(this._plugins['MediaAutoPlayPause'] as MediaAutoPlayPauseType | undefined)?.play(); (this._plugins['MediaAutoPlayPause'] as MediaAutoPlayPauseType | undefined)?.play();
} }
/**
* Show the media title after the media loads.
*/
protected _titleHandler(): void {
const show = () => {
this._titleTimerID = null;
this._titleControlRef.value?.show();
};
if (this._titleTimerID) {
window.clearTimeout(this._titleTimerID);
}
if (this._titleControlRef.value?.isVisible()) {
// If it's already visible, update it immediately (but also update it
// after the timer expires to ensure it re-positions if necessary, see
// comment below).
show();
}
// Allow a brief pause after the media loads, but before the title is
// displayed. This allows for a pleasant appearance/disappear of the title,
// and allows for the browser to finish rendering the carousel (inc.
// adaptive height which has `0.5s ease`, see `media-carousel.scss`).
this._titleTimerID = window.setTimeout(show, 0.5 * 1000);
}
/** /**
* Component connected callback. * Component connected callback.
*/ */
@@ -43,6 +71,7 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel {
super.connectedCallback(); super.connectedCallback();
this.addEventListener('frigate-card:media-show', this._autoplayHandler); this.addEventListener('frigate-card:media-show', this._autoplayHandler);
this.addEventListener('frigate-card:media-show', this._adaptiveHeightHandler); this.addEventListener('frigate-card:media-show', this._adaptiveHeightHandler);
this.addEventListener('frigate-card:media-show', this._titleHandler);
} }
/** /**
@@ -52,6 +81,7 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel {
super.disconnectedCallback(); super.disconnectedCallback();
this.removeEventListener('frigate-card:media-show', this._autoplayHandler); this.removeEventListener('frigate-card:media-show', this._autoplayHandler);
this.removeEventListener('frigate-card:media-show', this._adaptiveHeightHandler); this.removeEventListener('frigate-card:media-show', this._adaptiveHeightHandler);
this.removeEventListener('frigate-card:media-show', this._titleHandler);
} }
protected _destroyCarousel(): void { protected _destroyCarousel(): void {
@@ -93,7 +123,7 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel {
* actually the media load/show that will change the dimensions, and that is * actually the media load/show that will change the dimensions, and that is
* async from carousel actions (e.g. lazy-loaded media). * async from carousel actions (e.g. lazy-loaded media).
*/ */
protected _adaptiveHeightHandler(): void { protected _adaptiveHeightHandler(): void {
const adaptCarouselHeight = (): void => { const adaptCarouselHeight = (): void => {
if (!this._carousel) { if (!this._carousel) {
return; return;
+85
View File
@@ -0,0 +1,85 @@
import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit';
import { createRef, ref, Ref } from 'lit/directives/ref';
import { customElement, property } from 'lit/decorators.js';
import { TitleControlConfig } from '../types.js';
import titleStyle from '../scss/title-control.scss';
type PaperToast = HTMLElement & {
opened: boolean;
};
@customElement('frigate-card-title-control')
export class FrigateCardTitleControl extends LitElement {
@property({ attribute: false })
public config?: TitleControlConfig;
@property({ attribute: false })
public text?: string;
@property({ attribute: false })
public fitInto?: HTMLElement;
protected _toastRef: Ref<PaperToast> = createRef();
/**
* Master render method.
* @returns A rendered template.
*/
protected render(): TemplateResult {
if (!this.text || !this.config || this.config.mode == 'none' || !this.fitInto) {
return html``;
}
const verticalAlign = this.config.mode.match(/-top-/) ? 'top' : 'bottom';
const horizontalAlign = this.config.mode.match(/-left$/) ? 'left' : 'right';
return html` <paper-toast
${ref(this._toastRef)}
class="capsule"
.duration=${this.config.duration_seconds * 1000}
.verticalAlign=${verticalAlign}
.horizontalAlign=${horizontalAlign}
.text="${this.text}"
.fitInto=${this.fitInto}
>
</paper-toast>`;
}
/**
* Determine if the toast is visible.
* @returns `true` if the toast is visible, `false` otherwise.
*/
public isVisible(): boolean {
return this._toastRef.value?.opened ?? false;
}
/**
* Show the toast.
*/
public hide(): void {
if (this._toastRef.value) {
// Set it to false first, to ensure the timer resets.
this._toastRef.value.opened = false;
}
}
/**
* Show the toast.
*/
public show(): void {
if (this._toastRef.value) {
// Set it to false first, to ensure the timer resets.
this._toastRef.value.opened = false;
this._toastRef.value.opened = true;
}
}
/**
* Get element styles.
*/
static get styles(): CSSResultGroup {
return unsafeCSS(titleStyle);
}
}
+35 -25
View File
@@ -41,6 +41,7 @@ import {
import { renderProgressIndicator } from '../components/message.js'; import { renderProgressIndicator } from '../components/message.js';
import './next-prev-control.js'; import './next-prev-control.js';
import './title-control.js';
import viewerStyle from '../scss/viewer.scss'; import viewerStyle from '../scss/viewer.scss';
import viewerCoreStyle from '../scss/viewer-core.scss'; import viewerCoreStyle from '../scss/viewer-core.scss';
@@ -628,32 +629,41 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
const [prev, next] = [neighbors?.previous, neighbors?.next]; const [prev, next] = [neighbors?.previous, neighbors?.next];
return html`<div class="embla"> return html`<div class="embla">
<frigate-card-next-previous-control <frigate-card-next-previous-control
${ref(this._previousControlRef)} ${ref(this._previousControlRef)}
.direction=${'previous'} .direction=${'previous'}
.controlConfig=${this.viewerConfig?.controls.next_previous} .controlConfig=${this.viewerConfig?.controls.next_previous}
.thumbnail=${prev && prev.thumbnail ? prev.thumbnail : undefined} .thumbnail=${prev && prev.thumbnail ? prev.thumbnail : undefined}
.label=${prev ? prev.title : ''} .label=${prev ? prev.title : ''}
?disabled=${!prev} ?disabled=${!prev}
@click=${() => { @click=${() => {
this._nextPreviousHandler('previous'); this._nextPreviousHandler('previous');
}} }}
></frigate-card-next-previous-control> ></frigate-card-next-previous-control>
<div class="embla__viewport"> <div class="embla__viewport">
<div class="embla__container">${slides}</div> <div class="embla__container">${slides}</div>
</div>
<frigate-card-next-previous-control
${ref(this._nextControlRef)}
.direction=${'next'}
.controlConfig=${this.viewerConfig?.controls.next_previous}
.thumbnail=${next && next.thumbnail ? next.thumbnail : undefined}
.label=${next ? next.title : ''}
?disabled=${!next}
@click=${() => {
this._nextPreviousHandler('next');
}}
></frigate-card-next-previous-control>
</div> </div>
<frigate-card-next-previous-control ${this.view?.media
${ref(this._nextControlRef)} ? html` <frigate-card-title-control
.direction=${'next'} ${ref(this._titleControlRef)}
.controlConfig=${this.viewerConfig?.controls.next_previous} .config=${this.viewerConfig?.controls.title}
.thumbnail=${next && next.thumbnail ? next.thumbnail : undefined} .text="${this.view.media.title}"
.label=${next ? next.title : ''} .fitInto=${this as HTMLElement}
?disabled=${!next} >
@click=${() => { </frigate-card-title-control>`
this._nextPreviousHandler('next'); : ``} `;
}}
></frigate-card-next-previous-control>
</div>`;
} }
protected _renderMediaItem( protected _renderMediaItem(
+8
View File
@@ -43,6 +43,10 @@ export const CONF_EVENT_VIEWER_CONTROLS_THUMBNAILS_MODE =
`${CONF_EVENT_VIEWER}.controls.thumbnails.mode` as const; `${CONF_EVENT_VIEWER}.controls.thumbnails.mode` as const;
export const CONF_EVENT_VIEWER_CONTROLS_THUMBNAILS_SIZE = export const CONF_EVENT_VIEWER_CONTROLS_THUMBNAILS_SIZE =
`${CONF_EVENT_VIEWER}.controls.thumbnails.size` as const; `${CONF_EVENT_VIEWER}.controls.thumbnails.size` as const;
export const CONF_EVENT_VIEWER_CONTROLS_TITLE_MODE =
`${CONF_EVENT_VIEWER}.controls.title.mode` as const;
export const CONF_EVENT_VIEWER_CONTROLS_TITLE_DURATION_SECONDS =
`${CONF_EVENT_VIEWER}.controls.title.duration_seconds` as const;
export const CONF_LIVE = 'live' as const; export const CONF_LIVE = 'live' as const;
export const CONF_LIVE_CONTROLS_NEXT_PREVIOUS_STYLE = export const CONF_LIVE_CONTROLS_NEXT_PREVIOUS_STYLE =
@@ -55,6 +59,10 @@ export const CONF_LIVE_CONTROLS_THUMBNAILS_MODE =
`${CONF_LIVE}.controls.thumbnails.mode` as const; `${CONF_LIVE}.controls.thumbnails.mode` as const;
export const CONF_LIVE_CONTROLS_THUMBNAILS_SIZE = export const CONF_LIVE_CONTROLS_THUMBNAILS_SIZE =
`${CONF_LIVE}.controls.thumbnails.size` as const; `${CONF_LIVE}.controls.thumbnails.size` as const;
export const CONF_LIVE_CONTROLS_TITLE_MODE =
`${CONF_LIVE}.controls.title.mode` as const;
export const CONF_LIVE_CONTROLS_TITLE_DURATION_SECONDS =
`${CONF_LIVE}.controls.title.duration_seconds` as const;
export const CONF_LIVE_DRAGGABLE = `${CONF_LIVE}.draggable` as const; export const CONF_LIVE_DRAGGABLE = `${CONF_LIVE}.draggable` as const;
export const CONF_LIVE_JSMPEG = `${CONF_LIVE}.jsmpeg` as const; export const CONF_LIVE_JSMPEG = `${CONF_LIVE}.jsmpeg` as const;
export const CONF_LIVE_LAZY_LOAD = `${CONF_LIVE}.lazy_load` as const; export const CONF_LIVE_LAZY_LOAD = `${CONF_LIVE}.lazy_load` as const;
+36 -1
View File
@@ -33,6 +33,8 @@ import {
CONF_EVENT_VIEWER_CONTROLS_NEXT_PREVIOUS_STYLE, CONF_EVENT_VIEWER_CONTROLS_NEXT_PREVIOUS_STYLE,
CONF_EVENT_VIEWER_CONTROLS_THUMBNAILS_MODE, CONF_EVENT_VIEWER_CONTROLS_THUMBNAILS_MODE,
CONF_EVENT_VIEWER_CONTROLS_THUMBNAILS_SIZE, CONF_EVENT_VIEWER_CONTROLS_THUMBNAILS_SIZE,
CONF_EVENT_VIEWER_CONTROLS_TITLE_DURATION_SECONDS,
CONF_EVENT_VIEWER_CONTROLS_TITLE_MODE,
CONF_EVENT_VIEWER_DRAGGABLE, CONF_EVENT_VIEWER_DRAGGABLE,
CONF_EVENT_VIEWER_LAZY_LOAD, CONF_EVENT_VIEWER_LAZY_LOAD,
CONF_IMAGE_REFRESH_SECONDS, CONF_IMAGE_REFRESH_SECONDS,
@@ -42,6 +44,8 @@ import {
CONF_LIVE_CONTROLS_THUMBNAILS_MEDIA, CONF_LIVE_CONTROLS_THUMBNAILS_MEDIA,
CONF_LIVE_CONTROLS_THUMBNAILS_MODE, CONF_LIVE_CONTROLS_THUMBNAILS_MODE,
CONF_LIVE_CONTROLS_THUMBNAILS_SIZE, CONF_LIVE_CONTROLS_THUMBNAILS_SIZE,
CONF_LIVE_CONTROLS_TITLE_DURATION_SECONDS,
CONF_LIVE_CONTROLS_TITLE_MODE,
CONF_LIVE_DRAGGABLE, CONF_LIVE_DRAGGABLE,
CONF_LIVE_LAZY_LOAD, CONF_LIVE_LAZY_LOAD,
CONF_LIVE_LAZY_UNLOAD, CONF_LIVE_LAZY_UNLOAD,
@@ -233,6 +237,21 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
['snapshots', localize('config.live.controls.thumbnails.medias.snapshots')], ['snapshots', localize('config.live.controls.thumbnails.medias.snapshots')],
]); ]);
protected _titleModes = new Map([
['', ''],
['none', localize('config.event_viewer.controls.title.modes.none')],
['popup-top-left', localize('config.event_viewer.controls.title.modes.popup-top-left')],
['popup-top-right', localize('config.event_viewer.controls.title.modes.popup-top-right')],
[
'popup-bottom-left',
localize('config.event_viewer.controls.title.modes.popup-bottom-left'),
],
[
'popup-bottom-right',
localize('config.event_viewer.controls.title.modes.popup-bottom-right'),
],
]);
public setConfig(config: RawFrigateCardConfig): void { public setConfig(config: RawFrigateCardConfig): void {
// Note: This does not use Zod to parse the configuration, so it may be // Note: This does not use Zod to parse the configuration, so it may be
// partially or completely invalid. It's more useful to have a partially // partially or completely invalid. It's more useful to have a partially
@@ -752,6 +771,14 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
this._thumbnailMedias, this._thumbnailMedias,
)} )}
${this._renderStringInput(CONF_LIVE_CONTROLS_THUMBNAILS_SIZE)} ${this._renderStringInput(CONF_LIVE_CONTROLS_THUMBNAILS_SIZE)}
${this._renderDropdown(
CONF_LIVE_CONTROLS_TITLE_MODE,
this._titleModes,
)}
${this._renderStringInput(
CONF_LIVE_CONTROLS_TITLE_DURATION_SECONDS,
'number',
)}
</div> </div>
` `
: ''} : ''}
@@ -792,6 +819,14 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
this._thumbnailModes, this._thumbnailModes,
)} )}
${this._renderStringInput(CONF_EVENT_VIEWER_CONTROLS_THUMBNAILS_SIZE)} ${this._renderStringInput(CONF_EVENT_VIEWER_CONTROLS_THUMBNAILS_SIZE)}
${this._renderDropdown(
CONF_EVENT_VIEWER_CONTROLS_TITLE_MODE,
this._titleModes,
)}
${this._renderStringInput(
CONF_EVENT_VIEWER_CONTROLS_TITLE_DURATION_SECONDS,
'number',
)}
</div>` </div>`
: ''} : ''}
${this._renderOptionSetHeader('image')} ${this._renderOptionSetHeader('image')}
@@ -889,7 +924,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
value = target.checked; value = target.checked;
} else if (typeof target.value === 'string') { } else if (typeof target.value === 'string') {
value = target.value?.trim(); value = target.value?.trim();
if (target['type'] === 'number') { if (target['type'] === 'number' && value != '') {
value = Number(value); value = Number(value);
} }
} else { } else {
+17 -1
View File
@@ -6,7 +6,8 @@
"no_snapshots": "No snapshots", "no_snapshots": "No snapshots",
"no_clips": "No clips", "no_clips": "No clips",
"no_snapshot": "No recent snapshot", "no_snapshot": "No recent snapshot",
"no_clip": "No recent clip" "no_clip": "No recent clip",
"live": "Live"
}, },
"config": { "config": {
"cameras": { "cameras": {
@@ -73,6 +74,17 @@
"above": "Thumbnails above the media", "above": "Thumbnails above the media",
"none": "No thumbnails" "none": "No thumbnails"
} }
},
"title": {
"mode": "Event Viewer media title display mode",
"modes": {
"none": "No title display",
"popup-top-left": "Popup on the top left",
"popup-top-right": "Popup on the top right",
"popup-bottom-left": "Popup on the bottom left",
"popup-bottom-right": "Popup on the bottom right"
},
"duration_seconds": "Seconds to display popup title in Event Viewer (0=forever)"
} }
} }
}, },
@@ -99,6 +111,10 @@
"clips": "Clip thumbnails", "clips": "Clip thumbnails",
"snapshots": "Snapshot thumbnails" "snapshots": "Snapshot thumbnails"
} }
},
"title": {
"mode": "Live media title display mode",
"duration_seconds": "Seconds to display popup title in Live view (0=forever)"
} }
} }
}, },
-1
View File
@@ -2,7 +2,6 @@
--video-max-height: none; --video-max-height: none;
} }
.embla__container { .embla__container {
// To support adaptive height animations. // To support adaptive height animations.
transition: max-height 0.5s ease; transition: max-height 0.5s ease;
+8
View File
@@ -0,0 +1,8 @@
:host {
--paper-toast-background-color: rgba(0,0,0,0.6);
--paper-toast-color: white;
}
paper-toast {
max-width: unset;
}
+67 -28
View File
@@ -436,6 +436,21 @@ const nextPreviousControlConfigSchema = z.object({
}); });
export type NextPreviousControlConfig = z.infer<typeof nextPreviousControlConfigSchema>; export type NextPreviousControlConfig = z.infer<typeof nextPreviousControlConfigSchema>;
/**
* Title Control configuration section.
*/
const titleControlConfigSchema = z.object({
mode: z.enum([
'none',
'popup-top-right',
'popup-top-left',
'popup-bottom-right',
'popup-bottom-left',
]),
duration_seconds: z.number().min(0),
});
export type TitleControlConfig = z.infer<typeof titleControlConfigSchema>;
/** /**
* Live view configuration section. * Live view configuration section.
*/ */
@@ -454,6 +469,10 @@ const liveConfigDefault = {
size: '100px', size: '100px',
mode: 'none' as const, mode: 'none' as const,
}, },
title: {
mode: 'popup-bottom-right' as const,
duration_seconds: 2,
},
}, },
}; };
@@ -483,40 +502,46 @@ const jsmpegConfigSchema = z
.optional(); .optional();
export type JSMPEGConfig = z.infer<typeof jsmpegConfigSchema>; export type JSMPEGConfig = z.infer<typeof jsmpegConfigSchema>;
const liveNextPreviousControlConfigSchema = nextPreviousControlConfigSchema.extend({
// Live cannot show thumbnails, remove that option.
style: z
.enum(['none', 'chevrons', 'icons'])
.default(liveConfigDefault.controls.next_previous.style),
size: nextPreviousControlConfigSchema.shape.size.default(
liveConfigDefault.controls.next_previous.size,
),
});
const liveThumbnailControlConfigSchema = thumbnailsControlSchema.extend({
mode: thumbnailsControlSchema.shape.mode.default(
liveConfigDefault.controls.thumbnails.mode,
),
size: thumbnailsControlSchema.shape.size.default(
liveConfigDefault.controls.thumbnails.size,
),
media: z
.enum(['clips', 'snapshots'])
.default(liveConfigDefault.controls.thumbnails.media),
});
const liveOverridableConfigSchema = z const liveOverridableConfigSchema = z
.object({ .object({
webrtc: webrtcConfigSchema, webrtc: webrtcConfigSchema,
jsmpeg: jsmpegConfigSchema, jsmpeg: jsmpegConfigSchema,
controls: z controls: z
.object({ .object({
next_previous: liveNextPreviousControlConfigSchema.default( next_previous: nextPreviousControlConfigSchema
liveConfigDefault.controls.next_previous, .extend({
), // Live cannot show thumbnails, remove that option.
thumbnails: liveThumbnailControlConfigSchema.default( style: z
liveConfigDefault.controls.thumbnails, .enum(['none', 'chevrons', 'icons'])
), .default(liveConfigDefault.controls.next_previous.style),
size: nextPreviousControlConfigSchema.shape.size.default(
liveConfigDefault.controls.next_previous.size,
),
})
.default(liveConfigDefault.controls.next_previous),
thumbnails: thumbnailsControlSchema
.extend({
mode: thumbnailsControlSchema.shape.mode.default(
liveConfigDefault.controls.thumbnails.mode,
),
size: thumbnailsControlSchema.shape.size.default(
liveConfigDefault.controls.thumbnails.size,
),
media: z
.enum(['clips', 'snapshots'])
.default(liveConfigDefault.controls.thumbnails.media),
})
.default(liveConfigDefault.controls.thumbnails),
title: titleControlConfigSchema
.extend({
mode: titleControlConfigSchema.shape.mode.default(
liveConfigDefault.controls.title.mode,
),
duration_seconds: titleControlConfigSchema.shape.duration_seconds.default(
liveConfigDefault.controls.title.duration_seconds,
),
})
.default(liveConfigDefault.controls.title),
}) })
.default(liveConfigDefault.controls), .default(liveConfigDefault.controls),
}) })
@@ -589,6 +614,10 @@ const viewerConfigDefault = {
size: '100px', size: '100px',
mode: 'none' as const, mode: 'none' as const,
}, },
title: {
mode: 'popup-bottom-right' as const,
duration_seconds: 2,
},
}, },
}; };
const viewerNextPreviousControlConfigSchema = nextPreviousControlConfigSchema.extend({ const viewerNextPreviousControlConfigSchema = nextPreviousControlConfigSchema.extend({
@@ -621,6 +650,16 @@ const viewerConfigSchema = z
), ),
}) })
.default(viewerConfigDefault.controls.thumbnails), .default(viewerConfigDefault.controls.thumbnails),
title: titleControlConfigSchema
.extend({
mode: titleControlConfigSchema.shape.mode.default(
viewerConfigDefault.controls.title.mode,
),
duration_seconds: titleControlConfigSchema.shape.duration_seconds.default(
viewerConfigDefault.controls.title.duration_seconds,
),
})
.default(viewerConfigDefault.controls.title),
}) })
.default(viewerConfigDefault.controls), .default(viewerConfigDefault.controls),
}) })