Allow the timeline to refetch events.

This commit is contained in:
Dermot Duffy
2022-04-09 20:19:38 -07:00
parent 90b06ef22f
commit 3011b6b99a
+90 -25
View File
@@ -49,6 +49,8 @@ import timelineStyle from '../scss/timeline.scss';
import './surround-thumbnails.js'; import './surround-thumbnails.js';
const TIMELINE_EVENT_MANAGER_MAX_AGE_SECONDS = 10;
interface FrigateCardGroupData { interface FrigateCardGroupData {
id: string; id: string;
content: string; content: string;
@@ -63,6 +65,8 @@ interface TimelineViewContext extends ViewContext {
window: TimelineWindow; window: TimelineWindow;
} }
type TimelineMediaType = 'all' | 'clips' | 'snapshots';
/** /**
* A manager to maintain/fetch timeline events. * A manager to maintain/fetch timeline events.
*/ */
@@ -74,6 +78,14 @@ class TimelineEventManager {
// The latest date managed. // The latest date managed.
protected _dateEnd?: Date; protected _dateEnd?: Date;
// The last fetch date.
protected _dateFetch?: Date;
// The maximum allowable age of fetch data (will not fetch more frequently
// than this).
protected _maxAgeSeconds: number = TIMELINE_EVENT_MANAGER_MAX_AGE_SECONDS;
protected _contentCallback?: (source: FrigateBrowseMediaSource) => string; protected _contentCallback?: (source: FrigateBrowseMediaSource) => string;
protected _tooltipCallback?: (source: FrigateBrowseMediaSource) => string; protected _tooltipCallback?: (source: FrigateBrowseMediaSource) => string;
@@ -152,11 +164,48 @@ class TimelineEventManager {
* @returns * @returns
*/ */
public hasCoverage(start: Date, end?: Date): boolean { public hasCoverage(start: Date, end?: Date): boolean {
return ( const now = new Date().getTime();
!!this._dateStart &&
start >= this._dateStart && // Never fetched: no coverage.
(!end || (!!this._dateEnd && end <= this._dateEnd)) if (!this._dateFetch || !this._dateStart || !this._dateEnd) {
); return false;
}
// If the most recent fetch is older than maxAgeSeconds: no coverage.
if (
this._maxAgeSeconds &&
now - this._dateFetch.getTime() > this._maxAgeSeconds * 1000
) {
return false;
}
// If the most requested data is earlier than the earliest stored: no
// coverage.
if (start < this._dateStart) {
return false;
}
// If there's no end time specified: there IS coverage.
if (!end) {
return true;
}
// If the requested end time is older than the oldest requested: there IS
// coverage.
if (end.getTime() < this._dateEnd.getTime()) {
return true;
}
// If there's no maxAgeSeconds specified: no coverage.
if (!this._maxAgeSeconds) {
return false;
}
// If the requested end time is beyond `_maxAgeSeconds` of now: no coverage.
if (now - end.getTime() > this._maxAgeSeconds * 1000) {
return false;
}
// End time is within `_maxAgeSeconds` of the latest data: there IS
// coverage.
return end.getTime() - this._maxAgeSeconds * 1000 <= this._dateEnd.getTime();
} }
/** /**
@@ -172,7 +221,7 @@ class TimelineEventManager {
element: HTMLElement, element: HTMLElement,
hass: HomeAssistant & ExtendedHomeAssistant, hass: HomeAssistant & ExtendedHomeAssistant,
cameras: Map<string, CameraConfig>, cameras: Map<string, CameraConfig>,
media: 'all' | 'clips' | 'snapshots', media: TimelineMediaType,
start: Date, start: Date,
end: Date, end: Date,
): Promise<boolean> { ): Promise<boolean> {
@@ -195,16 +244,20 @@ class TimelineEventManager {
element: HTMLElement, element: HTMLElement,
hass: HomeAssistant & ExtendedHomeAssistant, hass: HomeAssistant & ExtendedHomeAssistant,
cameras: Map<string, CameraConfig>, cameras: Map<string, CameraConfig>,
media: 'all' | 'clips' | 'snapshots', media: TimelineMediaType,
start: Date, start?: Date,
end: Date, end?: Date,
): Promise<void> { ): Promise<void> {
if (!this._dateStart || start < this._dateStart) { if (!this._dateStart || (start && start < this._dateStart)) {
this._dateStart = start; this._dateStart = start;
} }
if (!this._dateEnd || end > this._dateEnd) { if (!this._dateEnd || (end && end > this._dateEnd)) {
this._dateEnd = end; this._dateEnd = end;
} }
if (!this._dateStart || !this._dateEnd) {
return;
}
this._dateFetch = new Date();
const fetchCameraEvents = async ( const fetchCameraEvents = async (
camera: string, camera: string,
@@ -330,7 +383,6 @@ export class FrigateCardTimelineCore extends LitElement {
const eventAttr = source.frigate?.event const eventAttr = source.frigate?.event
? `event='${JSON.stringify(source.frigate.event)}'` ? `event='${JSON.stringify(source.frigate.event)}'`
: ''; : '';
console.info(eventAttr);
// Cannot use Lit data-bindings as visjs requires a string for tooltips. // Cannot use Lit data-bindings as visjs requires a string for tooltips.
// Note that changes to attributes here must be mirrored in the xss // Note that changes to attributes here must be mirrored in the xss
@@ -378,6 +430,9 @@ export class FrigateCardTimelineCore extends LitElement {
byUser: boolean; byUser: boolean;
event: Event; event: Event;
}): void { }): void {
if (!properties.byUser) {
return;
}
console.info( console.info(
`Range changed: ${properties.start} -> ${properties.end} [${this._events.dataset.length}]`, `Range changed: ${properties.start} -> ${properties.end} [${this._events.dataset.length}]`,
); );
@@ -492,6 +547,7 @@ export class FrigateCardTimelineCore extends LitElement {
children: children, children: children,
}; };
if (!isEqual(target, this._thumbnails)) {
this._thumbnails = target; this._thumbnails = target;
// Update the thumbnail carousel with the regenerated thumbnails. // Update the thumbnail carousel with the regenerated thumbnails.
@@ -502,6 +558,7 @@ export class FrigateCardTimelineCore extends LitElement {
}) })
.dispatchChangeEvent(this); .dispatchChangeEvent(this);
} }
}
/** /**
* Build the visjs dataset to render on the timeline. * Build the visjs dataset to render on the timeline.
@@ -647,36 +704,35 @@ 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> {
const event = this.view?.media?.frigate?.event;
if ( if (
!this.hass || !this.hass ||
!this.cameras || !this.cameras ||
!this.view || !this.view ||
!event ||
!this._timeline || !this._timeline ||
!this.timelineConfig !this.timelineConfig
) { ) {
return; return;
} }
const [eventWindowStart, eventWindowEnd] = this._getStartEndFromEvent(event); const event = this.view?.media?.frigate?.event;
const [windowStart, windowEnd] = event
? this._getStartEndFromEvent(event)
: this._getStartEnd();
if ( if (
await this._events.fetchEventsIfNecessary( await this._events.fetchEventsIfNecessary(
this, this,
this.hass, this.hass,
this.cameras, this.cameras,
this.timelineConfig.media, this.timelineConfig.media,
eventWindowStart, windowStart,
eventWindowEnd, windowEnd,
) )
) { ) {
this._generateThumbnails(); this._generateThumbnails();
} }
const eventStart = new Date(event.start_time * 1000); if (event) {
const eventEnd = event.end_time ? new Date(event.end_time * 1000) : 0;
this._timeline.setSelection([event.id], { this._timeline.setSelection([event.id], {
focus: false, focus: false,
animation: { animation: {
@@ -684,9 +740,10 @@ export class FrigateCardTimelineCore extends LitElement {
zoom: false, zoom: false,
}, },
}); });
}
const timelineWindow = this._timeline.getWindow();
const context = this.view.context as TimelineViewContext | null; const context = this.view.context as TimelineViewContext | null;
const timelineWindow = this._timeline.getWindow();
if (context?.window) { if (context?.window) {
console.info( console.info(
@@ -695,13 +752,17 @@ export class FrigateCardTimelineCore extends LitElement {
if (!isEqual(context.window, timelineWindow)) { if (!isEqual(context.window, timelineWindow)) {
this._timeline.setWindow(context.window.start, context.window.end); this._timeline.setWindow(context.window.start, context.window.end);
} }
} else if ( } else if (event) {
const eventStart = new Date(event.start_time * 1000);
const eventEnd = event.end_time ? new Date(event.end_time * 1000) : 0;
if (
eventStart < timelineWindow.start || eventStart < timelineWindow.start ||
eventStart > timelineWindow.end || eventStart > timelineWindow.end ||
(eventEnd && (eventEnd < timelineWindow.start || eventEnd > timelineWindow.end)) (eventEnd && (eventEnd < timelineWindow.start || eventEnd > timelineWindow.end))
) { ) {
console.info(`Setting window from event ${eventWindowStart} -> ${eventWindowEnd}`); console.info(`Setting window from event ${windowStart} -> ${windowEnd}`);
this._timeline.setWindow(eventWindowStart, eventWindowEnd); this._timeline.setWindow(windowStart, windowEnd);
} }
if (this._isClustering()) { if (this._isClustering()) {
@@ -713,6 +774,10 @@ export class FrigateCardTimelineCore extends LitElement {
this._events.dataset.updateOnly(item); this._events.dataset.updateOnly(item);
} }
} }
} else {
console.info(`Setting window from live ${windowStart} -> ${windowEnd}`);
this._timeline.setWindow(windowStart, windowEnd);
}
} }
/** /**