diff --git a/src/components/timeline.ts b/src/components/timeline.ts index 52bce702..5b8e6f75 100644 --- a/src/components/timeline.ts +++ b/src/components/timeline.ts @@ -367,7 +367,6 @@ export class FrigateCardTimelineCore extends LitElement { tooltipCallback: this._getTooltip.bind(this), }); protected _refTimeline: Ref = createRef(); - protected _thumbnails?: FrigateBrowseMediaSource; protected _timeline?: Timeline; /** @@ -445,14 +444,13 @@ export class FrigateCardTimelineCore extends LitElement { properties.start, properties.end, ) - .then((fetched: boolean) => { - if (fetched) { - this._generateThumbnails(); - } + .then(() => { + this._generateThumbnails(); }); // Update the view to ensure that future view changes do not cause a // scroll. + console.info('=========> Evolve view from Range'); this.view ?.evolve({ context: { @@ -470,19 +468,18 @@ export class FrigateCardTimelineCore extends LitElement { */ // eslint-disable-next-line @typescript-eslint/no-unused-vars protected _timelineSelectHandler(data: { items: string[]; event: Event }): void { - if (!this._thumbnails || !this._thumbnails.children) { + if (!this.view?.target || !this.view?.target.children) { return; } const childIndex = data.items.length - ? this._thumbnails.children.findIndex( + ? this.view.target.children.findIndex( (child) => child.frigate?.event.id === data.items[0], ) : null; - + console.info('=========> Evolve view from select'); this.view ?.evolve({ - target: this._thumbnails, childIndex: childIndex, }) .dispatchChangeEvent(this); @@ -546,13 +543,12 @@ export class FrigateCardTimelineCore extends LitElement { children: children, }; - if (!isEqual(target, this._thumbnails)) { - this._thumbnails = target; - + if (!isEqual(target, this.view?.target)) { // Update the thumbnail carousel with the regenerated thumbnails. + console.info('=========> Evolve view from thumbnails'); this.view ?.evolve({ - target: this._thumbnails, + target: target, childIndex: childIndex < 0 ? null : childIndex, }) .dispatchChangeEvent(this); @@ -718,18 +714,15 @@ export class FrigateCardTimelineCore extends LitElement { ? this._getStartEndFromEvent(event) : this._getStartEnd(); - if ( - await this._events.fetchEventsIfNecessary( - this, - this.hass, - this.cameras, - this.timelineConfig.media, - windowStart, - windowEnd, - ) - ) { - this._generateThumbnails(); - } + await this._events.fetchEventsIfNecessary( + this, + this.hass, + this.cameras, + this.timelineConfig.media, + windowStart, + windowEnd, + ) + this._generateThumbnails(); this._timeline.setSelection(event ? [event.id] : [], { focus: false, @@ -783,6 +776,7 @@ export class FrigateCardTimelineCore extends LitElement { */ protected updated(changedProperties: PropertyValues): void { super.updated(changedProperties); + console.info(changedProperties); if (changedProperties.has('cameras')) { this._events.clear(); diff --git a/src/const.ts b/src/const.ts index 99b116ad..d394cdc8 100644 --- a/src/const.ts +++ b/src/const.ts @@ -84,6 +84,13 @@ export const CONF_IMAGE_MODE = `${CONF_IMAGE}.mode` as const; export const CONF_IMAGE_REFRESH_SECONDS = `${CONF_IMAGE}.refresh_seconds` as const; export const CONF_IMAGE_URL = `${CONF_IMAGE}.url` as const; +export const CONF_TIMELINE = 'timeline' 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; +export const CONF_TIMELINE_CONTROLS_THUMBNAILS_SIZE = `${CONF_TIMELINE}.controls.thumbnails.size` as const; +export const CONF_TIMELINE_CONTROLS_THUMBNAILS_SHOW_DETAILS = `${CONF_TIMELINE}.controls.thumbnails.show_details` as const; + export const CONF_MENU = 'menu' as const; export const CONF_MENU_BUTTONS_FRIGATE = `${CONF_MENU}.buttons.frigate` as const; export const CONF_MENU_BUTTONS_FRIGATE_UI = `${CONF_MENU}.buttons.frigate_ui` as const; diff --git a/src/editor.ts b/src/editor.ts index 85cf2169..2d95bbc5 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -66,6 +66,11 @@ import { CONF_MENU_BUTTONS_SNAPSHOTS, CONF_MENU_BUTTON_SIZE, CONF_MENU_MODE, + CONF_TIMELINE_CLUSTERING_THRESHOLD, + CONF_TIMELINE_CONTROLS_THUMBNAILS_MODE, + CONF_TIMELINE_CONTROLS_THUMBNAILS_SHOW_DETAILS, + CONF_TIMELINE_CONTROLS_THUMBNAILS_SIZE, + CONF_TIMELINE_MEDIA, CONF_VIEW_CAMERA_SELECT, CONF_VIEW_DEFAULT, CONF_VIEW_TIMEOUT_SECONDS, @@ -147,6 +152,12 @@ const options: EditorOptions = { secondary: localize('editor.image_secondary'), show: false, }, + timeline: { + icon: 'chart-gantt', + name: localize('editor.timeline'), + secondary: localize('editor.timeline_secondary'), + show: false, + }, dimensions: { icon: 'aspect-ratio', name: localize('editor.dimensions'), @@ -315,6 +326,13 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor { value: 'url', label: localize('config.image.modes.url') }, ]; + protected _timelineMediaTypes = [ + { value: '', label: '' }, + { value: 'all', label: localize('config.timeline.medias.all') }, + { value: 'clips', label: localize('config.timeline.medias.clips') }, + { value: 'snapshots', label: localize('config.timeline.medias.snapshots') }, + ]; + public setConfig(config: RawFrigateCardConfig): void { // Note: This does not use Zod to parse the configuration, so it may be // partially or completely invalid. It's more useful to have a partially @@ -969,6 +987,22 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor ${this._renderNumberInput(CONF_IMAGE_REFRESH_SECONDS)} ` : ''} + ${this._renderOptionSetHeader('timeline')} + ${options.timeline.show + ? html`
+ ${this._renderNumberInput(CONF_TIMELINE_CLUSTERING_THRESHOLD)} + ${this._renderOptionSelector(CONF_TIMELINE_MEDIA, this._timelineMediaTypes)} + ${this._renderOptionSelector( + CONF_TIMELINE_CONTROLS_THUMBNAILS_MODE, + this._thumbnailModes, + )} + ${this._renderStringInput(CONF_TIMELINE_CONTROLS_THUMBNAILS_SIZE)} + ${this._renderSwitch( + CONF_TIMELINE_CONTROLS_THUMBNAILS_SHOW_DETAILS, + defaults.event_viewer.controls.thumbnails.show_details, + )} +
` + : ''} ${this._renderOptionSetHeader('dimensions')} ${options.dimensions.show ? html`
diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json index cc5092c9..5039a9ad 100644 --- a/src/localize/languages/en.json +++ b/src/localize/languages/en.json @@ -169,6 +169,22 @@ }, "button_size": "Menu button size (e.g. '40px')" }, + "timeline": { + "clustering_threshold": "The count of events at which they are clustered", + "media": "The media the timeline displays", + "medias": { + "all": "All media types", + "clips": "Clips", + "snapshots": "Snapshots" + }, + "controls": { + "thumbnails": { + "mode": "Timeline thumbnails mode", + "size": "Timeline thumbnails size (e.g. '100px')", + "show_details": "Show event details with thumbnails" + } + } + }, "dimensions": { "aspect_ratio": "Default aspect ratio (e.g. '16:9')", "aspect_ratio_mode": "Aspect ratio mode", @@ -208,7 +224,9 @@ "add_new_camera": "Add new camera", "camera": "Camera", "overrides": "Overrides are active", - "overrides_secondary": "Dynamic configuration overrides detected" + "overrides_secondary": "Dynamic configuration overrides detected", + "timeline": "Timeline", + "timeline_secondary": "Event timeline options" }, "event": { "start": "Start",