Improvements to updating timeline from view.
This commit is contained in:
+1
-1
@@ -1030,7 +1030,7 @@ export class FrigateCard extends LitElement {
|
|||||||
|
|
||||||
if (this._cameras && (changedProps.has('_config') || changedProps.has('_cameras'))) {
|
if (this._cameras && (changedProps.has('_config') || changedProps.has('_cameras'))) {
|
||||||
this._timelineDataManager = new TimelineDataManager(
|
this._timelineDataManager = new TimelineDataManager(
|
||||||
this._cameras, this._config.timeline.media, this._config.timeline.show_recordings
|
this._cameras, this._config.timeline.media
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,16 +74,17 @@ interface FrigateCardGroupData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface TimelineRangeChange extends TimelineWindow {
|
interface TimelineRangeChange extends TimelineWindow {
|
||||||
event: Event & { additionalEvent: string };
|
event: Event & { additionalEvent?: string };
|
||||||
byUser: boolean;
|
byUser: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TimelineViewContext {
|
interface TimelineViewContext {
|
||||||
// The selected timeline window.
|
// Force a particular timeline window rather than taking the time from an
|
||||||
|
// event / recording.
|
||||||
window?: TimelineWindow;
|
window?: TimelineWindow;
|
||||||
|
|
||||||
// The date of the last event fetch.
|
// Whether or not to set the timeline window.
|
||||||
dateFetch?: Date;
|
noSetWindow?: boolean;
|
||||||
|
|
||||||
// Whether or not thumbnails were generated.
|
// Whether or not thumbnails were generated.
|
||||||
generatedThumbnails?: boolean;
|
generatedThumbnails?: boolean;
|
||||||
@@ -453,7 +454,14 @@ export class FrigateCardTimelineCore extends LitElement {
|
|||||||
* @param properties
|
* @param properties
|
||||||
*/
|
*/
|
||||||
protected _timelineRangeChangeHandler(properties: TimelineRangeChange): void {
|
protected _timelineRangeChangeHandler(properties: TimelineRangeChange): void {
|
||||||
if (this._timeline && properties.byUser) {
|
if (
|
||||||
|
this._timeline &&
|
||||||
|
properties.byUser &&
|
||||||
|
// Do not adjust select children or seek during zoom events.
|
||||||
|
properties.event.type !== 'wheel' &&
|
||||||
|
properties.event.additionalEvent !== 'pinchin' &&
|
||||||
|
properties.event.additionalEvent !== 'pinchout'
|
||||||
|
) {
|
||||||
if (this._pointerHeld) {
|
if (this._pointerHeld) {
|
||||||
this._ignoreClick = true;
|
this._ignoreClick = true;
|
||||||
}
|
}
|
||||||
@@ -557,8 +565,11 @@ export class FrigateCardTimelineCore extends LitElement {
|
|||||||
...(childIndex !== null && {
|
...(childIndex !== null && {
|
||||||
childIndex: childIndex,
|
childIndex: childIndex,
|
||||||
}),
|
}),
|
||||||
|
}) // Whether or not to set the timeline window.
|
||||||
|
.mergeInContext({
|
||||||
|
...this._generateTimelineContext({ noSetWindow: true }),
|
||||||
|
...context,
|
||||||
})
|
})
|
||||||
.mergeInContext({ ...this._generateTimelineContext(), ...context })
|
|
||||||
.dispatchChangeEvent(this);
|
.dispatchChangeEvent(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -745,7 +756,7 @@ export class FrigateCardTimelineCore extends LitElement {
|
|||||||
start: Date;
|
start: Date;
|
||||||
end: Date;
|
end: Date;
|
||||||
byUser: boolean;
|
byUser: boolean;
|
||||||
event: Event;
|
event: Event & { additionalEvent: string };
|
||||||
}): void {
|
}): void {
|
||||||
if (!properties.byUser) {
|
if (!properties.byUser) {
|
||||||
return;
|
return;
|
||||||
@@ -772,7 +783,7 @@ export class FrigateCardTimelineCore extends LitElement {
|
|||||||
target: thumbnails?.target ?? null,
|
target: thumbnails?.target ?? null,
|
||||||
childIndex: thumbnails?.childIndex ?? null,
|
childIndex: thumbnails?.childIndex ?? null,
|
||||||
})
|
})
|
||||||
.mergeInContext(this._generateTimelineContext())
|
.mergeInContext(this._generateTimelineContext({ noSetWindow: true }))
|
||||||
.dispatchChangeEvent(this);
|
.dispatchChangeEvent(this);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1049,15 +1060,15 @@ export class FrigateCardTimelineCore extends LitElement {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (event && this._isClustering()) {
|
if (!this._pointerHeld && event && this._isClustering()) {
|
||||||
// Hack: Clustering may not update unless the dataset changes, artifically
|
// Hack: Clustering may not update unless the dataset changes, artifically
|
||||||
// update the dataset to ensure the newly selected item cannot be included
|
// update the dataset to ensure the newly selected item cannot be included
|
||||||
// in a cluster.
|
// in a cluster. Only do this when the pointer is not held to avoid
|
||||||
|
// interrupting the user and to make the timeline smoother.
|
||||||
this.timelineDataManager?.rewriteItem(event.id);
|
this.timelineDataManager?.rewriteItem(event.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
let contextWindow: TimelineWindow | null = null;
|
if (!this._pointerHeld && !this.view.context?.timeline?.noSetWindow) {
|
||||||
if (!this._pointerHeld) {
|
|
||||||
// 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;
|
||||||
@@ -1065,9 +1076,9 @@ export class FrigateCardTimelineCore extends LitElement {
|
|||||||
|
|
||||||
// If there's a set context window, always move to it.
|
// If there's a set context window, always move to it.
|
||||||
if (context?.window && !isEqual(context.window, timelineWindow)) {
|
if (context?.window && !isEqual(context.window, timelineWindow)) {
|
||||||
contextWindow = {start: context.window.start, end: context.window.end};
|
this._timeline.setWindow(context.window.start, context.window.end);
|
||||||
} else if (event || recording) {
|
} else if (event || recording) {
|
||||||
const source = event ?? recording as FrigateEvent | FrigateRecording;
|
const source = event ?? (recording as FrigateEvent | FrigateRecording);
|
||||||
const start = fromUnixTime(source.start_time);
|
const start = fromUnixTime(source.start_time);
|
||||||
const end = source.end_time ? fromUnixTime(source.end_time) : 0;
|
const end = source.end_time ? fromUnixTime(source.end_time) : 0;
|
||||||
|
|
||||||
@@ -1075,19 +1086,11 @@ export class FrigateCardTimelineCore extends LitElement {
|
|||||||
if (
|
if (
|
||||||
start < timelineWindow.start ||
|
start < timelineWindow.start ||
|
||||||
start > timelineWindow.end ||
|
start > timelineWindow.end ||
|
||||||
(end &&
|
(end && (end < timelineWindow.start || end > timelineWindow.end))
|
||||||
(end < timelineWindow.start || end > timelineWindow.end))
|
|
||||||
) {
|
) {
|
||||||
contextWindow = {start: windowStart, end: windowEnd};
|
this._timeline.setWindow(windowStart, windowEnd);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Otherwise just the default window.
|
|
||||||
contextWindow = {start: windowStart, end: windowEnd};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contextWindow) {
|
|
||||||
this._timeline.setWindow(contextWindow.start, contextWindow.end);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only generate thumbnails if an actual fetch occurred, to avoid getting
|
// Only generate thumbnails if an actual fetch occurred, to avoid getting
|
||||||
@@ -1102,33 +1105,37 @@ export class FrigateCardTimelineCore extends LitElement {
|
|||||||
//
|
//
|
||||||
// Also don't generate thumbnails in mini-timelines (they will already have
|
// Also don't generate thumbnails in mini-timelines (they will already have
|
||||||
// been generated), or if the media child is a recording.
|
// been generated), or if the media child is a recording.
|
||||||
if ((fetched || !this.view.context?.timeline?.generatedThumbnails) && !this.mini && !recording) {
|
if (
|
||||||
|
(fetched || !this.view.context?.timeline?.generatedThumbnails) &&
|
||||||
|
!this.mini &&
|
||||||
|
!recording
|
||||||
|
) {
|
||||||
const thumbnails = this._generateThumbnails();
|
const thumbnails = this._generateThumbnails();
|
||||||
this.view
|
this.view
|
||||||
?.evolve({
|
?.evolve({
|
||||||
target: thumbnails?.target ?? null,
|
target: thumbnails?.target ?? null,
|
||||||
childIndex: thumbnails?.childIndex ?? null,
|
childIndex: thumbnails?.childIndex ?? null,
|
||||||
})
|
})
|
||||||
.mergeInContext(this._generateTimelineContext(contextWindow))
|
.mergeInContext(this._generateTimelineContext())
|
||||||
.dispatchChangeEvent(this);
|
.dispatchChangeEvent(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate the context for timeline views.
|
* Generate the context for timeline views.
|
||||||
* @param addWindow Whether or not to include the timeline window. If `false`
|
* @param options Configure how the context is set.
|
||||||
* the window is preserved if it is already in the context.
|
|
||||||
* @returns The TimelineViewContext object.
|
* @returns The TimelineViewContext object.
|
||||||
*/
|
*/
|
||||||
protected _generateTimelineContext(window?: TimelineWindow | null): ViewContext {
|
protected _generateTimelineContext(options?: {
|
||||||
|
noSetWindow?: boolean;
|
||||||
|
generatedThumbnails?: boolean;
|
||||||
|
}): ViewContext {
|
||||||
const newContext: TimelineViewContext = {
|
const newContext: TimelineViewContext = {
|
||||||
generatedThumbnails: true,
|
generatedThumbnails: options?.generatedThumbnails ?? true,
|
||||||
};
|
};
|
||||||
if (this._timeline) {
|
|
||||||
newContext.window = window ? window : this._timeline.getWindow();
|
if (options?.noSetWindow) {
|
||||||
}
|
newContext.noSetWindow = options.noSetWindow;
|
||||||
if (this.timelineDataManager?.lastFetchDate) {
|
|
||||||
newContext.dateFetch = this.timelineDataManager.lastFetchDate;
|
|
||||||
}
|
}
|
||||||
return { timeline: newContext };
|
return { timeline: newContext };
|
||||||
}
|
}
|
||||||
@@ -1205,6 +1212,7 @@ export class FrigateCardTimelineCore extends LitElement {
|
|||||||
|
|
||||||
this._dataview = this.timelineDataManager.createDataView(
|
this._dataview = this.timelineDataManager.createDataView(
|
||||||
this._getTimelineCameraIDs(),
|
this._getTimelineCameraIDs(),
|
||||||
|
!!this.timelineConfig?.show_recordings,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.mini && groups.length === 1) {
|
if (this.mini && groups.length === 1) {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
// TODO: When a media viewer is first loaded the selected child won't work (because the underlying carousel has not yet rendered)
|
// TODO: When a media viewer is first loaded the selected child won't work (because the underlying carousel has not yet rendered)
|
||||||
// TODO: thumbnails in drawers don't work.
|
|
||||||
|
// TODO: unselect in mini timeline should not cause media viewer to render nothing
|
||||||
// TODO: delete segments if not in summary? is this actually necessary? could it create gaps in data? better off stopping access via summary?
|
// TODO: delete segments if not in summary? is this actually necessary? could it create gaps in data? better off stopping access via summary?
|
||||||
// TODO: support filtering created dataviews by recordings or mediatype (so storage )
|
// TODO: support filtering created dataviews by recordings or mediatype (so storage )
|
||||||
// TODO: dataview refresh instead of rewriteitem?
|
|
||||||
// TODO: Make minitimeline configurable in the editor
|
// TODO: Make minitimeline configurable in the editor
|
||||||
// TODO: Is it really useful to select the children in the main timeline view on range change?
|
// TODO: Make performance okay for 2+ days of events
|
||||||
|
|
||||||
import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit';
|
import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit';
|
||||||
import { customElement, property } from 'lit/decorators.js';
|
import { customElement, property } from 'lit/decorators.js';
|
||||||
|
|||||||
@@ -107,16 +107,13 @@ export class TimelineDataManager {
|
|||||||
|
|
||||||
protected _cameras: Map<string, CameraConfig>;
|
protected _cameras: Map<string, CameraConfig>;
|
||||||
protected _mediaType: TimelineMediaType;
|
protected _mediaType: TimelineMediaType;
|
||||||
protected _recordings: boolean;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
cameras: Map<string, CameraConfig>,
|
cameras: Map<string, CameraConfig>,
|
||||||
mediaType: TimelineMediaType,
|
mediaType: TimelineMediaType,
|
||||||
recordings: boolean,
|
|
||||||
) {
|
) {
|
||||||
this._cameras = cameras;
|
this._cameras = cameras;
|
||||||
this._mediaType = mediaType;
|
this._mediaType = mediaType;
|
||||||
this._recordings = recordings;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the last event fetch date.
|
// Get the last event fetch date.
|
||||||
@@ -128,10 +125,13 @@ export class TimelineDataManager {
|
|||||||
return this._recordingSummary.get(cameraID) ?? null;
|
return this._recordingSummary.get(cameraID) ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public createDataView(cameraIDs: Set<string>): DataView<FrigateCardTimelineItem> {
|
public createDataView(
|
||||||
|
cameraIDs: Set<string>,
|
||||||
|
showRecordings: boolean
|
||||||
|
): DataView<FrigateCardTimelineItem> {
|
||||||
return new DataView(this._dataset, {
|
return new DataView(this._dataset, {
|
||||||
filter: (item: FrigateCardTimelineItem) =>
|
filter: (item: FrigateCardTimelineItem) =>
|
||||||
!!item.group && cameraIDs.has(String(item.group)),
|
!!item.group && cameraIDs.has(String(item.group)) && (showRecordings || item.type !== 'background')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +175,6 @@ export class TimelineDataManager {
|
|||||||
['video', 'image'].includes(child.media_content_type)
|
['video', 'image'].includes(child.media_content_type)
|
||||||
) {
|
) {
|
||||||
let item = this._dataset.get(event.id);
|
let item = this._dataset.get(event.id);
|
||||||
//const st = fromUnixTime();
|
|
||||||
if (!item) {
|
if (!item) {
|
||||||
item = {
|
item = {
|
||||||
id: event.id,
|
id: event.id,
|
||||||
@@ -303,8 +302,8 @@ export class TimelineDataManager {
|
|||||||
// range. This is because events may change at any point in time
|
// range. This is because events may change at any point in time
|
||||||
// (e.g. a long-running event that ends).
|
// (e.g. a long-running event that ends).
|
||||||
this._fetchEvents(element, hass, this._dateStart, this._dateEnd),
|
this._fetchEvents(element, hass, this._dateStart, this._dateEnd),
|
||||||
...(this._recordings ? [this._fetchRecordingSummary(hass)] : []),
|
this._fetchRecordingSummary(hass),
|
||||||
...(this._recordings && segmentEnd > segmentStart
|
...(segmentEnd > segmentStart
|
||||||
? [this._fetchRecordingSegments(hass, segmentStart, segmentEnd)]
|
? [this._fetchRecordingSegments(hass, segmentStart, segmentEnd)]
|
||||||
: []),
|
: []),
|
||||||
]);
|
]);
|
||||||
|
|||||||
Reference in New Issue
Block a user