Add window_seconds option to the timeline.
This commit is contained in:
@@ -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
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user