Add window_seconds option to the timeline.

This commit is contained in:
Dermot Duffy
2022-04-10 19:43:33 -07:00
parent e3e7646fea
commit e2e332cef1
6 changed files with 61 additions and 17 deletions
+1
View File
@@ -404,6 +404,7 @@ timeline:
| Option | Default | Overridable | Description |
| - | - | - | - |
| `window_seconds` | `3600` | :heavy_multiplication_x: | The length of the default timeline in seconds. By default, 1 hour (`3600` seconds) is shown in the timeline. |
| `clustering_threshold` | `3` | :heavy_multiplication_x: | The number of overlapping events to allow prior to clustering/grouping them. Higher numbers cause clustering to happen less frequently. `0` disables clustering entirely.|
| `media` | `all` | :heavy_multiplication_x: | Whether to show only events with `clips`, events with `snapshots` or `all` events. When `all` is used, `clips` are favored for events that have both a clip and a snapshot.|
| `controls` | | :heavy_multiplication_x: | Configuration for the timeline controls. See below.|
+41 -15
View File
@@ -1,4 +1,4 @@
// TODO: Card actions for timeline
// TODO: Save memory usage by saving thumbnails once.
// TODO: Search for TODOs and logging statements.
import {
@@ -35,6 +35,7 @@ import {
MEDIA_TYPE_PLAYLIST,
TimelineConfig,
FrigateEvent,
frigateCardConfigDefaults,
} from '../types';
import { View, ViewContext } from '../view';
import {
@@ -596,16 +597,38 @@ export class FrigateCardTimelineCore extends LitElement {
* @returns A tuple of start/end date.
*/
protected _getStartEndFromEvent(event: FrigateEvent): [Date, Date] {
const one_hour = { hours: 1 };
const start = sub(fromUnixTime(event.start_time), one_hour);
let end: Date;
const windowSeconds = this._getConfiguredWindowSeconds();
if (event.end_time) {
end = add(fromUnixTime(event.end_time), one_hour);
} else {
end = add(start, one_hour);
if (event.end_time - event.start_time > windowSeconds) {
// If the event is larger than the configured window, only show the most
// recent portion of the event that fits in the window.
return [
sub(fromUnixTime(event.end_time), {seconds: windowSeconds}),
fromUnixTime(event.end_time)
];
} else {
// If the event is shorter than the configured window, center the event
// in the window.
const gap = windowSeconds - (event.end_time - event.start_time);
return [
sub(fromUnixTime(event.start_time), {seconds: gap / 2}),
add(fromUnixTime(event.end_time), {seconds: gap / 2}),
]
}
}
return [start, end];
// If there's no end-time yet, place the start-time in the center of the
// time window.
return [
sub(fromUnixTime(event.start_time), {seconds: windowSeconds / 2}),
add(fromUnixTime(event.start_time), {seconds: windowSeconds / 2}),
];
}
/**
* Get the configured window length in seconds.
*/
protected _getConfiguredWindowSeconds(): number {
return this.timelineConfig?.window_seconds ?? frigateCardConfigDefaults.timeline.window_seconds;
}
/**
@@ -617,9 +640,10 @@ export class FrigateCardTimelineCore extends LitElement {
if (event) {
return this._getStartEndFromEvent(event);
}
const one_hour = { hours: 1 };
const end = new Date();
const start = sub(end, one_hour);
const start = sub(end, {
seconds: this._getConfiguredWindowSeconds()
});
return [start, end];
}
@@ -649,7 +673,6 @@ export class FrigateCardTimelineCore extends LitElement {
return {
cluster: this._isClustering()
? {
showStipes: true,
// It would be better to automatically calculate `maxItems` from the
// rendered height of the timeline (or group within the timeline) so
// as to not waste vertical space (e.g. after the user changes to
@@ -676,7 +699,7 @@ export class FrigateCardTimelineCore extends LitElement {
: (false as TimelineOptionsCluster),
minHeight: '100%',
maxHeight: '100%',
zoomMax: 31 * 24 * 60 * 60 * 1000,
zoomMax: 1 * 24 * 60 * 60 * 1000,
zoomMin: 1 * 1000,
selectable: true,
start: start,
@@ -742,7 +765,6 @@ export class FrigateCardTimelineCore extends LitElement {
windowStart,
windowEnd,
);
this._generateThumbnails();
this._timeline.setSelection(event ? [event.id] : [], {
focus: false,
@@ -752,6 +774,10 @@ export class FrigateCardTimelineCore extends LitElement {
},
});
// Regenerate the thumbnails after the selection, to allow the new selection
// to be in the generated view.
this._generateThumbnails();
const context = this.view.context as TimelineViewContext | null;
const timelineWindow = this._timeline.getWindow();
@@ -807,7 +833,7 @@ export class FrigateCardTimelineCore extends LitElement {
const options = this._getOptions();
if (changedProperties.has('timelineConfig') && this._refTimeline.value && options) {
if (this._timeline) {
// TODO this._timeline.setOptions(options);
this._timeline.setOptions(options);
} else {
this._timeline = new Timeline(
this._refTimeline.value,
+1
View File
@@ -89,6 +89,7 @@ export const CONF_IMAGE_REFRESH_SECONDS = `${CONF_IMAGE}.refresh_seconds` as con
export const CONF_IMAGE_URL = `${CONF_IMAGE}.url` as const;
export const CONF_TIMELINE = 'timeline' as const;
export const CONF_TIMELINE_WINDOW_SECONDS = `${CONF_TIMELINE}.window_seconds` as const;
export const CONF_TIMELINE_CLUSTERING_THRESHOLD = `${CONF_TIMELINE}.clustering_threshold` as const;
export const CONF_TIMELINE_MEDIA = `${CONF_TIMELINE}.media` as const;
export const CONF_TIMELINE_CONTROLS_THUMBNAILS_MODE = `${CONF_TIMELINE}.controls.thumbnails.mode` as const;
+2
View File
@@ -74,6 +74,7 @@ import {
CONF_TIMELINE_CONTROLS_THUMBNAILS_SHOW_DETAILS,
CONF_TIMELINE_CONTROLS_THUMBNAILS_SIZE,
CONF_TIMELINE_MEDIA,
CONF_TIMELINE_WINDOW_SECONDS,
CONF_VIEW_CAMERA_SELECT,
CONF_VIEW_DEFAULT,
CONF_VIEW_TIMEOUT_SECONDS,
@@ -1002,6 +1003,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
${this._renderOptionSetHeader('timeline')}
${options.timeline.show
? html` <div class="values">
${this._renderNumberInput(CONF_TIMELINE_WINDOW_SECONDS)}
${this._renderNumberInput(CONF_TIMELINE_CLUSTERING_THRESHOLD)}
${this._renderOptionSelector(CONF_TIMELINE_MEDIA, this._timelineMediaTypes)}
${this._renderOptionSelector(
+1
View File
@@ -172,6 +172,7 @@
"button_size": "Menu button size (e.g. '40px')"
},
"timeline": {
"window_seconds": "The default length of the timeline view in seconds",
"clustering_threshold": "The count of events at which they are clustered (0=no clustering)",
"media": "The media the timeline displays",
"medias": {
+15 -2
View File
@@ -772,6 +772,7 @@ const dimensionsConfigSchema = z
const timelineConfigDefault = {
clustering_threshold: 3,
media: 'all' as const,
window_seconds: 60 * 60,
controls: {
thumbnails: {
mode: 'left' as const,
@@ -783,8 +784,20 @@ const timelineConfigDefault = {
};
const timelineConfigSchema = z
.object({
clustering_threshold: z.number().default(timelineConfigDefault.clustering_threshold),
media: z.enum(['all', 'clips', 'snapshots']).default(timelineConfigDefault.media),
clustering_threshold: z
.number()
.optional()
.default(timelineConfigDefault.clustering_threshold),
media: z
.enum(['all', 'clips', 'snapshots'])
.optional()
.default(timelineConfigDefault.media),
window_seconds: z
.number()
.min(1 * 60)
.max(24 * 60 * 60)
.optional()
.default(timelineConfigDefault.window_seconds),
controls: z
.object({
thumbnails: thumbnailsControlSchema