Make mini timelines editable.

This commit is contained in:
Dermot Duffy
2022-09-25 09:12:36 -07:00
parent 5ec3cbac54
commit 69ca622284
5 changed files with 136 additions and 8 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
// 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: Make minitimeline configurable in the editor // TODO: time on recording != time on timeline in media viewer after scrolling around
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';
+21
View File
@@ -84,6 +84,17 @@ export const CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL =
`${CONF_MEDIA_VIEWER}.controls.thumbnails.show_timeline_control` as const; `${CONF_MEDIA_VIEWER}.controls.thumbnails.show_timeline_control` as const;
export const CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SIZE = export const CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SIZE =
`${CONF_MEDIA_VIEWER}.controls.thumbnails.size` as const; `${CONF_MEDIA_VIEWER}.controls.thumbnails.size` as const;
export const CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_CLUSTERING_THRESHOLD =
`${CONF_MEDIA_VIEWER}.controls.timeline.clustering_threshold` as const;
export const CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_MEDIA =
`${CONF_MEDIA_VIEWER}.controls.timeline.media` as const;
export const CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_MODE =
`${CONF_MEDIA_VIEWER}.controls.timeline.mode` as const;
export const CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_SHOW_RECORDINGS =
`${CONF_MEDIA_VIEWER}.controls.timeline.show_recordings` as const;
export const CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_WINDOW_SECONDS =
`${CONF_MEDIA_VIEWER}.controls.timeline.window_seconds` as const;
export const CONF_MEDIA_VIEWER_CONTROLS_TITLE_MODE = export const CONF_MEDIA_VIEWER_CONTROLS_TITLE_MODE =
`${CONF_MEDIA_VIEWER}.controls.title.mode` as const; `${CONF_MEDIA_VIEWER}.controls.title.mode` as const;
export const CONF_MEDIA_VIEWER_CONTROLS_TITLE_DURATION_SECONDS = export const CONF_MEDIA_VIEWER_CONTROLS_TITLE_DURATION_SECONDS =
@@ -115,6 +126,16 @@ export const CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_FAVORITE_CONTROL =
`${CONF_LIVE}.controls.thumbnails.show_favorite_control` as const; `${CONF_LIVE}.controls.thumbnails.show_favorite_control` as const;
export const CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL = export const CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL =
`${CONF_LIVE}.controls.thumbnails.show_timeline_control` as const; `${CONF_LIVE}.controls.thumbnails.show_timeline_control` as const;
export const CONF_LIVE_CONTROLS_TIMELINE_CLUSTERING_THRESHOLD =
`${CONF_LIVE}.control s.timeline.clustering_threshold` as const;
export const CONF_LIVE_CONTROLS_TIMELINE_MEDIA =
`${CONF_LIVE}.controls.timeline.media` as const;
export const CONF_LIVE_CONTROLS_TIMELINE_MODE =
`${CONF_LIVE}.controls.timeline.mode` as const;
export const CONF_LIVE_CONTROLS_TIMELINE_SHOW_RECORDINGS =
`${CONF_LIVE}.controls.timeline.show_recordings` as const;
export const CONF_LIVE_CONTROLS_TIMELINE_WINDOW_SECONDS =
`${CONF_LIVE}.controls.timeline.window_seconds` as const;
export const CONF_LIVE_CONTROLS_TITLE_MODE = `${CONF_LIVE}.controls.title.mode` as const; export const CONF_LIVE_CONTROLS_TITLE_MODE = `${CONF_LIVE}.controls.title.mode` as const;
export const CONF_LIVE_CONTROLS_TITLE_DURATION_SECONDS = export const CONF_LIVE_CONTROLS_TITLE_DURATION_SECONDS =
`${CONF_LIVE}.controls.title.duration_seconds` as const; `${CONF_LIVE}.controls.title.duration_seconds` as const;
+103 -6
View File
@@ -54,6 +54,11 @@ import {
CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_FAVORITE_CONTROL, CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_FAVORITE_CONTROL,
CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL, CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL,
CONF_LIVE_CONTROLS_THUMBNAILS_SIZE, CONF_LIVE_CONTROLS_THUMBNAILS_SIZE,
CONF_LIVE_CONTROLS_TIMELINE_CLUSTERING_THRESHOLD,
CONF_LIVE_CONTROLS_TIMELINE_MEDIA,
CONF_LIVE_CONTROLS_TIMELINE_MODE,
CONF_LIVE_CONTROLS_TIMELINE_SHOW_RECORDINGS,
CONF_LIVE_CONTROLS_TIMELINE_WINDOW_SECONDS,
CONF_LIVE_CONTROLS_TITLE_DURATION_SECONDS, CONF_LIVE_CONTROLS_TITLE_DURATION_SECONDS,
CONF_LIVE_CONTROLS_TITLE_MODE, CONF_LIVE_CONTROLS_TITLE_MODE,
CONF_LIVE_DRAGGABLE, CONF_LIVE_DRAGGABLE,
@@ -76,6 +81,11 @@ import {
CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SHOW_FAVORITE_CONTROL, CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SHOW_FAVORITE_CONTROL,
CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL, CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL,
CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SIZE, CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SIZE,
CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_CLUSTERING_THRESHOLD,
CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_MEDIA,
CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_MODE,
CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_SHOW_RECORDINGS,
CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_WINDOW_SECONDS,
CONF_MEDIA_VIEWER_CONTROLS_TITLE_DURATION_SECONDS, CONF_MEDIA_VIEWER_CONTROLS_TITLE_DURATION_SECONDS,
CONF_MEDIA_VIEWER_CONTROLS_TITLE_MODE, CONF_MEDIA_VIEWER_CONTROLS_TITLE_MODE,
CONF_MEDIA_VIEWER_DRAGGABLE, CONF_MEDIA_VIEWER_DRAGGABLE,
@@ -136,8 +146,10 @@ const MENU_CAMERAS_WEBRTC = 'cameras.webrtc';
const MENU_EVENT_GALLERY_CONTROLS = 'event_gallery.controls'; const MENU_EVENT_GALLERY_CONTROLS = 'event_gallery.controls';
const MENU_IMAGE_LAYOUT = 'image.layout'; const MENU_IMAGE_LAYOUT = 'image.layout';
const MENU_LIVE_CONTROLS = 'live.controls'; const MENU_LIVE_CONTROLS = 'live.controls';
const MENU_LIVE_CONTROLS_TIMELINE = 'live.controls.timeline';
const MENU_LIVE_LAYOUT = 'live.layout'; const MENU_LIVE_LAYOUT = 'live.layout';
const MENU_MEDIA_VIEWER_CONTROLS = 'media_viewer.controls'; const MENU_MEDIA_VIEWER_CONTROLS = 'media_viewer.controls';
const MENU_MEDIA_VIEWER_CONTROLS_TIMELINE = 'media_viewer.controls.timeline';
const MENU_MEDIA_VIEWER_LAYOUT = 'media_viewer.layout'; const MENU_MEDIA_VIEWER_LAYOUT = 'media_viewer.layout';
const MENU_TIMELINE_CONTROLS = 'timeline.controls'; const MENU_TIMELINE_CONTROLS = 'timeline.controls';
const MENU_OPTIONS = 'options'; const MENU_OPTIONS = 'options';
@@ -421,6 +433,13 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
{ value: 'fill', label: localize('config.common.layout.fits.fill') }, { value: 'fill', label: localize('config.common.layout.fits.fill') },
]; ];
protected _miniTimelineModes: EditorSelectOption[] = [
{ value: '', label: '' },
{ value: 'none', label: localize('config.timeline.mini.modes.none') },
{ value: 'above', label: localize('config.timeline.mini.modes.above') },
{ value: 'below', label: localize('config.timeline.mini.modes.below') },
];
public setConfig(config: RawFrigateCardConfig): void { public setConfig(config: RawFrigateCardConfig): void {
// Note: This does not use Zod to parse the configuration, so it may be // 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 // partially or completely invalid. It's more useful to have a partially
@@ -819,6 +838,71 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
); );
} }
/**
* Render the core timeline controls (mini or full timeline),
* @param configPathWindowSeconds Timeline window config path.
* @param configPathClusteringThreshold Clustering threshold config path.
* @param configPathTimelineMedia Timeline media config path.
* @param configPathShowRecordings Show recordings config path.
* @param defaultShowRecordings Default value of show_recordings.
* @returns A rendered template.
*/
protected _renderTimelineCoreControls(
configPathWindowSeconds: string,
configPathClusteringThreshold: string,
configPathTimelineMedia: string,
configPathShowRecordings: string,
defaultShowRecordings: boolean,
): TemplateResult {
return html` ${this._renderNumberInput(configPathWindowSeconds, {
label: localize(`config.${CONF_TIMELINE_WINDOW_SECONDS}`),
})}
${this._renderNumberInput(configPathClusteringThreshold, {
label: localize(`config.${CONF_TIMELINE_CLUSTERING_THRESHOLD}`),
})}
${this._renderOptionSelector(configPathTimelineMedia, this._timelineMediaTypes, {
label: localize(`config.${CONF_TIMELINE_MEDIA}`),
})}
${this._renderSwitch(configPathShowRecordings, defaultShowRecordings, {
label: localize(`config.${CONF_TIMELINE_SHOW_RECORDINGS}`),
})}`;
}
/**
* Render the mini timeline controls.
* @param domain The submenu domain.
* @param configPathWindowSeconds Timeline window config path.
* @param configPathClusteringThreshold Clustering threshold config path.
* @param configPathTimelineMedia Timeline media config path.
* @param configPathShowRecordings Show recordings config path.
* @returns A rendered template.
*/
protected _renderMiniTimeline(
domain: string,
configPathMode: string,
configPathWindowSeconds: string,
configPathClusteringThreshold: string,
configPathTimelineMedia: string,
configPathShowRecordings: string,
): TemplateResult | void {
return this._putInSubmenu(
domain,
true,
'config.timeline.mini.options',
{ name: 'mdi:chart-gantt' },
html` ${this._renderOptionSelector(configPathMode, this._miniTimelineModes, {
label: localize('config.timeline.mini.mode'),
})}
${this._renderTimelineCoreControls(
configPathWindowSeconds,
configPathClusteringThreshold,
configPathTimelineMedia,
configPathShowRecordings,
frigateCardConfigDefaults.mini_timeline.show_recordings,
)}`,
);
}
/** /**
* Render a camera section. * Render a camera section.
* @param cameras The full array of cameras. * @param cameras The full array of cameras.
@@ -1312,6 +1396,14 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
max: 60, max: 60,
}, },
)} )}
${this._renderMiniTimeline(
MENU_LIVE_CONTROLS_TIMELINE,
CONF_LIVE_CONTROLS_TIMELINE_MODE,
CONF_LIVE_CONTROLS_TIMELINE_WINDOW_SECONDS,
CONF_LIVE_CONTROLS_TIMELINE_CLUSTERING_THRESHOLD,
CONF_LIVE_CONTROLS_TIMELINE_MEDIA,
CONF_LIVE_CONTROLS_TIMELINE_SHOW_RECORDINGS,
)}
`, `,
)} )}
${this._renderMediaLayout( ${this._renderMediaLayout(
@@ -1429,6 +1521,14 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
CONF_MEDIA_VIEWER_CONTROLS_TITLE_DURATION_SECONDS, CONF_MEDIA_VIEWER_CONTROLS_TITLE_DURATION_SECONDS,
{ min: 0, max: 60 }, { min: 0, max: 60 },
)} )}
${this._renderMiniTimeline(
MENU_MEDIA_VIEWER_CONTROLS_TIMELINE,
CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_MODE,
CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_WINDOW_SECONDS,
CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_CLUSTERING_THRESHOLD,
CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_MEDIA,
CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_SHOW_RECORDINGS,
)}
`, `,
)} )}
${this._renderMediaLayout( ${this._renderMediaLayout(
@@ -1458,13 +1558,10 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
${this._renderOptionSetHeader('timeline')} ${this._renderOptionSetHeader('timeline')}
${this._expandedMenus[MENU_OPTIONS] === 'timeline' ${this._expandedMenus[MENU_OPTIONS] === 'timeline'
? html` <div class="values"> ? html` <div class="values">
${this._renderNumberInput(CONF_TIMELINE_WINDOW_SECONDS)} ${this._renderTimelineCoreControls(
${this._renderNumberInput(CONF_TIMELINE_CLUSTERING_THRESHOLD)} CONF_TIMELINE_WINDOW_SECONDS,
${this._renderOptionSelector( CONF_TIMELINE_CLUSTERING_THRESHOLD,
CONF_TIMELINE_MEDIA, CONF_TIMELINE_MEDIA,
this._timelineMediaTypes,
)}
${this._renderSwitch(
CONF_TIMELINE_SHOW_RECORDINGS, CONF_TIMELINE_SHOW_RECORDINGS,
defaults.timeline.show_recordings, defaults.timeline.show_recordings,
)} )}
+9
View File
@@ -254,6 +254,15 @@
"size": "Timeline thumbnails size in pixels" "size": "Timeline thumbnails size in pixels"
} }
}, },
"mini": {
"options": "Mini Timeline",
"mode": "Mode",
"modes": {
"none": "None",
"above": "Above",
"below": "Below"
}
},
"media": "The media the timeline displays", "media": "The media the timeline displays",
"medias": { "medias": {
"all": "All media types", "all": "All media types",
+1
View File
@@ -1240,6 +1240,7 @@ export const frigateCardConfigDefaults = {
event_gallery: galleryConfigDefault, event_gallery: galleryConfigDefault,
image: imageConfigDefault, image: imageConfigDefault,
timeline: timelineConfigDefault, timeline: timelineConfigDefault,
mini_timeline: timelineCoreConfigDefault,
}; };
const menuButtonSchema = z.discriminatedUnion('type', [ const menuButtonSchema = z.discriminatedUnion('type', [