Show thumbnails as tooltips.

This commit is contained in:
Dermot Duffy
2022-04-09 09:39:34 -07:00
parent 79bf66d961
commit 616c24c94b
5 changed files with 120 additions and 35 deletions
+1
View File
@@ -179,6 +179,7 @@ export class FrigateCardThumbnailCarousel extends FrigateCardCarousel {
.target=${parent}
.childIndex=${childIndex}
?details=${this._config?.show_details}
?controls=${true}
thumbnail_size=${ifDefined(this._config?.size)}
class="${classMap(classes)}"
@click=${(ev) => {
+68 -32
View File
@@ -2,7 +2,7 @@ import { CSSResult, TemplateResult, html, unsafeCSS, LitElement } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { format, fromUnixTime } from 'date-fns';
import type { FrigateBrowseMediaSource } from '../types.js';
import type { FrigateBrowseMediaSource, FrigateEvent } from '../types.js';
import { View } from '../view.js';
import {
getEventDurationString,
@@ -15,6 +15,20 @@ import thumbnailStyle from '../scss/thumbnail.scss';
@customElement('frigate-card-thumbnail')
export class FrigateCardThumbnail extends LitElement {
@property({ attribute: true, type: Boolean })
public details = false;
@property({ attribute: true, type: Boolean })
public controls = false;
@property({ attribute: false })
set thumbnail_size(size: number) {
this.style.setProperty('--frigate-card-thumbnail-size', String(size));
}
// ============================
// Data-binding based interface
// ============================
@property({ attribute: false })
protected view?: Readonly<View>;
@@ -24,34 +38,54 @@ export class FrigateCardThumbnail extends LitElement {
@property({ attribute: false })
public childIndex?: number;
@property({ attribute: true, type: Boolean, reflect: true })
public details = false;
// =============================================================
// Overrides that can be used if data bindings are not available
// =============================================================
@property({ attribute: true })
public thumbnail?: string;
@property({ attribute: false })
set thumbnail_size(size: number) {
this.style.setProperty('--frigate-card-thumbnail-size', String(size));
}
@property({ attribute: true })
public label?: string;
@property({ attribute: true })
public event?: string;
/**
* Render the element.
* @returns A template to display to the user.
*/
protected render(): TemplateResult | void {
if (!this.target || !this.target.children || this.childIndex === undefined) {
return;
let event: FrigateEvent | null = null;
let thumbnail: string | null = null;
let label: string | null = null;
// Take the event / thumbnail / label from the data-bound media (if specified).
if (this.target && this.target.children && this.childIndex !== undefined) {
const media = this.target.children[this.childIndex];
event = media.frigate?.event ?? null;
thumbnail = media.thumbnail;
label = media.title;
}
const media = this.target.children[this.childIndex];
if (!media.thumbnail) {
// Always give the overrides preference (if specified).
if (this.event) {
event = JSON.parse(this.event);
}
thumbnail = this.thumbnail ? this.thumbnail : thumbnail;
label = this.label ? this.label : label;
if (!thumbnail) {
return;
}
const event = media.frigate?.event;
console.info;
return html` <img
aria-label="${media.title}"
src="${media.thumbnail}"
title="${media.title}"
aria-label="${label ?? ''}"
src="${thumbnail}"
title="${label ?? ''}"
/>
${event?.retain_indefinitely
${this.controls && event?.retain_indefinitely
? html` <ha-icon
class="favorite"
icon="mdi:star"
@@ -80,22 +114,24 @@ export class FrigateCardThumbnail extends LitElement {
</div>
</div>`
: html``}
<ha-icon
class="timeline"
icon="mdi:target"
title=${localize('thumbnail.timeline')}
@click=${(ev: Event) => {
stopEventFromActivatingCardWideActions(ev);
this.view
?.evolve({
view: 'timeline',
target: this.target,
childIndex: this.childIndex ?? null,
context: {},
})
.dispatchChangeEvent(this);
}}
></ha-icon>`;
${this.controls
? html`<ha-icon
class="timeline"
icon="mdi:target"
title=${localize('thumbnail.timeline')}
@click=${(ev: Event) => {
stopEventFromActivatingCardWideActions(ev);
this.view
?.evolve({
view: 'timeline',
target: this.target,
childIndex: this.childIndex ?? null,
context: {},
})
.dispatchChangeEvent(this);
}}
></ha-icon>`
: ''}`;
}
/**
+44 -3
View File
@@ -1,5 +1,7 @@
// TODO: Hover over an event should show something useful.
// TODO: Periodically refetch events.
// TODO: Editor support for timeline (incl. in views).
// TODO: Make thumbnail controls optional (in all places that use thumbnails).
// TODO: Search for TODOs and logging statements.
import {
@@ -74,11 +76,14 @@ class TimelineEventManager {
// The latest date managed.
protected _dateEnd?: Date;
protected _contentCallback?: (source: FrigateBrowseMediaSource) => string;
protected _tooltipCallback?: (source: FrigateBrowseMediaSource) => string;
constructor(params?: {
contentCallback?: (source: FrigateBrowseMediaSource) => string;
tooltipCallback?: (source: FrigateBrowseMediaSource) => string;
}) {
this._contentCallback = params?.contentCallback;
this._tooltipCallback = params?.tooltipCallback;
}
/**
@@ -119,6 +124,7 @@ class TimelineEventManager {
id: event.id,
group: camera,
content: this._contentCallback?.(child) ?? '',
title: this._tooltipCallback?.(child) ?? '',
start: event.start_time * 1000,
event: event,
};
@@ -306,11 +312,41 @@ export class FrigateCardTimelineCore extends LitElement {
@property({ attribute: false })
protected timelineConfig?: TimelineConfig;
protected _events = new TimelineEventManager();
protected _events = new TimelineEventManager({
tooltipCallback: this._getTooltip.bind(this),
});
protected _refTimeline: Ref<HTMLElement> = createRef();
protected _thumbnails?: FrigateBrowseMediaSource;
protected _timeline?: Timeline;
/**
* Get a tooltip for a given timeline event.
* @param source The FrigateBrowseMediaSource in question.
* @returns The tooltip as a string to render.
*/
protected _getTooltip(source: FrigateBrowseMediaSource): string {
const thumbnailSizeAttr = this.timelineConfig
? `thumbnail_size="${this.timelineConfig.controls.thumbnails.size}"`
: '';
const eventAttr = source.frigate?.event
? `event='${JSON.stringify(source.frigate.event)}'`
: '';
console.info(eventAttr);
// Cannot use Lit data-bindings as visjs requires a string for tooltips.
// Note that changes to attributes here must be mirrored in the xss
// whitelist in `_getOptions()` .
return `
<frigate-card-thumbnail
details
thumbnail="${source.thumbnail}"
label="${source.title}"
${eventAttr}
${thumbnailSizeAttr}
>
</frigate-card-thumbnail>`;
}
/**
* Master render method.
* @returns A rendered template.
@@ -575,14 +611,19 @@ export class FrigateCardTimelineCore extends LitElement {
start: start,
end: end,
groupHeightMode: 'fixed',
tooltip: {
followMouse: true,
overflowMethod: 'cap',
},
xss: {
disabled: false,
filterOptions: {
whiteList: {
'frigate-card-timeline-event': [
'frigate-card-thumbnail': [
'details',
'thumbnail',
'label',
'media_id',
'event',
'thumbnail_size',
],
div: ['title'],
+4
View File
@@ -16,6 +16,10 @@
border: 1px solid var(--primary-color);
border-radius: var(--ha-card-border-radius, 4px);
padding: 2px;
// When details are enabled, use a background color so that the details have
// contrast with the background.
background-color: var(--primary-background-color, black);
}
img {
+3
View File
@@ -85,4 +85,7 @@ div.vis-tooltip {
padding: 0px;
background-color: unset;
border: none;
// Use browser default font-family for tooltips.
font-family: unset;
}