Implement adaptive height to support live cameras with different dimensions.
This commit is contained in:
@@ -1,8 +1,10 @@
|
|||||||
// TODO lazy loading
|
|
||||||
// TODO media events
|
|
||||||
// TODO height adapting
|
// TODO height adapting
|
||||||
// TODO can height adapting remove need for 16x9 dummy in the media carousel?
|
// TODO can height adapting remove need for 16x9 dummy in the media carousel?
|
||||||
// TODO controls
|
// TODO controls
|
||||||
|
// TODO lazy loading configuration
|
||||||
|
// TODO editor
|
||||||
|
// TODO readme
|
||||||
|
// TODO search for TODOs
|
||||||
|
|
||||||
import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit';
|
import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit';
|
||||||
import type {
|
import type {
|
||||||
|
|||||||
@@ -13,10 +13,9 @@ import './next-prev-control.js';
|
|||||||
|
|
||||||
import mediaCarouselStyle from '../scss/media-carousel.scss';
|
import mediaCarouselStyle from '../scss/media-carousel.scss';
|
||||||
|
|
||||||
// TODO Remove this if not needed (and below)
|
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`;
|
export const IMG_EMPTY = getEmptyImageSrc(16, 9);
|
||||||
// const IMG_EMPTY = getEmptyImageSrc(16, 9);
|
|
||||||
|
|
||||||
@customElement('frigate-card-media-carousel')
|
@customElement('frigate-card-media-carousel')
|
||||||
export class FrigateCardMediaCarousel extends FrigateCardCarousel {
|
export class FrigateCardMediaCarousel extends FrigateCardCarousel {
|
||||||
@@ -47,18 +46,49 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel {
|
|||||||
// Necessary because typescript local type narrowing is not paying attention
|
// Necessary because typescript local type narrowing is not paying attention
|
||||||
// to the side-effect of the call to super._loadCarousel().
|
// to the side-effect of the call to super._loadCarousel().
|
||||||
const carousel = this._carousel as EmblaCarouselType | undefined;
|
const carousel = this._carousel as EmblaCarouselType | undefined;
|
||||||
|
|
||||||
|
// Update the view object as the carousel is moved.
|
||||||
carousel?.on('select', this._selectSlideSetViewHandler.bind(this));
|
carousel?.on('select', this._selectSlideSetViewHandler.bind(this));
|
||||||
|
|
||||||
|
// Dispatch MediaShow events as the carousel is moved.
|
||||||
carousel?.on('init', this._selectSlideMediaShowHandler.bind(this));
|
carousel?.on('init', this._selectSlideMediaShowHandler.bind(this));
|
||||||
carousel?.on('select', this._selectSlideMediaShowHandler.bind(this));
|
carousel?.on('select', this._selectSlideMediaShowHandler.bind(this));
|
||||||
|
|
||||||
|
// Adapt the height of the container to the media as the carousel is moved.
|
||||||
|
carousel?.on('init', this._adaptiveHeightHandler.bind(this));
|
||||||
|
carousel?.on('select', this._adaptiveHeightHandler.bind(this));
|
||||||
|
carousel?.on('resize', this._adaptiveHeightHandler.bind(this));
|
||||||
|
|
||||||
if (this._getLazyLoadCount() != null) {
|
if (this._getLazyLoadCount() != null) {
|
||||||
|
// Load media as the carousel is moved (if lazy loading is in use).
|
||||||
carousel?.on('init', this._lazyLoadMediaHandler.bind(this));
|
carousel?.on('init', this._lazyLoadMediaHandler.bind(this));
|
||||||
carousel?.on('select', this._lazyLoadMediaHandler.bind(this));
|
carousel?.on('select', this._lazyLoadMediaHandler.bind(this));
|
||||||
carousel?.on('resize', this._lazyLoadMediaHandler.bind(this));
|
carousel?.on('resize', this._lazyLoadMediaHandler.bind(this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapt the height of the container to the height of the media (for cases
|
||||||
|
* where the carousel has different media heights, e.g. live cameras with
|
||||||
|
* different aspect ratios).
|
||||||
|
*/
|
||||||
|
protected _adaptiveHeightHandler(): void {
|
||||||
|
if (!this._carousel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const slides = this._carousel.slideNodes();
|
||||||
|
const heights = this._carousel.slidesInView(true).map((index) => {
|
||||||
|
const firstChild = slides[index].querySelector("*");
|
||||||
|
return firstChild ? firstChild.getBoundingClientRect().height : 0;
|
||||||
|
})
|
||||||
|
const targetHeight = Math.max(...heights);
|
||||||
|
if (targetHeight > 0) {
|
||||||
|
this._carousel.containerNode().style.maxHeight = `${targetHeight}px`;
|
||||||
|
} else {
|
||||||
|
this._carousel.containerNode().style.removeProperty('max-height')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle the user selecting a new slide in the carousel.
|
* Handle the user selecting a new slide in the carousel.
|
||||||
*/
|
*/
|
||||||
@@ -173,33 +203,40 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel {
|
|||||||
if (this._carousel && this._carousel?.slidesInView(true).includes(slideIndex)) {
|
if (this._carousel && this._carousel?.slidesInView(true).includes(slideIndex)) {
|
||||||
dispatchExistingMediaShowInfoAsEvent(this, mediaShowInfo);
|
dispatchExistingMediaShowInfoAsEvent(this, mediaShowInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// After media has been loaded, the height of the container may need to be
|
||||||
|
// re-adjusted.
|
||||||
|
this._adaptiveHeightHandler();
|
||||||
/**
|
/**
|
||||||
* Images need a width/height from initial load, and browsers will assume
|
* Images need a width/height from initial load, and browsers will assume
|
||||||
* that the aspect ratio of the initial dummy-image load will persist. In
|
* that the aspect ratio of the initial dummy-image load will persist. In
|
||||||
* lazy-loading, this can cause a 1x1 pixel dummy image to cause the
|
* lazy-loading, this can cause a 1x1 pixel dummy image to cause the
|
||||||
* browser to assume all images will be square, so the whole carousel will
|
* browser to assume all images will be square, so the whole carousel will
|
||||||
* have the wrong aspect-ratio until every single image has been lazily
|
* have the wrong aspect-ratio until every single image has been lazily
|
||||||
* loaded. To avoid this, we use a 16:9 dummy image at first (most
|
* loaded. Adaptive height helps in that the carousel gets resized on each
|
||||||
|
* img display to the correct size, but it still causes a minor noticeable
|
||||||
|
* flicker until the height change is complete.
|
||||||
|
*
|
||||||
|
* To avoid this, we use a 16:9 dummy image at first (most
|
||||||
* likely?) and once the first piece of real media has been loaded, all
|
* likely?) and once the first piece of real media has been loaded, all
|
||||||
* dummy images are replaced with dummy images that match the aspect ratio
|
* dummy images are replaced with dummy images that match the aspect ratio
|
||||||
* of the real image. It still might be wrong, but it's the best option
|
* of the real image. It still might be wrong, but it's the best option
|
||||||
* available.
|
* available.
|
||||||
*/
|
*/
|
||||||
// TODO remove this
|
const firstMediaLoad = !Object.keys(this._mediaShowInfo).length;
|
||||||
// const firstMediaLoad = !Object.keys(this._mediaShowInfo).length;
|
if (firstMediaLoad && this._getLazyLoadCount() != null) {
|
||||||
// if (firstMediaLoad && this.viewerConfig.lazy_load) {
|
const replacementImageSrc = getEmptyImageSrc(
|
||||||
// const replacementImageSrc = getEmptyImageSrc(
|
mediaShowInfo.width,
|
||||||
// mediaShowInfo.width,
|
mediaShowInfo.height,
|
||||||
// mediaShowInfo.height,
|
);
|
||||||
// );
|
|
||||||
|
|
||||||
// this.renderRoot.querySelectorAll('.embla__container img').forEach((img) => {
|
this.renderRoot.querySelectorAll('.embla__container img').forEach((img) => {
|
||||||
// const imageElement: HTMLImageElement = img as HTMLImageElement;
|
const imageElement = img as HTMLImageElement;
|
||||||
// if (imageElement.src === IMG_EMPTY) {
|
if (imageElement.src === IMG_EMPTY) {
|
||||||
// imageElement.src = replacementImageSrc;
|
imageElement.src = replacementImageSrc;
|
||||||
// }
|
}
|
||||||
// });
|
});
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import type {
|
|||||||
MediaShowInfo,
|
MediaShowInfo,
|
||||||
ViewerConfig,
|
ViewerConfig,
|
||||||
} from '../types.js';
|
} from '../types.js';
|
||||||
import { FrigateCardMediaCarousel } from './media-carousel.js';
|
import { FrigateCardMediaCarousel, IMG_EMPTY } from './media-carousel.js';
|
||||||
import { FrigateCardThumbnailCarousel, ThumbnailCarouselTap } from './thumbnail-carousel.js';
|
import { FrigateCardThumbnailCarousel, ThumbnailCarouselTap } 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';
|
||||||
@@ -35,10 +35,6 @@ import './next-prev-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';
|
||||||
|
|
||||||
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`;
|
|
||||||
const IMG_EMPTY = getEmptyImageSrc(16, 9);
|
|
||||||
|
|
||||||
@customElement('frigate-card-viewer')
|
@customElement('frigate-card-viewer')
|
||||||
export class FrigateCardViewer extends LitElement {
|
export class FrigateCardViewer extends LitElement {
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
|
|||||||
Reference in New Issue
Block a user