Ensure container resizes when parent resizes.

This commit is contained in:
Dermot Duffy
2022-10-01 23:49:35 -07:00
parent b4879b53df
commit 3c247b6320
4 changed files with 134 additions and 111 deletions
+38 -31
View File
@@ -22,6 +22,7 @@ import './next-prev-control.js';
import './carousel.js'; import './carousel.js';
import { FrigateCardNextPreviousControl } from './next-prev-control.js'; import { FrigateCardNextPreviousControl } from './next-prev-control.js';
import { FrigateCardTitleControl } from './title-control.js'; import { FrigateCardTitleControl } from './title-control.js';
import { debounce } from 'lodash-es';
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`;
@@ -143,10 +144,14 @@ export class FrigateCardMediaCarousel extends LitElement {
protected _boundAutoPlayHandler = this.autoPlay.bind(this); protected _boundAutoPlayHandler = this.autoPlay.bind(this);
protected _boundAutoUnmuteHandler = this.autoUnmute.bind(this); protected _boundAutoUnmuteHandler = this.autoUnmute.bind(this);
protected _boundAdaptContainerHeightToSlide =
this._adaptContainerHeightToSlide.bind(this);
protected _boundTitleHandler = this._titleHandler.bind(this); protected _boundTitleHandler = this._titleHandler.bind(this);
// Debounce multiple calls to adapt the container height.
protected _debouncedAdaptContainerHeightToSlide = debounce(
this._adaptContainerHeightToSlide.bind(this),
1 * 100,
{trailing: true});
// This carousel may be resized by Lovelace resizes, window resizes, // This carousel may be resized by Lovelace resizes, window resizes,
// fullscreen, etc. Always call the adaptive height handler when the size // fullscreen, etc. Always call the adaptive height handler when the size
// changes. // changes.
@@ -272,10 +277,9 @@ export class FrigateCardMediaCarousel extends LitElement {
this.addEventListener('frigate-card:media:loaded', this._boundAutoUnmuteHandler); this.addEventListener('frigate-card:media:loaded', this._boundAutoUnmuteHandler);
this.addEventListener( this.addEventListener(
'frigate-card:media:loaded', 'frigate-card:media:loaded',
this._boundAdaptContainerHeightToSlide, this._debouncedAdaptContainerHeightToSlide,
); );
this.addEventListener('frigate-card:media:loaded', this._boundTitleHandler); this.addEventListener('frigate-card:media:loaded', this._boundTitleHandler);
this._resizeObserver.observe(this);
this._intersectionObserver.observe(this); this._intersectionObserver.observe(this);
} }
@@ -287,7 +291,7 @@ export class FrigateCardMediaCarousel extends LitElement {
this.removeEventListener('frigate-card:media:loaded', this._boundAutoUnmuteHandler); this.removeEventListener('frigate-card:media:loaded', this._boundAutoUnmuteHandler);
this.removeEventListener( this.removeEventListener(
'frigate-card:media:loaded', 'frigate-card:media:loaded',
this._boundAdaptContainerHeightToSlide, this._debouncedAdaptContainerHeightToSlide,
); );
this.removeEventListener('frigate-card:media:loaded', this._boundTitleHandler); this.removeEventListener('frigate-card:media:loaded', this._boundTitleHandler);
this._resizeObserver.disconnect(); this._resizeObserver.disconnect();
@@ -302,7 +306,7 @@ export class FrigateCardMediaCarousel extends LitElement {
*/ */
protected _reInitAndAdjustHeight(): void { protected _reInitAndAdjustHeight(): void {
this.frigateCardCarousel()?.carouselReInitWhenSafe(); this.frigateCardCarousel()?.carouselReInitWhenSafe();
this._adaptContainerHeightToSlide(); this._debouncedAdaptContainerHeightToSlide();
} }
/** /**
@@ -331,11 +335,10 @@ export class FrigateCardMediaCarousel extends LitElement {
* 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).
* *
* This component does not use the stock Embla auto-height plugin as it * This component does not use the stock Embla auto-height plugin as that
* resizes the container on selection rather than media load. * resizes the container only on selection rather than media load.
*/ */
protected _adaptContainerHeightToSlide(): void { protected _adaptContainerHeightToSlide(): void {
const adaptCarouselHeight = (): void => {
const selected = this.frigateCardCarousel()?.getCarouselSelected(); const selected = this.frigateCardCarousel()?.getCarouselSelected();
if (selected) { if (selected) {
this.style.removeProperty('max-height'); this.style.removeProperty('max-height');
@@ -344,23 +347,13 @@ export class FrigateCardMediaCarousel extends LitElement {
this.style.maxHeight = `${height}px`; this.style.maxHeight = `${height}px`;
} }
} }
};
// Hack: This method attempts to measure the height of the selected slide in
// order to set the overall carousel height to match. This method is
// triggered from `frigate-card:media:loaded` events, which are usually in
// turn triggered from media/metadata load events from media players.
// Sufficient time needs to be allowed after these metadata load events to
// allow the browser to repaint the element heights, so that we can get the
// right values here. requestAnimationFrame() works well for this.
window.requestAnimationFrame(adaptCarouselHeight);
} }
/** /**
* Fire a media show event when a slide is selected. * Fire a media show event when a slide is selected.
*/ */
protected _dispatchMediaLoadedInfo(): void { protected _dispatchMediaLoadedInfo(selected: CarouselSelect): void {
const slideIndex = this.frigateCardCarousel()?.getCarouselSelected()?.index; const slideIndex = selected.index;
if (slideIndex !== undefined && slideIndex in this._mediaLoadedInfo) { if (slideIndex !== undefined && slideIndex in this._mediaLoadedInfo) {
dispatchExistingMediaLoadedInfoAsEvent(this, this._mediaLoadedInfo[slideIndex]); dispatchExistingMediaLoadedInfoAsEvent(this, this._mediaLoadedInfo[slideIndex]);
} }
@@ -404,26 +397,40 @@ export class FrigateCardMediaCarousel extends LitElement {
} }
protected render(): TemplateResult | void { protected render(): TemplateResult | void {
return html` <frigate-card-carousel const selectSlide = (ev?: CustomEvent<CarouselSelect>): void => {
${ref(this._refCarousel)}
.carouselOptions=${this.carouselOptions}
.carouselPlugins=${this.carouselPlugins}
transitionEffect=${ifDefined(this.transitionEffect)}
@frigate-card:carousel:init=${this._dispatchMediaLoadedInfo.bind(this)}
@frigate-card:carousel:select=${(ev: CustomEvent<CarouselSelect>) => {
this._slideResizeObserver.disconnect(); this._slideResizeObserver.disconnect();
this._slideResizeObserver.observe(ev.detail.element); const parent = this.getRootNode();
if (parent && parent instanceof ShadowRoot) {
this._slideResizeObserver.observe(parent.host);
}
const selected = ev ? ev.detail : this.frigateCardCarousel()?.getCarouselSelected();
if (selected) {
this._slideResizeObserver.observe(selected.element);
// Pass up the media-carousel select event first to allow parents to // Pass up the media-carousel select event first to allow parents to
// initialize/reset before the media info is dispatched. // initialize/reset before the media info is dispatched.
dispatchFrigateCardEvent<CarouselSelect>( dispatchFrigateCardEvent<CarouselSelect>(
this, this,
'media-carousel:select', 'media-carousel:select',
ev.detail, selected,
); );
// Dispatch media info. // Dispatch media info.
this._dispatchMediaLoadedInfo(); this._dispatchMediaLoadedInfo(selected);
}
}
return html` <frigate-card-carousel
${ref(this._refCarousel)}
.carouselOptions=${this.carouselOptions}
.carouselPlugins=${this.carouselPlugins}
transitionEffect=${ifDefined(this.transitionEffect)}
@frigate-card:carousel:init=${() => {
selectSlide();
}}
@frigate-card:carousel:select=${(ev: CustomEvent<CarouselSelect>) => {
selectSlide(ev);
}} }}
@frigate-card:carousel:media:loaded=${this._storeMediaLoadedInfo.bind(this)} @frigate-card:carousel:media:loaded=${this._storeMediaLoadedInfo.bind(this)}
@frigate-card:carousel:media:unloaded=${this._removeMediaLoadedInfo.bind(this)} @frigate-card:carousel:media:unloaded=${this._removeMediaLoadedInfo.bind(this)}
+17 -15
View File
@@ -328,7 +328,9 @@ export class FrigateCardTimelineCore extends LitElement {
if (!onlyShowMatchingHour || isMatchingHour) { if (!onlyShowMatchingHour || isMatchingHour) {
children.push( children.push(
createChild( createChild(
`${prettifyTitle(config.frigate.camera_name)} ${formatDateAndTime(hour)}`, `${prettifyTitle(config.frigate.camera_name)} ${formatDateAndTime(
hour,
)}`,
getRecordingMediaContentID({ getRecordingMediaContentID({
clientId: config.frigate.client_id, clientId: config.frigate.client_id,
year: dayData.day.getFullYear(), year: dayData.day.getFullYear(),
@@ -466,7 +468,6 @@ export class FrigateCardTimelineCore extends LitElement {
properties.event.additionalEvent !== 'pinchin' && properties.event.additionalEvent !== 'pinchin' &&
properties.event.additionalEvent !== 'pinchout' properties.event.additionalEvent !== 'pinchout'
) { ) {
const targetTime = this._pointerHeld?.window const targetTime = this._pointerHeld?.window
? add(properties.start, { ? add(properties.start, {
seconds: seconds:
@@ -683,7 +684,8 @@ export class FrigateCardTimelineCore extends LitElement {
if (properties.group) { if (properties.group) {
this._changeViewToRecording( this._changeViewToRecording(
properties.what === 'background' ? properties.time : window.end, properties.what === 'background' ? properties.time : window.end,
String(properties.group)); String(properties.group),
);
} else if (this.mini && this.view?.camera) { } else if (this.mini && this.view?.camera) {
// In mini mode group may not be displayed / used, so just use the camera directly. // In mini mode group may not be displayed / used, so just use the camera directly.
this._changeViewToRecording(window.end, this.view.camera); this._changeViewToRecording(window.end, this.view.camera);
@@ -816,10 +818,12 @@ export class FrigateCardTimelineCore extends LitElement {
: this._timeline.getSelection(); : this._timeline.getSelection();
let childIndex = -1; let childIndex = -1;
const children: FrigateBrowseMediaSource[] = []; const children: FrigateBrowseMediaSource[] = [];
this._dataview?.get({ this._dataview
?.get({
filter: (item) => item.type !== 'background', filter: (item) => item.type !== 'background',
order: sortTimelineItemsYoungestToOldest } order: sortTimelineItemsYoungestToOldest,
).forEach((item) => { })
.forEach((item) => {
const cameraID = item.group ? String(item.group) : null; const cameraID = item.group ? String(item.group) : null;
const cameraConfig = cameraID ? this.cameras?.get(cameraID) : null; const cameraConfig = cameraID ? this.cameras?.get(cameraID) : null;
const event = item.event; const event = item.event;
@@ -1053,13 +1057,7 @@ export class FrigateCardTimelineCore extends LitElement {
* Update the timeline from the view object. * Update the timeline from the view object.
*/ */
protected async _updateTimelineFromView(): Promise<void> { protected async _updateTimelineFromView(): Promise<void> {
if ( if (!this.hass || !this.cameras || !this.view || !this.timelineConfig) {
!this.hass ||
!this.cameras ||
!this.view ||
!this.timelineConfig ||
!this._timeline
) {
return; return;
} }
@@ -1091,7 +1089,7 @@ export class FrigateCardTimelineCore extends LitElement {
)); ));
} }
this._timeline.setSelection(event ? [event.id] : [], { this._timeline?.setSelection(event ? [event.id] : [], {
focus: false, focus: false,
animation: { animation: {
animation: false, animation: false,
@@ -1107,7 +1105,11 @@ export class FrigateCardTimelineCore extends LitElement {
this.timelineDataManager?.rewriteItem(event.id); this.timelineDataManager?.rewriteItem(event.id);
} }
if (!this._pointerHeld && !this.view.context?.timeline?.noSetWindow) { if (
!this._pointerHeld &&
!this.view.context?.timeline?.noSetWindow &&
this._timeline
) {
// Regenerate the thumbnails after the selection, to allow the new selection // Regenerate the thumbnails after the selection, to allow the new selection
// to be in the generated view. // to be in the generated view.
const context = this.view.context?.timeline; const context = this.view.context?.timeline;
+15 -7
View File
@@ -294,7 +294,10 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
value: 'icons', value: 'icons',
label: localize('config.common.controls.next_previous.styles.icons'), label: localize('config.common.controls.next_previous.styles.icons'),
}, },
{ value: 'none', label: localize('config.common.controls.next_previous.styles.none') }, {
value: 'none',
label: localize('config.common.controls.next_previous.styles.none'),
},
]; ];
protected _aspectRatioModes: EditorSelectOption[] = [ protected _aspectRatioModes: EditorSelectOption[] = [
@@ -336,7 +339,10 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
protected _thumbnailMedias: EditorSelectOption[] = [ protected _thumbnailMedias: EditorSelectOption[] = [
{ value: '', label: '' }, { value: '', label: '' },
{ value: 'clips', label: localize('config.common.controls.thumbnails.medias.clips') }, {
value: 'clips',
label: localize('config.common.controls.thumbnails.medias.clips'),
},
{ {
value: 'snapshots', value: 'snapshots',
label: localize('config.common.controls.thumbnails.medias.snapshots'), label: localize('config.common.controls.thumbnails.medias.snapshots'),
@@ -1388,7 +1394,9 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
${this._expandedMenus[MENU_OPTIONS] === 'cameras' ${this._expandedMenus[MENU_OPTIONS] === 'cameras'
? html` ? html`
<div class="values"> <div class="values">
${cameras.map((_, index) => this._renderCamera(cameras, index, entities))} ${cameras.map((_, index) =>
this._renderCamera(cameras, index, entities),
)}
${this._renderCamera(cameras, cameras.length, entities, true)} ${this._renderCamera(cameras, cameras.length, entities, true)}
</div> </div>
` `
@@ -1424,7 +1432,6 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
${this._renderNumberInput(CONF_MENU_BUTTON_SIZE, { ${this._renderNumberInput(CONF_MENU_BUTTON_SIZE, {
min: BUTTON_SIZE_MIN, min: BUTTON_SIZE_MIN,
})} })}
</div>
${this._renderMenuButton('frigate') /* */} ${this._renderMenuButton('frigate') /* */}
${this._renderMenuButton('cameras') /* */} ${this._renderMenuButton('cameras') /* */}
${this._renderMenuButton('live') /* */} ${this._renderMenuButton('live') /* */}
@@ -1436,6 +1443,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
${this._renderMenuButton('fullscreen')} ${this._renderMenuButton('fullscreen')}
${this._renderMenuButton('timeline')} ${this._renderMenuButton('timeline')}
${this._renderMenuButton('media_player')} ${this._renderMenuButton('media_player')}
</div>
` `
: ''} : ''}
${this._renderOptionSetHeader('live')} ${this._renderOptionSetHeader('live')}
@@ -1493,7 +1501,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
{ {
configPathMedia: CONF_LIVE_CONTROLS_THUMBNAILS_MEDIA, configPathMedia: CONF_LIVE_CONTROLS_THUMBNAILS_MEDIA,
configPathMode: CONF_LIVE_CONTROLS_THUMBNAILS_MODE, configPathMode: CONF_LIVE_CONTROLS_THUMBNAILS_MODE,
} },
)} )}
${this._renderTitleControls( ${this._renderTitleControls(
MENU_LIVE_CONTROLS_TITLE, MENU_LIVE_CONTROLS_TITLE,
@@ -1582,7 +1590,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL, CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL,
{ {
configPathMode: CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_MODE, configPathMode: CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_MODE,
} },
)} )}
${this._renderTitleControls( ${this._renderTitleControls(
MENU_MEDIA_VIEWER_CONTROLS_TITLE, MENU_MEDIA_VIEWER_CONTROLS_TITLE,
@@ -1641,7 +1649,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
CONF_TIMELINE_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL, CONF_TIMELINE_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL,
{ {
configPathMode: CONF_TIMELINE_CONTROLS_THUMBNAILS_MODE, configPathMode: CONF_TIMELINE_CONTROLS_THUMBNAILS_MODE,
} },
)} )}
</div>` </div>`
: ''} : ''}
+7 -1
View File
@@ -9,13 +9,19 @@
/* Don't drop shadow or have radius for nested webrtc card */ /* Don't drop shadow or have radius for nested webrtc card */
#webrtc ha-card { #webrtc ha-card {
border-radius: 0px; border-radius: 0px;
background-color: unset;
// To get the WebRTC player to line up correctly with some themes. // To get the WebRTC player to line up correctly with some themes.
margin: 0px; margin: 0px;
box-shadow: none; box-shadow: none;
} }
ha-card,
div.fix-safari,
#video {
background: unset;
background-color: unset;
}
#webrtc #video { #webrtc #video {
@include media-layout.media-layout(); @include media-layout.media-layout();
} }