Change view to null for unspecified attributes.

This commit is contained in:
Dermot Duffy
2022-04-08 19:54:58 -07:00
parent 9c372815f2
commit f20bd8202f
9 changed files with 109 additions and 94 deletions
+6 -2
View File
@@ -381,7 +381,11 @@ export class FrigateCard extends LitElement {
});
}
if (this._getConfig().menu.buttons.download && this._view?.isViewerView()) {
if (
this._getConfig().menu.buttons.download &&
(this._view?.isViewerView() || this._view?.is('timeline') &&
!!this._view?.media)
) {
buttons.push({
type: 'custom:frigate-card-menu-icon',
title: localize('config.menu.buttons.download'),
@@ -743,7 +747,7 @@ export class FrigateCard extends LitElement {
* Download media being displayed in the viewer.
*/
protected async _downloadViewerMedia(): Promise<void> {
if (!this._hass || !this._view?.isViewerView()) {
if (!this._hass || !(this._view?.isViewerView() || this._view?.is('timeline'))) {
// Should not occur.
return;
}
-1
View File
@@ -214,7 +214,6 @@ export class FrigateCardGalleryCore extends LitElement {
.evolve({
view: this.view.is('clips') ? 'clip' : 'snapshot',
childIndex: index,
previous: this.view,
})
.dispatchChangeEvent(this);
}
+4 -1
View File
@@ -357,7 +357,10 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel {
this.view
.evolve({
camera: Array.from(this.cameras.keys())[selectedSnap],
previous: this.view,
// Reset the target so thumbnails will be re-fetched.
target: null,
childIndex: null,
})
.dispatchChangeEvent(this);
}
+27 -24
View File
@@ -60,7 +60,7 @@ export class FrigateCardSurround extends LitElement {
view = view as Readonly<View>;
browseMediaParams = browseMediaParams as BrowseMediaQueryParameters;
if (!hass || !view || !browseMediaParams) {
if (!hass || !view || view.target || !browseMediaParams) {
return;
}
let parent: FrigateBrowseMediaSource | null;
@@ -74,12 +74,20 @@ export class FrigateCardSurround extends LitElement {
?.evolve({
...(this.targetView && { view: this.targetView }),
target: parent,
childIndex: undefined,
childIndex: null,
})
.dispatchChangeEvent(this);
}
}
/**
* Determine if a drawer is being used.
* @returns `true` if a drawer is used, `false` otherwise.
*/
protected _hasDrawer(): boolean {
return !!this.config && ['left', 'right'].includes(this.config.mode);
}
/**
* Master render method.
* @returns A rendered template.
@@ -89,19 +97,22 @@ export class FrigateCardSurround extends LitElement {
return;
}
const changeDrawer = (ev: CustomEvent, action: 'open' | 'close') => {
// The event catch/re-dispatch below protect encapsulation: Catches the
// request to view thumbnails and re-dispatches a request to open the drawer
// (if the thumbnails are in a drawer). The new event needs to be dispatched
// from the origin of the inbound event, so it can be handled by
// <frigate-card-surround> .
if (this.config && this._hasDrawer()) {
dispatchFrigateCardEvent(ev.composedPath()[0], 'drawer:' + action, {
drawer: this.config.mode,
});
}
};
return html` <frigate-card-surround
@frigate-card:thumbnails:open=${(ev: CustomEvent) => {
if (this.config && ['left', 'right'].includes(this.config.mode)) {
// Protects encapsulation: Catches the request to view thumbnails and
// re-dispatches a request to open the drawer (if the thumbnails are
// in a drawer). The new event needs to be dispatched from the origin
// of the inbound event, so it can be handled by
// <frigate-card-surround> .
dispatchFrigateCardEvent(ev.composedPath()[0], 'drawer:open', {
drawer: this.config.mode,
});
}
}}
@frigate-card:thumbnails:open=${(ev: CustomEvent) => changeDrawer(ev, 'open')}
@frigate-card:thumbnails:close=${(ev: CustomEvent) => changeDrawer(ev, 'close')}
>
${this.config?.mode !== 'none'
? html` <frigate-card-thumbnail-carousel
@@ -109,16 +120,8 @@ export class FrigateCardSurround extends LitElement {
.config=${this.config}
.view=${this.view}
.target=${this.view.target}
.selected=${this.view.childIndex ?? null}
@frigate-card:change-view=${(ev) => {
// Close the drawer if the carousel or thumbnail requests a view change
// (e.g. playing the clip, or viewing something on the timeline).
if (this.config && ['left', 'right'].includes(this.config.mode)) {
dispatchFrigateCardEvent(ev.composedPath()[0], 'drawer:close', {
drawer: this.config.mode,
});
}
}}
.selected=${this.view.childIndex}
@frigate-card:change-view=${(ev: CustomEvent) => changeDrawer(ev, 'close')}
@frigate-card:carousel:tap=${(ev: CustomEvent<ThumbnailCarouselTap>) => {
this.view
?.evolve({
+1 -1
View File
@@ -36,7 +36,7 @@ export class FrigateCardThumbnailCarousel extends FrigateCardCarousel {
// Use contentsChanged here to avoid the carousel rebuilding and resetting in
// front of the user, unless the contents have actually changed.
@property({ attribute: false, hasChanged: contentsChanged })
public target?: FrigateBrowseMediaSource;
public target?: FrigateBrowseMediaSource | null;
// Thumbnail carousels can expand (e.g. drawer-based carousels after the main
// media loads). The carousel must be re-initialized in these cases, or the
+1 -1
View File
@@ -90,7 +90,7 @@ export class FrigateCardThumbnail extends LitElement {
?.evolve({
view: 'timeline',
target: this.target,
childIndex: this.childIndex,
childIndex: this.childIndex ?? null,
context: {},
})
.dispatchChangeEvent(this);
+24 -22
View File
@@ -1,8 +1,6 @@
// TODO: Clips vs snapshots: Should be able to navigate from snapshots view and it should just work.
// TODO: Hover over an event should show something useful.
// TODO: Periodically refetch events.
// TODO: Search for TODOs and logging statements.
// TODO: Allow download of selected event in timeline.
import {
CSSResultGroup,
@@ -211,9 +209,8 @@ class TimelineEventManager {
if (!cameraConfig || !this._dateStart || !this._dateEnd) {
return;
}
const browseMediaQueryParametersBase = BrowseMediaUtil.getBrowseMediaQueryParametersBase(
cameraConfig,
);
const browseMediaQueryParametersBase =
BrowseMediaUtil.getBrowseMediaQueryParametersBase(cameraConfig);
if (!browseMediaQueryParametersBase) {
return;
}
@@ -384,20 +381,27 @@ export class FrigateCardTimelineCore extends LitElement {
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
protected _timelineSelectHandler(data: { items: string[]; event: Event }): void {
if (!this._thumbnails || !this._thumbnails.children || data.items.length <= 0) {
if (!this._thumbnails || !this._thumbnails.children) {
return;
}
const childIndex = this._thumbnails.children.findIndex(
(child) => child.frigate?.event.id === data.items[0],
);
if (childIndex >= 0) {
this.view
?.evolve({
target: this._thumbnails,
childIndex: childIndex,
})
.dispatchChangeEvent(this);
const childIndex = data.items.length
? this._thumbnails.children.findIndex(
(child) => child.frigate?.event.id === data.items[0],
)
: null;
this.view
?.evolve({
target: this._thumbnails,
childIndex: childIndex,
})
.dispatchChangeEvent(this);
if (childIndex !== null && childIndex >= 0) {
dispatchFrigateCardEvent(this, 'thumbnails:open');
} else {
dispatchFrigateCardEvent(this, 'thumbnails:close');
}
}
@@ -428,12 +432,12 @@ export class FrigateCardTimelineCore extends LitElement {
['all', 'snapshots'].includes(this.timelineConfig.media) &&
BrowseMediaUtil.isTrueMedia(item.snapshot)
) {
added = true
added = true;
children.push(item.snapshot);
}
if (added && selected.includes(item.event.id)) {
childIndex = children.length-1;
childIndex = children.length - 1;
}
}
});
@@ -459,7 +463,7 @@ export class FrigateCardTimelineCore extends LitElement {
this.view
?.evolve({
target: this._thumbnails,
childIndex: childIndex < 0 ? undefined : childIndex,
childIndex: childIndex < 0 ? null : childIndex,
})
.dispatchChangeEvent(this);
}
@@ -642,9 +646,7 @@ export class FrigateCardTimelineCore extends LitElement {
});
const timelineWindow = this._timeline.getWindow();
const context = this.view.context
? (this.view.context as TimelineViewContext)
: undefined;
const context = this.view.context as TimelineViewContext | null;
if (context?.window) {
console.info(
+19 -17
View File
@@ -145,11 +145,11 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
// A task to resolve target media if lazy loading is disabled.
protected _mediaResolutionTask = new Task<
[FrigateBrowseMediaSource | undefined],
[FrigateBrowseMediaSource | null | undefined],
void
>(
this,
async ([target]: (FrigateBrowseMediaSource | undefined)[]): Promise<void> => {
async ([target]: (FrigateBrowseMediaSource | null | undefined)[]): Promise<void> => {
for (
let i = 0;
!this.viewerConfig?.lazy_load &&
@@ -183,12 +183,12 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
if (this._carousel && changedProperties.has('view')) {
const oldView = changedProperties.get('view') as View | undefined;
if (oldView) {
if (oldView.target != this.view?.target) {
if (oldView.target !== this.view?.target) {
// If the media target is different entirely, reset the carousel.
this._destroyCarousel();
} else if (this.view?.childIndex != oldView.childIndex) {
const slide = this._getSlideForChild(this.view?.childIndex);
if (slide !== undefined && slide !== this.carouselSelected()) {
} else if (this.view.childIndex != oldView.childIndex) {
const slide = this._getSlideForChild(this.view.childIndex);
if (slide !== null && slide !== this.carouselSelected()) {
// If the media target is the same as already loaded, but isn't of
// the selected slide, scroll to that slide.
this.carouselScrollTo(slide);
@@ -226,14 +226,19 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
// need to be destroyed here.
}
protected _getSlideForChild(childIndex: number | undefined): number | undefined {
if (childIndex === undefined) {
return undefined;
/**
* Get the slide number given a media child number.
* @param childIndex The child index (relative to `view.target`)
* @returns A number or null if the child is not found.
*/
protected _getSlideForChild(childIndex: number | null | undefined): number | null {
if (childIndex === undefined || childIndex === null) {
return null;
}
const slideIndex = Object.keys(this._slideToChild).find(
(key) => this._slideToChild[key] === childIndex,
);
return slideIndex !== undefined ? Number(slideIndex) : undefined;
return slideIndex !== undefined ? Number(slideIndex) : null;
}
/**
@@ -251,7 +256,7 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
protected _getOptions(): EmblaOptionsType {
return {
// Start the carousel on the selected child number.
startIndex: this._getSlideForChild(this.view?.childIndex),
startIndex: this._getSlideForChild(this.view?.childIndex) ?? undefined,
draggable: this.viewerConfig?.draggable,
};
}
@@ -286,7 +291,7 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
!this.view ||
!this.view.target ||
!this.view.target.children ||
this.view.childIndex === undefined
this.view.childIndex === null
) {
return null;
}
@@ -398,12 +403,10 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
}
const clipStartTime = BrowseMediaUtil.getEventStartTime(child);
if (clipStartTime && clipStartTime === snapshotStartTime) {
return new View({
return this.view.evolve({
view: 'clip',
camera: this.view.camera,
target: clips,
childIndex: i,
previous: this.view,
});
}
}
@@ -426,7 +429,6 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
this.view
.evolve({
childIndex: childIndex,
previous: this.view,
})
.dispatchChangeEvent(this);
}
@@ -443,7 +445,7 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
const childIndex: number | undefined = this._slideToChild[index];
if (
childIndex == undefined ||
childIndex === undefined ||
!this.hass ||
!this.view ||
!this.view.target ||
+27 -25
View File
@@ -7,10 +7,10 @@ export interface ViewContext {}
export interface ViewEvolveParameters {
view?: FrigateCardView;
camera?: string;
target?: FrigateBrowseMediaSource;
childIndex?: number;
previous?: View;
context?: ViewContext;
target?: FrigateBrowseMediaSource | null;
childIndex?: number | null;
previous?: View | null;
context?: ViewContext | null;
}
export interface ViewParameters extends ViewEvolveParameters {
@@ -21,18 +21,18 @@ export interface ViewParameters extends ViewEvolveParameters {
export class View {
view: FrigateCardView;
camera: string;
target?: FrigateBrowseMediaSource;
childIndex?: number;
previous?: View;
context?: ViewContext;
target: FrigateBrowseMediaSource | null;
childIndex: number | null;
previous: View | null;
context: ViewContext | null;
constructor(params: ViewParameters) {
this.view = params?.view;
this.camera = params?.camera;
this.target = params?.target;
this.childIndex = params?.childIndex;
this.previous = params?.previous;
this.context = params?.context;
this.view = params.view;
this.camera = params.camera;
this.target = params.target ?? null;
this.childIndex = params.childIndex ?? null;
this.previous = params.previous ?? null;
this.context = params.context ?? null;
}
/**
@@ -56,12 +56,15 @@ export class View {
*/
public evolve(params: ViewEvolveParameters): View {
return new View({
view: params.view ?? this.view,
camera: params.camera ?? this.camera,
target: params.target ?? this.target,
childIndex: params.childIndex ?? this.childIndex,
previous: params.previous ?? this.previous,
context: params.context ?? this.context,
view: params.view !== undefined ? params.view : this.view,
camera: params.camera !== undefined ? params.camera : this.camera,
target: params.target !== undefined ? params.target : this.target,
childIndex: params.childIndex !== undefined ? params.childIndex : this.childIndex,
context: params.context !== undefined ? params.context : this.context,
// Special case: Set the previous to this of the evolved view (rather than
// the previous of this).
previous: params.previous !== undefined ? params.previous : this,
});
}
@@ -110,14 +113,13 @@ export class View {
/**
* Get the media item that should be played.
**/
get media(): FrigateBrowseMediaSource | undefined {
get media(): FrigateBrowseMediaSource | null {
if (this.target) {
if (this.target.children && this.childIndex !== undefined) {
return this.target.children[this.childIndex];
if (this.target.children && this.childIndex !== null) {
return this.target.children[this.childIndex] ?? null;
}
return this.target;
}
return undefined;
return null;
}
/**