Make mini timelines editable.
This commit is contained in:
@@ -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';
|
||||||
|
|||||||
@@ -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
@@ -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,
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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', [
|
||||||
|
|||||||
Reference in New Issue
Block a user