Convert viewer to inherit from media carousel.
This commit is contained in:
@@ -31,6 +31,14 @@ export class FrigateCardCarousel extends LitElement {
|
||||
this._carousel?.scrollTo(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the selected slide.
|
||||
* @returns The slide index or undefined if the carousel is not loaded.
|
||||
*/
|
||||
carouselSelected(): number | undefined {
|
||||
return this._carousel?.selectedScrollSnap();
|
||||
}
|
||||
|
||||
/**
|
||||
* The updated lifecycle callback for this element.
|
||||
* @param changedProperties The properties that were changed in this render.
|
||||
@@ -61,16 +69,14 @@ export class FrigateCardCarousel extends LitElement {
|
||||
'.embla__viewport',
|
||||
) as HTMLElement;
|
||||
|
||||
if (carouselNode && !this._carousel) {
|
||||
if (!this._carousel && carouselNode) {
|
||||
this._carousel = EmblaCarousel(carouselNode, this._options);
|
||||
this._carousel.on('init', () => dispatchFrigateCardEvent(this, 'carousel:init'));
|
||||
this._carousel.on('resize', () =>
|
||||
dispatchFrigateCardEvent(this, 'carousel:resize'),
|
||||
);
|
||||
this._carousel.on('select', () => {
|
||||
if (this._carousel) {
|
||||
const selected = this.carouselSelected();
|
||||
if (selected !== undefined) {
|
||||
dispatchFrigateCardEvent<CarouselSelect>(this, 'carousel:select', {
|
||||
index: this._carousel.selectedScrollSnap(),
|
||||
index: selected,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ export class FrigateCardThumbnailCarousel extends FrigateCardCarousel {
|
||||
@property({ attribute: false })
|
||||
protected target?: BrowseMediaSource;
|
||||
|
||||
protected _tapSelected? = 0;
|
||||
protected _tapSelected?;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
+82
-56
@@ -4,10 +4,9 @@ import {
|
||||
TemplateResult,
|
||||
html,
|
||||
unsafeCSS,
|
||||
PropertyValues,
|
||||
} from 'lit';
|
||||
import { BrowseMediaUtil } from '../browse-media-util.js';
|
||||
import EmblaCarousel, { EmblaCarouselType } from 'embla-carousel';
|
||||
import { EmblaCarouselType } from 'embla-carousel';
|
||||
import { HomeAssistant } from 'custom-card-helpers';
|
||||
import { createRef, Ref, ref } from 'lit/directives/ref.js';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
@@ -22,7 +21,7 @@ import type {
|
||||
MediaShowInfo,
|
||||
ViewerConfig,
|
||||
} from '../types.js';
|
||||
import { CarouselTap } from './carousel.js';
|
||||
import { CarouselTap, FrigateCardCarousel } from './carousel.js';
|
||||
import { FrigateCardThumbnailCarousel } from './thumbnail-carousel.js';
|
||||
import { ResolvedMediaCache, ResolvedMediaUtil } from '../resolved-media.js';
|
||||
import { View } from '../view.js';
|
||||
@@ -44,6 +43,7 @@ import './thumbnail-carousel.js';
|
||||
|
||||
import viewerStyle from '../scss/viewer.scss';
|
||||
import viewerCoreStyle from '../scss/viewer-core.scss';
|
||||
import mediaCarouselStyle from '../scss/media-carousel.scss';
|
||||
|
||||
const getEmptyImageSrc = (width: number, height: number) =>
|
||||
`data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}"%3E%3C/svg%3E`;
|
||||
@@ -171,12 +171,68 @@ export class FrigateCardViewerCore extends LitElement {
|
||||
@property({ attribute: false })
|
||||
protected resolvedMediaCache?: ResolvedMediaCache;
|
||||
|
||||
// Media carousel object.
|
||||
protected _carousel?: EmblaCarouselType;
|
||||
protected _loadedCarousel = false;
|
||||
|
||||
protected _mediaCarouselRef: Ref<FrigateCardMediaCarousel> = createRef();
|
||||
protected _thumbnailCarouselRef: Ref<FrigateCardThumbnailCarousel> = createRef();
|
||||
|
||||
protected _syncThumbnailCarousel(): void {
|
||||
const mediaSelected = this._mediaCarouselRef.value?.carouselSelected();
|
||||
if (mediaSelected !== undefined) {
|
||||
this._thumbnailCarouselRef.value?.carouselScrollTo(mediaSelected);
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
if (!this.view) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<frigate-card-media-carousel
|
||||
${ref(this._mediaCarouselRef)}
|
||||
.hass=${this.hass}
|
||||
.view=${this.view}
|
||||
.viewerConfig=${this.viewerConfig}
|
||||
.browseMediaQueryParameters=${this.browseMediaQueryParameters}
|
||||
.resolvedMediaCache=${this.resolvedMediaCache}
|
||||
@frigate-card:carousel:select=${this._syncThumbnailCarousel.bind(this)}
|
||||
>
|
||||
</frigate-card-media-carousel>
|
||||
<frigate-card-thumbnail-carousel
|
||||
${ref(this._thumbnailCarouselRef)}
|
||||
.target=${this.view.target}
|
||||
@frigate-card:carousel:tap=${(ev: CustomEvent<CarouselTap>) => {
|
||||
this._mediaCarouselRef.value?.carouselScrollTo(ev.detail.index);
|
||||
}}
|
||||
@frigate-card:carousel:init=${this._syncThumbnailCarousel.bind(this)}
|
||||
>
|
||||
</frigate-card-thumbnail-carousel>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get element styles.
|
||||
*/
|
||||
static get styles(): CSSResultGroup {
|
||||
return unsafeCSS(viewerCoreStyle);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@customElement('frigate-card-media-carousel')
|
||||
export class FrigateCardMediaCarousel extends FrigateCardCarousel {
|
||||
@property({ attribute: false })
|
||||
protected hass?: HomeAssistant & ExtendedHomeAssistant;
|
||||
|
||||
@property({ attribute: false })
|
||||
protected view?: View;
|
||||
|
||||
@property({ attribute: false })
|
||||
protected viewerConfig?: ViewerConfig;
|
||||
|
||||
@property({ attribute: false })
|
||||
protected browseMediaQueryParameters?: BrowseMediaQueryParameters;
|
||||
|
||||
@property({ attribute: false })
|
||||
protected resolvedMediaCache?: ResolvedMediaCache;
|
||||
|
||||
// Mapping of slide # to BrowseMediaSource child #.
|
||||
// (Folders are not media items that can be rendered).
|
||||
protected _slideToChild: Record<number, number> = {};
|
||||
@@ -187,30 +243,13 @@ export class FrigateCardViewerCore extends LitElement {
|
||||
// Whether or not a given slide has been successfully lazily loaded.
|
||||
protected _slideHasBeenLazyLoaded: Record<number, boolean> = {};
|
||||
|
||||
/**
|
||||
* The updated lifecycle callback for this element.
|
||||
* @param changedProperties The properties that were changed in this render.
|
||||
*/
|
||||
updated(changedProperties: PropertyValues): void {
|
||||
super.updated(changedProperties);
|
||||
|
||||
if (!this._loadedCarousel) {
|
||||
this.updateComplete.then(() => {
|
||||
this._loadCarousel();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the carousel with "slides" (clips or snapshots).
|
||||
*/
|
||||
protected _loadCarousel(): void {
|
||||
const carouselNode = this.renderRoot.querySelector(
|
||||
'.embla__viewport',
|
||||
) as HTMLElement;
|
||||
|
||||
if (carouselNode && this.viewerConfig) {
|
||||
this._loadedCarousel = true;
|
||||
if (this._carousel || !this.viewerConfig) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Start the carousel on the selected child number.
|
||||
const startIndex = Number(
|
||||
@@ -219,32 +258,27 @@ export class FrigateCardViewerCore extends LitElement {
|
||||
),
|
||||
);
|
||||
|
||||
this._carousel = EmblaCarousel(carouselNode, {
|
||||
this._options = {
|
||||
startIndex: isNaN(startIndex) ? undefined : startIndex,
|
||||
draggable: this.viewerConfig.draggable,
|
||||
});
|
||||
}
|
||||
|
||||
super._loadCarousel();
|
||||
|
||||
// Necessary because typescript local type narrowing is not paying attention
|
||||
// to the side-effect of the call to super._loadCarousel().
|
||||
const carousel = this._carousel as EmblaCarouselType | undefined;
|
||||
|
||||
// Update views and dispatch media-show events based on slide selections.
|
||||
this._carousel.on('select', this._selectSlideSetViewHandler.bind(this));
|
||||
this._carousel.on('select', this._selectSlideMediaShowHandler.bind(this));
|
||||
carousel?.on('select', this._selectSlideSetViewHandler.bind(this));
|
||||
carousel?.on('select', this._selectSlideMediaShowHandler.bind(this));
|
||||
|
||||
// Lazily load media that is displayed. These handlers are registered
|
||||
// regardless of the value of this.lazyLoad to allow that value to change
|
||||
// after the carousel has been initialized.
|
||||
this._carousel.on('init', this._lazyLoadMediaHandler.bind(this));
|
||||
this._carousel.on('select', this._lazyLoadMediaHandler.bind(this));
|
||||
this._carousel.on('resize', this._lazyLoadMediaHandler.bind(this));
|
||||
|
||||
this._carousel.on('select', this._syncThumbnailCarousel.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
protected _syncThumbnailCarousel(): void {
|
||||
if (!this._carousel) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._thumbnailCarouselRef.value?.carouselScrollTo(
|
||||
this._carousel.selectedScrollSnap());
|
||||
carousel?.on('init', this._lazyLoadMediaHandler.bind(this));
|
||||
carousel?.on('select', this._lazyLoadMediaHandler.bind(this));
|
||||
carousel?.on('resize', this._lazyLoadMediaHandler.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -532,15 +566,7 @@ export class FrigateCardViewerCore extends LitElement {
|
||||
}}
|
||||
></frigate-card-next-previous-control>`
|
||||
: ``}
|
||||
</div>
|
||||
<frigate-card-thumbnail-carousel
|
||||
${ref(this._thumbnailCarouselRef)}
|
||||
.target=${this.view.target}
|
||||
.selected=${this._carousel?.selectedScrollSnap()}
|
||||
@frigate-card:carousel:tap=${(ev: CustomEvent<CarouselTap>) => this._carousel?.scrollTo(ev.detail.index)}
|
||||
@frigate-card:carousel:init=${this._syncThumbnailCarousel.bind(this)}
|
||||
>
|
||||
</frigate-card-thumbnail-carousel>`;
|
||||
</div>`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -722,6 +748,6 @@ export class FrigateCardViewerCore extends LitElement {
|
||||
* Get element styles.
|
||||
*/
|
||||
static get styles(): CSSResultGroup {
|
||||
return unsafeCSS(viewerCoreStyle);
|
||||
return [super.styles, unsafeCSS(mediaCarouselStyle)];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
:host {
|
||||
--video-max-height: none;
|
||||
}
|
||||
|
||||
.embla__slide {
|
||||
flex: 0 0 100%;
|
||||
}
|
||||
|
||||
frigate-card-ha-hls-player {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
:host {
|
||||
--frigate-card-viewer-thumbnail-size: 100px;
|
||||
}
|
||||
|
||||
.embla__slide {
|
||||
flex: 0 0 var(--frigate-card-viewer-thumbnail-size);
|
||||
opacity: 0.4;
|
||||
|
||||
@@ -4,75 +4,14 @@
|
||||
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
--video-max-height: none;
|
||||
--frigate-card-viewer-thumbnail-size: 100px;
|
||||
}
|
||||
|
||||
img,video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.embla {
|
||||
position: relative;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
// Master containers, thumbnails are a fixed size and the main viewer panel
|
||||
// should grow/shrink accordingly.
|
||||
.embla {
|
||||
frigate-card-media-carousel {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
frigate-card-thumbnail-carousel {
|
||||
flex: 0 0 var(--frigate-card-viewer-thumbnail-size);
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.embla__container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
user-select: none;
|
||||
-webkit-touch-callout: none;
|
||||
-khtml-user-select: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.embla__viewport {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.embla__viewport.is-draggable {
|
||||
cursor: move;
|
||||
cursor: grab;
|
||||
}
|
||||
.embla__viewport.is-dragging {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.embla__slide {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
margin-right: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.embla__slide {
|
||||
flex: 0 0 100%;
|
||||
}
|
||||
.embla__slide img,video {
|
||||
// Letterbox media. <frigate-card-ha-hls-player> has similar added directly in
|
||||
// its element.
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
frigate-card-ha-hls-player {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
Reference in New Issue
Block a user