Match media direction to timeline.

This commit is contained in:
Dermot Duffy
2023-02-08 19:51:57 -08:00
parent 1d4cb3f9ff
commit 479671ab6c
8 changed files with 77 additions and 56 deletions
+1 -1
View File
@@ -26,7 +26,7 @@
"custom-card-helpers": "^1.9.0", "custom-card-helpers": "^1.9.0",
"date-fns": "^2.29.2", "date-fns": "^2.29.2",
"date-fns-tz": "^1.3.7", "date-fns-tz": "^1.3.7",
"embla-carousel": "^7.0.2", "embla-carousel": "^7.0.9",
"embla-carousel-wheel-gestures": "^3.0.0", "embla-carousel-wheel-gestures": "^3.0.0",
"home-assistant-js-websocket": "^8.0.0", "home-assistant-js-websocket": "^8.0.0",
"keycharm": "^0.4.0", "keycharm": "^0.4.0",
-1
View File
@@ -1,4 +1,3 @@
import { HomeAssistant } from 'custom-card-helpers';
import fromUnixTime from 'date-fns/fromUnixTime'; import fromUnixTime from 'date-fns/fromUnixTime';
import isEqual from 'lodash-es/isEqual'; import isEqual from 'lodash-es/isEqual';
import { CameraConfig } from '../../types'; import { CameraConfig } from '../../types';
+3 -2
View File
@@ -525,9 +525,10 @@ export class CameraManager {
// are assumed to be unique. // are assumed to be unique.
uniqBy(mediaArray, (media) => media.getID() ?? media), uniqBy(mediaArray, (media) => media.getID() ?? media),
// Sort all items leading with the most recent. // Sort all items leading oldest -> youngest (so media is loaded in this
// order in the viewer which matches the left-to-right timeline order).
(media) => media.getStartTime(), (media) => media.getStartTime(),
'desc', 'asc',
); );
} }
+50 -37
View File
@@ -35,6 +35,7 @@ import { MediaQueriesResults } from '../view/media-queries-results';
import { errorToConsole } from '../utils/basic'; import { errorToConsole } from '../utils/basic';
import './media-filter'; import './media-filter';
import "./surround-basic"; import "./surround-basic";
import { ViewMedia } from '../view/media';
const GALLERY_MEDIA_CHUNK_SIZE = 100; const GALLERY_MEDIA_CHUNK_SIZE = 100;
@@ -91,8 +92,8 @@ export class FrigateCardGallery extends LitElement {
const mediaType = this.view.is('snapshots') const mediaType = this.view.is('snapshots')
? 'snapshots' ? 'snapshots'
: this.view.is('clips') : this.view.is('clips')
? 'clips' ? 'clips'
: null; : null;
changeViewToRecentEventsForCameraAndDependents( changeViewToRecentEventsForCameraAndDependents(
this, this,
this.hass, this.hass,
@@ -110,14 +111,14 @@ export class FrigateCardGallery extends LitElement {
return html` return html`
<frigate-card-surround-basic <frigate-card-surround-basic
.drawerIcons=${{ .drawerIcons=${{
...(this.galleryConfig && ...(this.galleryConfig &&
this.galleryConfig.controls.filter.mode !== 'none' && { this.galleryConfig.controls.filter.mode !== 'none' && {
[this.galleryConfig.controls.filter.mode]: GALLERY_MEDIA_FILTER_MENU_ICONS, [this.galleryConfig.controls.filter.mode]: GALLERY_MEDIA_FILTER_MENU_ICONS,
}), }),
}} }}
> >
${this.galleryConfig && this.galleryConfig.controls.filter.mode !== 'none' ${this.galleryConfig && this.galleryConfig.controls.filter.mode !== 'none'
? html` <frigate-card-media-filter ? html` <frigate-card-media-filter
.hass=${this.hass} .hass=${this.hass}
.cameras=${this.cameras} .cameras=${this.cameras}
.cameraManager=${this.cameraManager} .cameraManager=${this.cameraManager}
@@ -126,7 +127,7 @@ export class FrigateCardGallery extends LitElement {
slot=${this.galleryConfig.controls.filter.mode} slot=${this.galleryConfig.controls.filter.mode}
> >
</frigate-card-media-filter>` </frigate-card-media-filter>`
: ''} : ''}
<frigate-card-gallery-core <frigate-card-gallery-core
.hass=${this.hass} .hass=${this.hass}
.view=${this.view} .view=${this.view}
@@ -181,6 +182,8 @@ export class FrigateCardGalleryCore extends LitElement {
@state() @state()
protected _showExtensionLoader = true; protected _showExtensionLoader = true;
protected _media?: ViewMedia[];
constructor() { constructor() {
super(); super();
this._resizeObserver = new ResizeObserver(this._resizeHandler.bind(this)); this._resizeObserver = new ResizeObserver(this._resizeHandler.bind(this));
@@ -220,10 +223,10 @@ export class FrigateCardGalleryCore extends LitElement {
const columns = this.galleryConfig?.controls.thumbnails.show_details const columns = this.galleryConfig?.controls.thumbnails.show_details
? Math.max(1, Math.floor(this.clientWidth / THUMBNAIL_DETAILS_WIDTH_MIN)) ? Math.max(1, Math.floor(this.clientWidth / THUMBNAIL_DETAILS_WIDTH_MIN))
: Math.max( : Math.max(
1, 1,
Math.ceil(this.clientWidth / THUMBNAIL_WIDTH_MAX), Math.ceil(this.clientWidth / THUMBNAIL_WIDTH_MAX),
Math.ceil(this.clientWidth / thumbnailSize), Math.ceil(this.clientWidth / thumbnailSize),
); );
this.style.setProperty('--frigate-card-gallery-columns', String(columns)); this.style.setProperty('--frigate-card-gallery-columns', String(columns));
} }
@@ -272,8 +275,8 @@ export class FrigateCardGalleryCore extends LitElement {
const newMediaQueries = MediaQueriesClassifier.areEventQueries(query) const newMediaQueries = MediaQueriesClassifier.areEventQueries(query)
? new EventMediaQueries(extension.queries as EventQuery[]) ? new EventMediaQueries(extension.queries as EventQuery[])
: MediaQueriesClassifier.areRecordingQueries(query) : MediaQueriesClassifier.areRecordingQueries(query)
? new RecordingMediaQueries(extension.queries as RecordingQuery[]) ? new RecordingMediaQueries(extension.queries as RecordingQuery[])
: null; : null;
if (newMediaQueries) { if (newMediaQueries) {
this.view this.view
@@ -307,6 +310,14 @@ export class FrigateCardGalleryCore extends LitElement {
} }
if (changedProps.has('view')) { if (changedProps.has('view')) {
this._showExtensionLoader = true; this._showExtensionLoader = true;
const oldView: View | undefined = changedProps.get('view');
if (oldView?.queryResults?.getResults() !== this.view?.queryResults?.getResults()) {
// Gallery places the most recent media at the top (the query results place
// the most recent media at the end for use in the viewer). This is copied
// to a new array to avoid reversing the query results in place.
this._media = [...this.view?.queryResults?.getResults() ?? []].reverse();
}
} }
} }
@@ -315,10 +326,8 @@ export class FrigateCardGalleryCore extends LitElement {
* @returns A rendered template. * @returns A rendered template.
*/ */
protected render(): TemplateResult | void { protected render(): TemplateResult | void {
const results = this.view?.queryResults?.getResults();
if ( if (
!results || !this._media ||
!this.hass || !this.hass ||
!this.view || !this.view ||
!this.view.isGalleryView() || !this.view.isGalleryView() ||
@@ -328,9 +337,9 @@ export class FrigateCardGalleryCore extends LitElement {
} }
return html` return html`
${results.map( ${this._media.map(
(media, index) => (media, index) =>
html`<frigate-card-thumbnail html`<frigate-card-thumbnail
.hass=${this.hass} .hass=${this.hass}
.cameraManager=${this.cameraManager} .cameraManager=${this.cameraManager}
.media=${media} .media=${media}
@@ -338,28 +347,32 @@ export class FrigateCardGalleryCore extends LitElement {
.view=${this.view} .view=${this.view}
?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}
?show_timeline_control=${!!this.galleryConfig?.controls.thumbnails ?show_timeline_control=${!!this.galleryConfig?.controls.thumbnails
.show_timeline_control} .show_timeline_control}
@click=${(ev: Event) => { @click=${(ev: Event) => {
if (this.view) { if (this.view && this._media) {
this.view this.view
.evolve({ .evolve({
view: 'media', view: 'media',
queryResults: this.view.queryResults?.clone().selectResult(index), queryResults: this.view.queryResults?.clone().selectResult(
}) // Media in the gallery is reversed vs the queryResults (see
.dispatchChangeEvent(this); // note above).
} this._media.length - index - 1
stopEventFromActivatingCardWideActions(ev); ),
}} })
.dispatchChangeEvent(this);
}
stopEventFromActivatingCardWideActions(ev);
}}
> >
</frigate-card-thumbnail>`, </frigate-card-thumbnail>`,
)} )}
${this._showExtensionLoader ${this._showExtensionLoader
? html`${renderProgressIndicator({ ? html`${renderProgressIndicator({
cardWideConfig: this.cardWideConfig, cardWideConfig: this.cardWideConfig,
componentRef: this._refLoader, componentRef: this._refLoader,
})}` })}`
: ''} : ''}
`; `;
} }
+15 -10
View File
@@ -402,18 +402,23 @@ export class FrigateCardViewerCarousel extends LitElement {
.dispatchChangeEvent(this); .dispatchChangeEvent(this);
} }
/**
* Handle the user selecting a new slide in the carousel.
*/
protected _setViewHandler(ev: CustomEvent<CarouselSelect>): void { protected _setViewHandler(ev: CustomEvent<CarouselSelect>): void {
// The slide may already be selected on load, so don't dispatch a new view this._setViewSelectedIndex(ev.detail.index);
// unless necessary.
if (ev.detail.index !== this.view?.queryResults?.getSelectedIndex()) {
this._setViewSelectedIndex(ev.detail.index);
}
} }
protected _setViewSelectedIndex(index: number): void { protected _setViewSelectedIndex(index: number): void {
if (!this.view?.queryResults) {
return;
}
const selectedIndex = this.view.queryResults.getSelectedIndex();
if (selectedIndex === null || selectedIndex === index) {
// The slide may already be selected on load, so don't dispatch a new view
// unless necessary (i.e. the new index is different from the current
// index).
return;
}
const newResults = this.view?.queryResults?.clone().selectResult(index); const newResults = this.view?.queryResults?.clone().selectResult(index);
if (!newResults) { if (!newResults) {
return; return;
@@ -555,7 +560,7 @@ export class FrigateCardViewerCarousel extends LitElement {
} }
const media = this.view?.queryResults?.getSelectedResult(); const media = this.view?.queryResults?.getSelectedResult();
if (!media || !this.cameras) { if (!media || !this.cameras || !this.view || !this.view.queryResults) {
return; return;
} }
@@ -578,7 +583,7 @@ export class FrigateCardViewerCarousel extends LitElement {
draggable: this.viewerConfig?.draggable ?? true, draggable: this.viewerConfig?.draggable ?? true,
}))} }))}
.carouselPlugins=${guard( .carouselPlugins=${guard(
[this.viewerConfig, this.view?.queryResults?.getResults()], [this.viewerConfig, this.view.queryResults.getResults()],
this._getPlugins.bind(this), this._getPlugins.bind(this),
)} )}
.label=${media.getTitle() ?? undefined} .label=${media.getTitle() ?? undefined}
+3 -2
View File
@@ -198,8 +198,9 @@ const executeMediaQueryForView = async (
if (!mediaArray) { if (!mediaArray) {
return null; return null;
} }
// Select the last item by default (which is the most recent).
const queryResults = new MediaQueriesResults(mediaArray); const selectedIndex = mediaArray.length ? mediaArray.length - 1 : undefined;
const queryResults = new MediaQueriesResults(mediaArray, selectedIndex);
let viewerContext: ViewContext | undefined = {}; let viewerContext: ViewContext | undefined = {};
if (options?.targetTime && options.cameraIDs) { if (options?.targetTime && options.cameraIDs) {
+3 -1
View File
@@ -132,12 +132,13 @@ export class TimelineDataSource {
} }
const mediaArray = await this._cameraManager.executeMediaQueries(hass, eventQueries); const mediaArray = await this._cameraManager.executeMediaQueries(hass, eventQueries);
const data: FrigateCardTimelineItem[] = []
for (const media of mediaArray ?? []) { for (const media of mediaArray ?? []) {
const endTime = media.getEndTime(); const endTime = media.getEndTime();
const startTime = media.getStartTime(); const startTime = media.getStartTime();
const id = media.getID(); const id = media.getID();
if (id && startTime) { if (id && startTime) {
this._dataset.update({ data.push({
id: id, id: id,
group: media.getCameraID(), group: media.getCameraID(),
content: '', content: '',
@@ -148,6 +149,7 @@ export class TimelineDataSource {
}); });
} }
} }
this._dataset.update(data);
this._eventRanges.add({ this._eventRanges.add({
...cacheFriendlyWindow, ...cacheFriendlyWindow,
+2 -2
View File
@@ -1858,7 +1858,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"embla-carousel@npm:^7.0.2": "embla-carousel@npm:^7.0.9":
version: 7.0.9 version: 7.0.9
resolution: "embla-carousel@npm:7.0.9" resolution: "embla-carousel@npm:7.0.9"
checksum: dabb371a44c2d67f21aa7a0ff3f452a5c2678b79e80cb251e0a83b707c4b684fbdbe010927142cdcae3d1c9711a269e05655bd05c39608337fd1b6f5d2c20371 checksum: dabb371a44c2d67f21aa7a0ff3f452a5c2678b79e80cb251e0a83b707c4b684fbdbe010927142cdcae3d1c9711a269e05655bd05c39608337fd1b6f5d2c20371
@@ -2424,7 +2424,7 @@ __metadata:
custom-card-helpers: ^1.9.0 custom-card-helpers: ^1.9.0
date-fns: ^2.29.2 date-fns: ^2.29.2
date-fns-tz: ^1.3.7 date-fns-tz: ^1.3.7
embla-carousel: ^7.0.2 embla-carousel: ^7.0.9
embla-carousel-wheel-gestures: ^3.0.0 embla-carousel-wheel-gestures: ^3.0.0
eslint: ^8.23.0 eslint: ^8.23.0
eslint-config-airbnb-base: ^15.0.0 eslint-config-airbnb-base: ^15.0.0