Initial attempt add hiding elements during loading.

This commit is contained in:
Dermot Duffy
2022-08-07 13:20:03 -07:00
parent 561358f43b
commit c19a62d426
9 changed files with 179 additions and 65 deletions
+52 -37
View File
@@ -99,6 +99,7 @@ import { supportsFeature } from './utils/ha/update.js';
import { isValidMediaShowInfo } from './utils/media-info.js'; import { isValidMediaShowInfo } from './utils/media-info.js';
import { View } from './view.js'; import { View } from './view.js';
import pkg from '../package.json'; import pkg from '../package.json';
import { ViewContext } from 'view';
/** A note on media callbacks: /** A note on media callbacks:
* *
@@ -184,8 +185,9 @@ export class FrigateCard extends LitElement {
// Automated refreshes of the default view. // Automated refreshes of the default view.
protected _updateTimerID: number | null = null; protected _updateTimerID: number | null = null;
// Information about the most recently loaded media item. // Information about loaded media items.
protected _mediaShowInfo: MediaShowInfo | null = null; protected _currentMediaShowInfo: MediaShowInfo | null = null;
protected _lastValidMediaShowInfo: MediaShowInfo | null = null;
// Array of dynamic menu buttons to be added to menu. // Array of dynamic menu buttons to be added to menu.
protected _dynamicMenuButtons: MenuButton[] = []; protected _dynamicMenuButtons: MenuButton[] = [];
@@ -331,7 +333,11 @@ export class FrigateCard extends LitElement {
for (const action of actions) { for (const action of actions) {
// All frigate card actions will have action of 'fire-dom-event' and // All frigate card actions will have action of 'fire-dom-event' and
// styling only applies to those. // styling only applies to those.
if (!action || action.action !== 'fire-dom-event' || !('frigate_card_action' in action)) { if (
!action ||
action.action !== 'fire-dom-event' ||
!('frigate_card_action' in action)
) {
continue; continue;
} }
const frigateCardAction = action as FrigateCardCustomAction; const frigateCardAction = action as FrigateCardCustomAction;
@@ -925,6 +931,15 @@ export class FrigateCard extends LitElement {
} }
protected _changeView(args?: { view?: View; resetMessage?: boolean }): void { protected _changeView(args?: { view?: View; resetMessage?: boolean }): void {
const changeView = (view: View): void => {
if (View.isMediaChange(this._view, view)) {
this._currentMediaShowInfo = null;
}
this._view = view;
this._generateConditionState();
this._resetMainScroll();
};
if (args?.resetMessage ?? true) { if (args?.resetMessage ?? true) {
this._message = null; this._message = null;
} }
@@ -945,21 +960,19 @@ export class FrigateCard extends LitElement {
} }
if (camera) { if (camera) {
this._view = new View({ changeView(
view: this._getConfig().view.default, new View({
camera: camera, view: this._getConfig().view.default,
}); camera: camera,
this._generateConditionState(); }),
this._resetMainScroll(); );
// Restart the update timer, so the default view is refreshed at a fixed // Restart the update timer, so the default view is refreshed at a fixed
// interval from now (if so configured). // interval from now (if so configured).
this._startUpdateTimer(); this._startUpdateTimer();
} }
} else { } else {
this._view = args.view; changeView(args.view);
this._generateConditionState();
this._resetMainScroll();
} }
} }
@@ -987,6 +1000,15 @@ export class FrigateCard extends LitElement {
this._changeView({ view: e.detail }); this._changeView({ view: e.detail });
} }
/**
* Add view context to the current view.
* @param ev A ViewContext event.
*/
protected _addViewContextHandler(ev: CustomEvent<ViewContext>): void {
this._changeView({
view: this._view?.clone().mergeInContext(ev.detail),
});
}
/** /**
* Called before each update. * Called before each update.
*/ */
@@ -1592,7 +1614,7 @@ export class FrigateCard extends LitElement {
*/ */
protected _resetMainScroll(): void { protected _resetMainScroll(): void {
// Reset the scroll on the main div to the top. // Reset the scroll on the main div to the top.
this._refMain.value?.scroll({top: 0}); this._refMain.value?.scroll({ top: 0 });
} }
/** /**
@@ -1614,19 +1636,11 @@ export class FrigateCard extends LitElement {
if (!isValidMediaShowInfo(mediaShowInfo)) { if (!isValidMediaShowInfo(mediaShowInfo)) {
return; return;
} }
let requestRefresh = false;
if (
this._view?.isGalleryView() &&
(mediaShowInfo.width != this._mediaShowInfo?.width ||
mediaShowInfo.height != this._mediaShowInfo?.height)
) {
requestRefresh = true;
}
this._mediaShowInfo = mediaShowInfo; this._lastValidMediaShowInfo = this._currentMediaShowInfo = mediaShowInfo;
if (requestRefresh) {
this.requestUpdate(); // An update may be required to draw elements.
} this.requestUpdate();
} }
/** /**
@@ -1702,8 +1716,8 @@ export class FrigateCard extends LitElement {
} }
const aspectRatioMode = this._getConfig().dimensions.aspect_ratio_mode; const aspectRatioMode = this._getConfig().dimensions.aspect_ratio_mode;
if (aspectRatioMode == 'dynamic' && this._mediaShowInfo) { if (aspectRatioMode == 'dynamic' && this._lastValidMediaShowInfo) {
return `${this._mediaShowInfo.width} / ${this._mediaShowInfo.height}`; return `${this._lastValidMediaShowInfo.width} / ${this._lastValidMediaShowInfo.height}`;
} }
const defaultAspectRatio = this._getConfig().dimensions.aspect_ratio; const defaultAspectRatio = this._getConfig().dimensions.aspect_ratio;
@@ -1776,16 +1790,14 @@ export class FrigateCard extends LitElement {
style="${styleMap(cardStyle)}" style="${styleMap(cardStyle)}"
@action=${(ev: CustomEvent) => this._actionHandler(ev, actions)} @action=${(ev: CustomEvent) => this._actionHandler(ev, actions)}
@ll-custom=${this._cardActionHandler.bind(this)} @ll-custom=${this._cardActionHandler.bind(this)}
@frigate-card:message=${this._messageHandler} @frigate-card:message=${this._messageHandler.bind(this)}
@frigate-card:change-view=${this._changeViewHandler} @frigate-card:view:change=${this._changeViewHandler.bind(this)}
@frigate-card:view:change-context=${this._addViewContextHandler.bind(this)}
@frigate-card:media-show=${this._mediaShowHandler} @frigate-card:media-show=${this._mediaShowHandler}
@frigate-card:render=${() => this.requestUpdate()} @frigate-card:render=${() => this.requestUpdate()}
> >
${renderMenuAbove ? this._renderMenu() : ''} ${renderMenuAbove ? this._renderMenu() : ''}
<div <div ${ref(this._refMain)} class="main">
${ref(this._refMain)}
class="main"
>
${this._cameras === undefined && !this._message ${this._cameras === undefined && !this._message
? until( ? until(
(async () => { (async () => {
@@ -1807,9 +1819,12 @@ export class FrigateCard extends LitElement {
} }
</div> </div>
${!renderMenuAbove ? this._renderMenu() : ''} ${!renderMenuAbove ? this._renderMenu() : ''}
${!this._message && this._getConfig().elements ${!this._message &&
(!this._view?.isAnyMediaView() || this._currentMediaShowInfo) &&
this._getConfig().elements
? // Elements need to render after the main views so it can render 'on ? // Elements need to render after the main views so it can render 'on
// top'. // top', but only if the view is a non-media view or the media is
// already loaded.
html` <frigate-card-elements html` <frigate-card-elements
${ref(this._refElements)} ${ref(this._refElements)}
.hass=${this._hass} .hass=${this._hass}
@@ -1925,8 +1940,8 @@ export class FrigateCard extends LitElement {
* @returns The Lovelace card size in units of 50px. * @returns The Lovelace card size in units of 50px.
*/ */
public getCardSize(): number { public getCardSize(): number {
if (this._mediaShowInfo) { if (this._lastValidMediaShowInfo) {
return this._mediaShowInfo.height / 50; return this._lastValidMediaShowInfo.height / 50;
} }
return 6; return 6;
} }
+15 -5
View File
@@ -44,7 +44,10 @@ import {
WebRTCCardConfig, WebRTCCardConfig,
} from '../types.js'; } from '../types.js';
import { stopEventFromActivatingCardWideActions } from '../utils/action.js'; import { stopEventFromActivatingCardWideActions } from '../utils/action.js';
import { contentsChanged, errorToConsole } from '../utils/basic.js'; import {
contentsChanged,
errorToConsole,
} from '../utils/basic.js';
import { getCameraIcon, getCameraTitle } from '../utils/camera.js'; import { getCameraIcon, getCameraTitle } from '../utils/camera.js';
import { homeAssistantSignPath } from '../utils/ha'; import { homeAssistantSignPath } from '../utils/ha';
import { getFullDependentBrowseMediaQueryParameters } from '../utils/ha/browse-media.js'; import { getFullDependentBrowseMediaQueryParameters } from '../utils/ha/browse-media.js';
@@ -52,7 +55,7 @@ import {
dispatchExistingMediaShowInfoAsEvent, dispatchExistingMediaShowInfoAsEvent,
dispatchMediaShowEvent, dispatchMediaShowEvent,
} from '../utils/media-info.js'; } from '../utils/media-info.js';
import { View } from '../view.js'; import { dispatchViewContextChangeEvent, View } from '../view.js';
import { AutoMediaPlugin } from './embla-plugins/automedia.js'; import { AutoMediaPlugin } from './embla-plugins/automedia.js';
import { Lazyload } from './embla-plugins/lazyload.js'; import { Lazyload } from './embla-plugins/lazyload.js';
import { import {
@@ -226,7 +229,7 @@ export class FrigateCardLive extends LitElement {
ev.stopPropagation(); ev.stopPropagation();
} }
}} }}
@frigate-card:change-view=${(ev: CustomEvent<View>) => { @frigate-card:view:change=${(ev: CustomEvent<View>) => {
if (this._inBackground) { if (this._inBackground) {
ev.stopPropagation(); ev.stopPropagation();
} }
@@ -448,10 +451,13 @@ export class FrigateCardLiveCarousel extends LitElement {
.evolve({ .evolve({
camera: Array.from(this.cameras.keys())[selectedCameraIndex], camera: Array.from(this.cameras.keys())[selectedCameraIndex],
// Reset the target so thumbnails will be re-fetched. // Reset the target.
target: null, target: null,
childIndex: null, childIndex: null,
}) })
// Don't yet fetch thumbnails (they will be fetched when the carousel
// settles).
.mergeInContext({ thumbnails: { fetch: false } })
.dispatchChangeEvent(this); .dispatchChangeEvent(this);
} }
@@ -582,7 +588,11 @@ export class FrigateCardLiveCarousel extends LitElement {
.label="${title ? `${localize('common.live')}: ${title}` : ''}" .label="${title ? `${localize('common.live')}: ${title}` : ''}"
.titlePopupConfig=${config.controls.title} .titlePopupConfig=${config.controls.title}
transitionEffect=${this._getTransitionEffect()} transitionEffect=${this._getTransitionEffect()}
@frigate-card:carousel:settle=${this._setViewHandler.bind(this)} @frigate-card:media-carousel:select=${this._setViewHandler.bind(this)}
@frigate-card:carousel:settle=${() => {
// Fetch the thumbnails after the carousel has settled.
dispatchViewContextChangeEvent(this, { thumbnails: { fetch: true }});
}}
> >
<frigate-card-next-previous-control <frigate-card-next-previous-control
slot="previous" slot="previous"
+10
View File
@@ -363,6 +363,16 @@ export class FrigateCardMediaCarousel extends LitElement {
@frigate-card:carousel:select=${(ev: CustomEvent<CarouselSelect>) => { @frigate-card:carousel:select=${(ev: CustomEvent<CarouselSelect>) => {
this._slideResizeObserver.disconnect(); this._slideResizeObserver.disconnect();
this._slideResizeObserver.observe(ev.detail.element); this._slideResizeObserver.observe(ev.detail.element);
// Pass up the media-carousel select event first to allow parents to
// initialize/reset before the media info is dispatched.
dispatchFrigateCardEvent<CarouselSelect>(
this,
'media-carousel:select',
ev.detail,
);
// Dispatch media info.
this._dispatchMediaShowInfo(); this._dispatchMediaShowInfo();
}} }}
@frigate-card:carousel:media-show=${this._storeMediaShowInfo.bind(this)} @frigate-card:carousel:media-show=${this._storeMediaShowInfo.bind(this)}
+14 -2
View File
@@ -27,6 +27,17 @@ import { dispatchFrigateCardErrorEvent } from './message.js';
import './surround.js'; import './surround.js';
import { ThumbnailCarouselTap } from './thumbnail-carousel.js'; import { ThumbnailCarouselTap } from './thumbnail-carousel.js';
interface ThumbnailViewContext {
// Whetherr or not to fetch thumbnails.
fetch?: boolean;
}
declare module 'view' {
interface ViewContext {
thumbnails?: ThumbnailViewContext;
}
}
@customElement('frigate-card-surround-thumbnails') @customElement('frigate-card-surround-thumbnails')
export class FrigateCardSurround extends LitElement { export class FrigateCardSurround extends LitElement {
@property({ attribute: false }) @property({ attribute: false })
@@ -64,7 +75,8 @@ export class FrigateCardSurround extends LitElement {
!this.config || !this.config ||
this.config.mode === 'none' || this.config.mode === 'none' ||
this.view.target || this.view.target ||
!this.browseMediaParams !this.browseMediaParams ||
!(this.view.context?.thumbnails?.fetch ?? true)
) { ) {
return; return;
} }
@@ -147,7 +159,7 @@ export class FrigateCardSurround extends LitElement {
.target=${this.view.target} .target=${this.view.target}
.selected=${this.view.childIndex} .selected=${this.view.childIndex}
.cameras=${this.cameras} .cameras=${this.cameras}
@frigate-card:change-view=${(ev: CustomEvent) => changeDrawer(ev, 'close')} @frigate-card:view:change=${(ev: CustomEvent) => changeDrawer(ev, 'close')}
@frigate-card:thumbnail-carousel:tap=${(ev: CustomEvent<ThumbnailCarouselTap>) => { @frigate-card:thumbnail-carousel:tap=${(ev: CustomEvent<ThumbnailCarouselTap>) => {
// Send the view change from the source of the tap event, so the // Send the view change from the source of the tap event, so the
// view change will be caught by the handler above (to close the drawer). // view change will be caught by the handler above (to close the drawer).
+4 -2
View File
@@ -282,8 +282,8 @@ export class FrigateCardThumbnail extends LitElement {
view: 'timeline', view: 'timeline',
target: this.target, target: this.target,
childIndex: this.childIndex ?? null, childIndex: this.childIndex ?? null,
context: {},
}) })
.removeContext('timeline')
.dispatchChangeEvent(this); .dispatchChangeEvent(this);
} else if (recording) { } else if (recording) {
this.view this.view
@@ -291,7 +291,9 @@ export class FrigateCardThumbnail extends LitElement {
view: 'timeline', view: 'timeline',
target: null, target: null,
childIndex: null, childIndex: null,
context: { })
.mergeInContext({
timeline: {
window: { window: {
start: fromUnixTime(recording.start_time), start: fromUnixTime(recording.start_time),
end: fromUnixTime(recording.end_time), end: fromUnixTime(recording.end_time),
+21 -13
View File
@@ -21,6 +21,7 @@ import { customElement, property } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js'; import { classMap } from 'lit/directives/class-map.js';
import { createRef, ref, Ref } from 'lit/directives/ref.js'; import { createRef, ref, Ref } from 'lit/directives/ref.js';
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
import { ViewContext } from 'view';
import { DataSet } from 'vis-data/esnext'; import { DataSet } from 'vis-data/esnext';
import { import {
DataGroupCollectionType, DataGroupCollectionType,
@@ -64,7 +65,7 @@ import {
isTrueMedia, isTrueMedia,
multipleBrowseMediaQuery, multipleBrowseMediaQuery,
} from '../utils/ha/browse-media'; } from '../utils/ha/browse-media';
import { View, ViewContext } from '../view'; import { View } from '../view';
import { dispatchFrigateCardErrorEvent, dispatchMessageEvent } from './message.js'; import { dispatchFrigateCardErrorEvent, dispatchMessageEvent } from './message.js';
import './surround-thumbnails.js'; import './surround-thumbnails.js';
@@ -81,12 +82,18 @@ interface FrigateCardTimelineItem extends TimelineItem {
source?: FrigateBrowseMediaSource; source?: FrigateBrowseMediaSource;
} }
interface TimelineViewContext extends ViewContext { interface TimelineViewContext {
// The selected timeline window. // The selected timeline window.
window?: TimelineWindow; window?: TimelineWindow;
// The date of the last event fetch. // The date of the last event fetch.
dateFetch?: Date; dateFetch?: Date;
}
declare module 'view' {
interface ViewContext {
timeline?: TimelineViewContext;
}
} }
type TimelineMediaType = 'all' | 'clips' | 'snapshots'; type TimelineMediaType = 'all' | 'clips' | 'snapshots';
@@ -867,8 +874,8 @@ export class FrigateCardTimelineCore extends LitElement {
?.evolve({ ?.evolve({
target: thumbnails?.target ?? null, target: thumbnails?.target ?? null,
childIndex: thumbnails?.childIndex ?? null, childIndex: thumbnails?.childIndex ?? null,
context: this._generateViewContext(true),
}) })
.mergeInContext(this._generateTimelineContext(true))
.dispatchChangeEvent(this); .dispatchChangeEvent(this);
} }
}); });
@@ -1174,7 +1181,7 @@ export class FrigateCardTimelineCore extends LitElement {
// 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 as TimelineViewContext | null; const context = this.view.context?.timeline;
const timelineWindow = this._timeline.getWindow(); const timelineWindow = this._timeline.getWindow();
if (context?.window) { if (context?.window) {
@@ -1222,8 +1229,8 @@ export class FrigateCardTimelineCore extends LitElement {
?.evolve({ ?.evolve({
target: thumbnails?.target ?? null, target: thumbnails?.target ?? null,
childIndex: thumbnails?.childIndex ?? null, childIndex: thumbnails?.childIndex ?? null,
context: this._generateViewContext(false),
}) })
.mergeInContext(this._generateTimelineContext(false))
.dispatchChangeEvent(this); .dispatchChangeEvent(this);
} }
} }
@@ -1234,9 +1241,10 @@ export class FrigateCardTimelineCore extends LitElement {
* the window is preserved if it is already in the context. * the window is preserved if it is already in the context.
* @returns The TimelineViewContext object. * @returns The TimelineViewContext object.
*/ */
protected _generateViewContext(addWindow: boolean): TimelineViewContext { protected _generateTimelineContext(addWindow: boolean): ViewContext {
const currentContext = this.view?.context as TimelineViewContext | undefined; const currentContext = this.view?.context?.timeline;
const newContext: TimelineViewContext = {}; const newContext: TimelineViewContext = {}
if (addWindow && this._timeline) { if (addWindow && this._timeline) {
newContext.window = this._timeline.getWindow(); newContext.window = this._timeline.getWindow();
} else if (currentContext?.window) { } else if (currentContext?.window) {
@@ -1245,7 +1253,7 @@ export class FrigateCardTimelineCore extends LitElement {
if (this._data.lastFetchDate) { if (this._data.lastFetchDate) {
newContext.dateFetch = this._data.lastFetchDate; newContext.dateFetch = this._data.lastFetchDate;
} }
return newContext || null; return Object.keys(newContext) ? {timeline: newContext} : {};
} }
/** /**
+1 -1
View File
@@ -618,7 +618,7 @@ export class FrigateCardViewerCarousel extends LitElement {
.label="${this.view.media.title}" .label="${this.view.media.title}"
.titlePopupConfig=${this.viewerConfig?.controls.title} .titlePopupConfig=${this.viewerConfig?.controls.title}
transitionEffect=${this._getTransitionEffect()} transitionEffect=${this._getTransitionEffect()}
@frigate-card:carousel:select=${this._setViewHandler.bind(this)} @frigate-card:media-carousel:select=${this._setViewHandler.bind(this)}
@frigate-card:media-show=${this._recordingSeekHandler.bind(this)} @frigate-card:media-show=${this._recordingSeekHandler.bind(this)}
> >
<frigate-card-next-previous-control <frigate-card-next-previous-control
+5
View File
@@ -1,2 +1,7 @@
declare module '*.scss'; declare module '*.scss';
declare module '*.jpg'; declare module '*.jpg';
declare module "view" {
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface ViewContext {
}
}
+57 -5
View File
@@ -1,15 +1,13 @@
import { ViewContext } from 'view';
import { import {
FrigateBrowseMediaSource, FrigateBrowseMediaSource,
FrigateCardUserSpecifiedView, FrigateCardUserSpecifiedView,
FrigateCardView, FrigateCardView,
FRIGATE_CARD_VIEWS_USER_SPECIFIED, FRIGATE_CARD_VIEWS_USER_SPECIFIED,
FRIGATE_CARD_VIEW_DEFAULT FRIGATE_CARD_VIEW_DEFAULT,
} from './types.js'; } from './types.js';
import { dispatchFrigateCardEvent } from './utils/basic.js'; import { dispatchFrigateCardEvent } from './utils/basic.js';
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface ViewContext {}
export interface ViewEvolveParameters { export interface ViewEvolveParameters {
view?: FrigateCardView; view?: FrigateCardView;
camera?: string; camera?: string;
@@ -63,6 +61,26 @@ export class View {
: FRIGATE_CARD_VIEW_DEFAULT; : FRIGATE_CARD_VIEW_DEFAULT;
} }
/**
* Detect if a view change represents a major "media change" for the given
* view.
* @param prev The previous view.
* @param curr The current view.
* @returns True if the view change is a real media change.
*/
public static isMediaChange(prev?: View, curr?: View): boolean {
return (
!prev ||
!curr ||
prev.view !== curr.view ||
prev.camera !== curr.camera ||
// When in the live view, the target/childIndex are the events that
// happened in the past -- not reflective of the actual live media viewer.
(curr.view !== 'live' &&
(prev.target !== curr.target || prev.childIndex !== curr.childIndex))
);
}
/** /**
* Clone a view. * Clone a view.
*/ */
@@ -96,6 +114,28 @@ export class View {
}); });
} }
/**
* Merge view contexts.
* @param context The context to merge in.
* @returns This view.
*/
public mergeInContext(context: ViewContext): View {
this.context = { ...this.context, ...context };
return this;
}
/**
* Remove a context key.
* @param key The key to remove.
* @returns This view.
*/
public removeContext(key: keyof ViewContext): View {
if (this.context) {
delete(this.context[key]);
}
return this;
}
/** /**
* Determine if current view matches a named view. * Determine if current view matches a named view.
*/ */
@@ -168,6 +208,18 @@ export class View {
* @param target The target dispatching the event. * @param target The target dispatching the event.
*/ */
public dispatchChangeEvent(target: EventTarget): void { public dispatchChangeEvent(target: EventTarget): void {
dispatchFrigateCardEvent(target, 'change-view', this); dispatchFrigateCardEvent(target, 'view:change', this);
} }
} }
/**
* Dispatch an event to change the view context.
* @param target The EventTarget to send the event from.
* @param context The context to change.
*/
export const dispatchViewContextChangeEvent = (
target: EventTarget,
context: ViewContext,
): void => {
dispatchFrigateCardEvent(target, 'view:change-context', context);
};