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);
|
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.
|
* The updated lifecycle callback for this element.
|
||||||
* @param changedProperties The properties that were changed in this render.
|
* @param changedProperties The properties that were changed in this render.
|
||||||
@@ -61,16 +69,14 @@ export class FrigateCardCarousel extends LitElement {
|
|||||||
'.embla__viewport',
|
'.embla__viewport',
|
||||||
) as HTMLElement;
|
) as HTMLElement;
|
||||||
|
|
||||||
if (carouselNode && !this._carousel) {
|
if (!this._carousel && carouselNode) {
|
||||||
this._carousel = EmblaCarousel(carouselNode, this._options);
|
this._carousel = EmblaCarousel(carouselNode, this._options);
|
||||||
this._carousel.on('init', () => dispatchFrigateCardEvent(this, 'carousel:init'));
|
this._carousel.on('init', () => dispatchFrigateCardEvent(this, 'carousel:init'));
|
||||||
this._carousel.on('resize', () =>
|
|
||||||
dispatchFrigateCardEvent(this, 'carousel:resize'),
|
|
||||||
);
|
|
||||||
this._carousel.on('select', () => {
|
this._carousel.on('select', () => {
|
||||||
if (this._carousel) {
|
const selected = this.carouselSelected();
|
||||||
|
if (selected !== undefined) {
|
||||||
dispatchFrigateCardEvent<CarouselSelect>(this, 'carousel:select', {
|
dispatchFrigateCardEvent<CarouselSelect>(this, 'carousel:select', {
|
||||||
index: this._carousel.selectedScrollSnap(),
|
index: selected,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export class FrigateCardThumbnailCarousel extends FrigateCardCarousel {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
protected target?: BrowseMediaSource;
|
protected target?: BrowseMediaSource;
|
||||||
|
|
||||||
protected _tapSelected? = 0;
|
protected _tapSelected?;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|||||||
+82
-56
@@ -4,10 +4,9 @@ import {
|
|||||||
TemplateResult,
|
TemplateResult,
|
||||||
html,
|
html,
|
||||||
unsafeCSS,
|
unsafeCSS,
|
||||||
PropertyValues,
|
|
||||||
} from 'lit';
|
} from 'lit';
|
||||||
import { BrowseMediaUtil } from '../browse-media-util.js';
|
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 { HomeAssistant } from 'custom-card-helpers';
|
||||||
import { createRef, Ref, ref } from 'lit/directives/ref.js';
|
import { createRef, Ref, ref } from 'lit/directives/ref.js';
|
||||||
import { customElement, property } from 'lit/decorators.js';
|
import { customElement, property } from 'lit/decorators.js';
|
||||||
@@ -22,7 +21,7 @@ import type {
|
|||||||
MediaShowInfo,
|
MediaShowInfo,
|
||||||
ViewerConfig,
|
ViewerConfig,
|
||||||
} from '../types.js';
|
} from '../types.js';
|
||||||
import { CarouselTap } from './carousel.js';
|
import { CarouselTap, FrigateCardCarousel } from './carousel.js';
|
||||||
import { FrigateCardThumbnailCarousel } from './thumbnail-carousel.js';
|
import { FrigateCardThumbnailCarousel } from './thumbnail-carousel.js';
|
||||||
import { ResolvedMediaCache, ResolvedMediaUtil } from '../resolved-media.js';
|
import { ResolvedMediaCache, ResolvedMediaUtil } from '../resolved-media.js';
|
||||||
import { View } from '../view.js';
|
import { View } from '../view.js';
|
||||||
@@ -44,6 +43,7 @@ import './thumbnail-carousel.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';
|
||||||
|
import mediaCarouselStyle from '../scss/media-carousel.scss';
|
||||||
|
|
||||||
const getEmptyImageSrc = (width: number, height: number) =>
|
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`;
|
`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 })
|
@property({ attribute: false })
|
||||||
protected resolvedMediaCache?: ResolvedMediaCache;
|
protected resolvedMediaCache?: ResolvedMediaCache;
|
||||||
|
|
||||||
// Media carousel object.
|
protected _mediaCarouselRef: Ref<FrigateCardMediaCarousel> = createRef();
|
||||||
protected _carousel?: EmblaCarouselType;
|
|
||||||
protected _loadedCarousel = false;
|
|
||||||
|
|
||||||
protected _thumbnailCarouselRef: Ref<FrigateCardThumbnailCarousel> = 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 #.
|
// Mapping of slide # to BrowseMediaSource child #.
|
||||||
// (Folders are not media items that can be rendered).
|
// (Folders are not media items that can be rendered).
|
||||||
protected _slideToChild: Record<number, number> = {};
|
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.
|
// Whether or not a given slide has been successfully lazily loaded.
|
||||||
protected _slideHasBeenLazyLoaded: Record<number, boolean> = {};
|
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).
|
* Load the carousel with "slides" (clips or snapshots).
|
||||||
*/
|
*/
|
||||||
protected _loadCarousel(): void {
|
protected _loadCarousel(): void {
|
||||||
const carouselNode = this.renderRoot.querySelector(
|
if (this._carousel || !this.viewerConfig) {
|
||||||
'.embla__viewport',
|
return;
|
||||||
) as HTMLElement;
|
}
|
||||||
|
|
||||||
if (carouselNode && this.viewerConfig) {
|
|
||||||
this._loadedCarousel = true;
|
|
||||||
|
|
||||||
// Start the carousel on the selected child number.
|
// Start the carousel on the selected child number.
|
||||||
const startIndex = Number(
|
const startIndex = Number(
|
||||||
@@ -219,32 +258,27 @@ export class FrigateCardViewerCore extends LitElement {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
this._carousel = EmblaCarousel(carouselNode, {
|
this._options = {
|
||||||
startIndex: isNaN(startIndex) ? undefined : startIndex,
|
startIndex: isNaN(startIndex) ? undefined : startIndex,
|
||||||
draggable: this.viewerConfig.draggable,
|
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.
|
// Update views and dispatch media-show events based on slide selections.
|
||||||
this._carousel.on('select', this._selectSlideSetViewHandler.bind(this));
|
carousel?.on('select', this._selectSlideSetViewHandler.bind(this));
|
||||||
this._carousel.on('select', this._selectSlideMediaShowHandler.bind(this));
|
carousel?.on('select', this._selectSlideMediaShowHandler.bind(this));
|
||||||
|
|
||||||
// Lazily load media that is displayed. These handlers are registered
|
// Lazily load media that is displayed. These handlers are registered
|
||||||
// regardless of the value of this.lazyLoad to allow that value to change
|
// regardless of the value of this.lazyLoad to allow that value to change
|
||||||
// after the carousel has been initialized.
|
// after the carousel has been initialized.
|
||||||
this._carousel.on('init', this._lazyLoadMediaHandler.bind(this));
|
carousel?.on('init', this._lazyLoadMediaHandler.bind(this));
|
||||||
this._carousel.on('select', this._lazyLoadMediaHandler.bind(this));
|
carousel?.on('select', this._lazyLoadMediaHandler.bind(this));
|
||||||
this._carousel.on('resize', this._lazyLoadMediaHandler.bind(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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -532,15 +566,7 @@ export class FrigateCardViewerCore extends LitElement {
|
|||||||
}}
|
}}
|
||||||
></frigate-card-next-previous-control>`
|
></frigate-card-next-previous-control>`
|
||||||
: ``}
|
: ``}
|
||||||
</div>
|
</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>`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -722,6 +748,6 @@ export class FrigateCardViewerCore extends LitElement {
|
|||||||
* Get element styles.
|
* Get element styles.
|
||||||
*/
|
*/
|
||||||
static get styles(): CSSResultGroup {
|
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 {
|
.embla__slide {
|
||||||
flex: 0 0 var(--frigate-card-viewer-thumbnail-size);
|
flex: 0 0 var(--frigate-card-viewer-thumbnail-size);
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
|
|||||||
@@ -4,75 +4,14 @@
|
|||||||
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
--video-max-height: none;
|
|
||||||
--frigate-card-viewer-thumbnail-size: 100px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
img,video {
|
frigate-card-media-carousel {
|
||||||
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 {
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
frigate-card-thumbnail-carousel {
|
frigate-card-thumbnail-carousel {
|
||||||
flex: 0 0 var(--frigate-card-viewer-thumbnail-size);
|
flex: 0 0 var(--frigate-card-viewer-thumbnail-size);
|
||||||
margin-top: 5px;
|
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