Allow the timeline to refetch events.
This commit is contained in:
+90
-25
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user