Fixes for record button.
This commit is contained in:
@@ -22,9 +22,7 @@ import {
|
|||||||
fetchLatestMediaAndDispatchViewChange,
|
fetchLatestMediaAndDispatchViewChange,
|
||||||
getFullDependentBrowseMediaQueryParametersOrDispatchError,
|
getFullDependentBrowseMediaQueryParametersOrDispatchError,
|
||||||
} from '../utils/ha/browse-media';
|
} from '../utils/ha/browse-media';
|
||||||
import {
|
import { changeViewToRecentRecordingForCameraAndDependents } from '../utils/media-to-view.js';
|
||||||
changeViewToRecentRecording,
|
|
||||||
} from '../utils/media-to-view.js';
|
|
||||||
import { TimelineDataManager } from '../utils/timeline-data-manager';
|
import { TimelineDataManager } from '../utils/timeline-data-manager';
|
||||||
import { View } from '../view.js';
|
import { View } from '../view.js';
|
||||||
import { renderProgressIndicator } from './message.js';
|
import { renderProgressIndicator } from './message.js';
|
||||||
@@ -67,7 +65,7 @@ export class FrigateCardGallery extends LitElement {
|
|||||||
|
|
||||||
if (!this.view.target) {
|
if (!this.view.target) {
|
||||||
if (mediaType === 'recordings') {
|
if (mediaType === 'recordings') {
|
||||||
changeViewToRecentRecording(
|
changeViewToRecentRecordingForCameraAndDependents(
|
||||||
this,
|
this,
|
||||||
this.hass,
|
this.hass,
|
||||||
this.dataManager,
|
this.dataManager,
|
||||||
@@ -237,7 +235,6 @@ export class FrigateCardGalleryCore extends LitElement {
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cameraConfig = this.cameras.get(this.view.camera);
|
|
||||||
return html`
|
return html`
|
||||||
${this._showBackArrow()
|
${this._showBackArrow()
|
||||||
? html` <ha-card
|
? html` <ha-card
|
||||||
@@ -279,7 +276,9 @@ export class FrigateCardGalleryCore extends LitElement {
|
|||||||
.target=${this.view?.target ?? null}
|
.target=${this.view?.target ?? null}
|
||||||
.childIndex=${index}
|
.childIndex=${index}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.clientID=${cameraConfig?.frigate.client_id}
|
.cameraConfig=${child.frigate?.cameraID
|
||||||
|
? this.cameras?.get(child.frigate.cameraID)
|
||||||
|
: undefined}
|
||||||
?details=${!!this.galleryConfig?.controls.thumbnails.show_details}
|
?details=${!!this.galleryConfig?.controls.thumbnails.show_details}
|
||||||
?show_favorite_control=${!!this.galleryConfig?.controls.thumbnails
|
?show_favorite_control=${!!this.galleryConfig?.controls.thumbnails
|
||||||
.show_favorite_control}
|
.show_favorite_control}
|
||||||
@@ -287,13 +286,16 @@ export class FrigateCardGalleryCore extends LitElement {
|
|||||||
.show_timeline_control}
|
.show_timeline_control}
|
||||||
@click=${(ev: Event) => {
|
@click=${(ev: Event) => {
|
||||||
if (this.view) {
|
if (this.view) {
|
||||||
|
const targetView = this.view.getViewerViewForGalleryView();
|
||||||
|
if (targetView) {
|
||||||
this.view
|
this.view
|
||||||
.evolve({
|
.evolve({
|
||||||
view: this.view.is('clips') ? 'clip' : 'snapshot',
|
view: targetView,
|
||||||
childIndex: index,
|
childIndex: index,
|
||||||
})
|
})
|
||||||
.dispatchChangeEvent(this);
|
.dispatchChangeEvent(this);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
stopEventFromActivatingCardWideActions(ev);
|
stopEventFromActivatingCardWideActions(ev);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ export class FrigateCardThumbnailCarousel extends LitElement {
|
|||||||
.target=${parent}
|
.target=${parent}
|
||||||
.childIndex=${childIndex}
|
.childIndex=${childIndex}
|
||||||
.mediaSeek=${this.view?.context?.mediaViewer?.seek.get(childIndex)}
|
.mediaSeek=${this.view?.context?.mediaViewer?.seek.get(childIndex)}
|
||||||
.clientID=${cameraConfig?.frigate.client_id}
|
.cameraConfig=${cameraConfig ?? undefined}
|
||||||
?details=${this.config?.show_details}
|
?details=${this.config?.show_details}
|
||||||
?show_favorite_control=${this.config?.show_favorite_control}
|
?show_favorite_control=${this.config?.show_favorite_control}
|
||||||
?show_timeline_control=${this.config?.show_timeline_control}
|
?show_timeline_control=${this.config?.show_timeline_control}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import thumbnailFeatureRecordingStyle from '../scss/thumbnail-feature-recording.
|
|||||||
import thumbnailStyle from '../scss/thumbnail.scss';
|
import thumbnailStyle from '../scss/thumbnail.scss';
|
||||||
import { stopEventFromActivatingCardWideActions } from '../utils/action.js';
|
import { stopEventFromActivatingCardWideActions } from '../utils/action.js';
|
||||||
import { errorToConsole, prettifyTitle } from '../utils/basic.js';
|
import { errorToConsole, prettifyTitle } from '../utils/basic.js';
|
||||||
|
import { getCameraTitle } from '../utils/camera.js';
|
||||||
import { retainEvent } from '../utils/frigate.js';
|
import { retainEvent } from '../utils/frigate.js';
|
||||||
import { getEventDurationString } from '../utils/frigate.js';
|
import { getEventDurationString } from '../utils/frigate.js';
|
||||||
import { renderTask } from '../utils/task.js';
|
import { renderTask } from '../utils/task.js';
|
||||||
@@ -19,6 +20,7 @@ import { MediaSeek } from './viewer.js';
|
|||||||
import { TaskStatus } from '@lit-labs/task';
|
import { TaskStatus } from '@lit-labs/task';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
CameraConfig,
|
||||||
ExtendedHomeAssistant,
|
ExtendedHomeAssistant,
|
||||||
FrigateBrowseMediaSource,
|
FrigateBrowseMediaSource,
|
||||||
FrigateEvent,
|
FrigateEvent,
|
||||||
@@ -108,6 +110,9 @@ export class FrigateCardThumbnailFeatureRecording extends LitElement {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
public date?: Date;
|
public date?: Date;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
public cameraTitle?: string;
|
||||||
|
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
if (!this.date) {
|
if (!this.date) {
|
||||||
return;
|
return;
|
||||||
@@ -115,6 +120,7 @@ export class FrigateCardThumbnailFeatureRecording extends LitElement {
|
|||||||
return html`
|
return html`
|
||||||
<div class="title">${format(this.date, 'HH:mm')}</div>
|
<div class="title">${format(this.date, 'HH:mm')}</div>
|
||||||
<div class="subtitle">${format(this.date, 'MMM do')}</div>
|
<div class="subtitle">${format(this.date, 'MMM do')}</div>
|
||||||
|
${this.cameraTitle ? html`<div class="camera">${this.cameraTitle}</div>` : html``}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,7 +246,7 @@ export class FrigateCardThumbnail extends LitElement {
|
|||||||
public hass?: ExtendedHomeAssistant;
|
public hass?: ExtendedHomeAssistant;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
public clientID?: string;
|
public cameraConfig?: CameraConfig;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the element.
|
* Render the element.
|
||||||
@@ -277,6 +283,7 @@ export class FrigateCardThumbnail extends LitElement {
|
|||||||
starred: !!event?.retain_indefinitely,
|
starred: !!event?.retain_indefinitely,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const clientID = this.cameraConfig?.frigate.client_id;
|
||||||
return html` ${event
|
return html` ${event
|
||||||
? html`<frigate-card-thumbnail-feature-event
|
? html`<frigate-card-thumbnail-feature-event
|
||||||
aria-label="${label ?? ''}"
|
aria-label="${label ?? ''}"
|
||||||
@@ -285,22 +292,25 @@ export class FrigateCardThumbnail extends LitElement {
|
|||||||
.thumbnail=${thumbnail ?? undefined}
|
.thumbnail=${thumbnail ?? undefined}
|
||||||
.label=${label ?? undefined}
|
.label=${label ?? undefined}
|
||||||
></frigate-card-thumbnail-feature-event>`
|
></frigate-card-thumbnail-feature-event>`
|
||||||
: html`<frigate-card-thumbnail-feature-recording
|
: recording
|
||||||
|
? html`<frigate-card-thumbnail-feature-recording
|
||||||
aria-label="${label ?? ''}"
|
aria-label="${label ?? ''}"
|
||||||
title="${label ?? ''}"
|
title="${label ?? ''}"
|
||||||
|
.cameraTitle=${this.details || !this.cameraConfig || !this.hass ? undefined : getCameraTitle(this.hass, this.cameraConfig)}
|
||||||
.date=${recording ? fromUnixTime(recording.start_time) : undefined}
|
.date=${recording ? fromUnixTime(recording.start_time) : undefined}
|
||||||
></frigate-card-thumbnail-feature-recording>`}
|
></frigate-card-thumbnail-feature-recording>`
|
||||||
${this.show_favorite_control && event && this.hass && this.clientID
|
: html``}
|
||||||
|
${this.show_favorite_control && event && this.hass && clientID
|
||||||
? html` <ha-icon
|
? html` <ha-icon
|
||||||
class="${classMap(starClasses)}"
|
class="${classMap(starClasses)}"
|
||||||
icon=${event?.retain_indefinitely ? 'mdi:star' : 'mdi:star-outline'}
|
icon=${event?.retain_indefinitely ? 'mdi:star' : 'mdi:star-outline'}
|
||||||
title=${localize('thumbnail.retain_indefinitely')}
|
title=${localize('thumbnail.retain_indefinitely')}
|
||||||
@click=${(ev: Event) => {
|
@click=${(ev: Event) => {
|
||||||
stopEventFromActivatingCardWideActions(ev);
|
stopEventFromActivatingCardWideActions(ev);
|
||||||
if (event && this.hass && this.clientID) {
|
if (event && this.hass && clientID) {
|
||||||
retainEvent(
|
retainEvent(
|
||||||
this.hass,
|
this.hass,
|
||||||
this.clientID,
|
clientID,
|
||||||
event.id,
|
event.id,
|
||||||
!event.retain_indefinitely,
|
!event.retain_indefinitely,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1024,6 +1024,7 @@ export class FrigateCardTimelineCore extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const options = this._getOptions();
|
const options = this._getOptions();
|
||||||
|
let createdTimeline = false;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.timelineDataManager &&
|
this.timelineDataManager &&
|
||||||
@@ -1056,6 +1057,7 @@ export class FrigateCardTimelineCore extends LitElement {
|
|||||||
this.timelineConfig.media,
|
this.timelineConfig.media,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
createdTimeline = true;
|
||||||
if (this.mini && groups.length === 1) {
|
if (this.mini && groups.length === 1) {
|
||||||
// In a mini timeline, if there's only one group don't bother grouping
|
// In a mini timeline, if there's only one group don't bother grouping
|
||||||
// at all.
|
// at all.
|
||||||
@@ -1097,9 +1099,17 @@ export class FrigateCardTimelineCore extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (changedProperties.has('view')) {
|
if (changedProperties.has('view')) {
|
||||||
|
if (createdTimeline) {
|
||||||
|
// If the timeline was just created, give it one frame to draw itself.
|
||||||
|
// Failure to do so may result in subsequent calls to
|
||||||
|
// `this._timeline.setwindow()` being entirely ignored. Example case:
|
||||||
|
// Clicking the timeline control on a recording thumbnail.
|
||||||
|
window.requestAnimationFrame(this._updateTimelineFromView.bind(this));
|
||||||
|
} else {
|
||||||
this._updateTimelineFromView();
|
this._updateTimelineFromView();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return compiled CSS styles.
|
* Return compiled CSS styles.
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ import { EmblaCarouselPlugins } from './carousel.js';
|
|||||||
import { renderTask } from '../utils/task.js';
|
import { renderTask } from '../utils/task.js';
|
||||||
import { updateElementStyleFromMediaLayoutConfig } from '../utils/media-layout.js';
|
import { updateElementStyleFromMediaLayoutConfig } from '../utils/media-layout.js';
|
||||||
import { TimelineDataManager } from '../utils/timeline-data-manager.js';
|
import { TimelineDataManager } from '../utils/timeline-data-manager.js';
|
||||||
import { changeViewToRecentRecording } from '../utils/media-to-view.js';
|
import { changeViewToRecentRecordingForCameraAndDependents } from '../utils/media-to-view.js';
|
||||||
|
|
||||||
export interface MediaSeek {
|
export interface MediaSeek {
|
||||||
// Specifies the point at which this recording should be played, the
|
// Specifies the point at which this recording should be played, the
|
||||||
@@ -131,7 +131,7 @@ export class FrigateCardViewer extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mediaType === 'recordings') {
|
if (mediaType === 'recordings') {
|
||||||
changeViewToRecentRecording(
|
changeViewToRecentRecordingForCameraAndDependents(
|
||||||
this,
|
this,
|
||||||
this.hass,
|
this.hass,
|
||||||
this.timelineDataManager,
|
this.timelineDataManager,
|
||||||
|
|||||||
@@ -21,7 +21,9 @@
|
|||||||
div {
|
div {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.title {
|
div.title {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
|
div.camera {
|
||||||
|
font-size: 0.7em;
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
sortSegmentsOldestToYoungest,
|
sortSegmentsOldestToYoungest,
|
||||||
TimelineDataManager,
|
TimelineDataManager,
|
||||||
} from './timeline-data-manager';
|
} from './timeline-data-manager';
|
||||||
|
import { getAllDependentCameras } from './camera.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change the view to a recent recording.
|
* Change the view to a recent recording.
|
||||||
@@ -20,17 +21,15 @@ import {
|
|||||||
* @param dataManager The datamanager to use for data access.
|
* @param dataManager The datamanager to use for data access.
|
||||||
* @param cameras The camera configurations.
|
* @param cameras The camera configurations.
|
||||||
* @param view The current view.
|
* @param view The current view.
|
||||||
* @param cameraIDs The camera IDs to include recordings for.
|
* @param options A set of cameraIDs to fetch recordings for, and a targetView to dispatch to.
|
||||||
* @param options A specific window (start and end) to fetch recordings for, and a targetTime to seek to.
|
|
||||||
*/
|
*/
|
||||||
export const changeViewToRecentRecording = async (
|
export const changeViewToRecentRecordingForCameraAndDependents = async (
|
||||||
element: HTMLElement,
|
element: HTMLElement,
|
||||||
hass: ExtendedHomeAssistant,
|
hass: ExtendedHomeAssistant,
|
||||||
dataManager: TimelineDataManager,
|
dataManager: TimelineDataManager,
|
||||||
cameras: Map<string, CameraConfig>,
|
cameras: Map<string, CameraConfig>,
|
||||||
view: View,
|
view: View,
|
||||||
options?: {
|
options?: {
|
||||||
cameraIDs?: Set<string>;
|
|
||||||
targetView?: 'recording' | 'recordings';
|
targetView?: 'recording' | 'recordings';
|
||||||
},
|
},
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
@@ -46,6 +45,7 @@ import {
|
|||||||
...options,
|
...options,
|
||||||
|
|
||||||
// Fetch 1 days worth of recordings (including recordings that are for the current hour).
|
// Fetch 1 days worth of recordings (including recordings that are for the current hour).
|
||||||
|
cameraIDs: getAllDependentCameras(cameras, view.camera),
|
||||||
start: sub(now, { days: 1 }),
|
start: sub(now, { days: 1 }),
|
||||||
end: add(now, { hours: 1 }),
|
end: add(now, { hours: 1 }),
|
||||||
}
|
}
|
||||||
@@ -59,8 +59,9 @@ import {
|
|||||||
* @param dataManager The datamanager to use for data access.
|
* @param dataManager The datamanager to use for data access.
|
||||||
* @param cameras The camera configurations.
|
* @param cameras The camera configurations.
|
||||||
* @param view The current view.
|
* @param view The current view.
|
||||||
* @param cameraIDs The camera IDs to include recordings for.
|
* @param options A specific window (start and end) to fetch recordings for, a
|
||||||
* @param options A specific window (start and end) to fetch recordings for, and a targetTime to seek to.
|
* targetTime to seek to, a targetView to dispatch to and a set of cameraIDs to
|
||||||
|
* restrict to.
|
||||||
*/
|
*/
|
||||||
export const changeViewToRecording = async (
|
export const changeViewToRecording = async (
|
||||||
element: HTMLElement,
|
element: HTMLElement,
|
||||||
|
|||||||
+14
@@ -150,6 +150,20 @@ export class View {
|
|||||||
return ['clips', 'snapshots', 'recordings'].includes(this.view);
|
return ['clips', 'snapshots', 'recordings'].includes(this.view);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the viewer view given a gallery view.
|
||||||
|
*/
|
||||||
|
public getViewerViewForGalleryView(): 'clip' | 'snapshot' | 'recording' | null {
|
||||||
|
if (this.is('clips')) {
|
||||||
|
return 'clip';
|
||||||
|
} else if (this.is('snapshots')) {
|
||||||
|
return 'snapshot';
|
||||||
|
} else if (this.is('recordings')) {
|
||||||
|
return 'recording';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if a view is of a piece of media (including the media viewer,
|
* Determine if a view is of a piece of media (including the media viewer,
|
||||||
* live view, image view -- anything that can create a MediaLoadedInfo event).
|
* live view, image view -- anything that can create a MediaLoadedInfo event).
|
||||||
|
|||||||
Reference in New Issue
Block a user