Recalculate grid for a slot change.
This commit is contained in:
@@ -1,5 +1,8 @@
|
|||||||
// TODO: Video scanning not select other camera?
|
// TODO: Video scanning not select other camera?
|
||||||
// TODO: Drag from live results in different cameras being shown in timeline in media viewer?
|
// TODO: Drag from live results in different cameras being shown in timeline in media viewer?
|
||||||
|
// TODO: It's possible to get a single entry in the grid, rendered without the
|
||||||
|
// grid (correctly), but still with isGrid so the menu button still shows the
|
||||||
|
// take it out of grid mode option.
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
@@ -43,6 +46,7 @@ export class FrigateCardMediaGrid extends LitElement {
|
|||||||
if (!this._controller && this._refSlot.value) {
|
if (!this._controller && this._refSlot.value) {
|
||||||
this._controller = new MediaGridController(this._refSlot.value, {
|
this._controller = new MediaGridController(this._refSlot.value, {
|
||||||
selected: this.selected,
|
selected: this.selected,
|
||||||
|
displayConfig: this.displayConfig,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ import {
|
|||||||
dispatchFrigateCardEvent,
|
dispatchFrigateCardEvent,
|
||||||
formatDateAndTime,
|
formatDateAndTime,
|
||||||
isHoverableDevice,
|
isHoverableDevice,
|
||||||
|
isTruthy,
|
||||||
setOrRemoveAttribute,
|
setOrRemoveAttribute,
|
||||||
} from '../utils/basic';
|
} from '../utils/basic';
|
||||||
import {
|
import {
|
||||||
@@ -485,10 +486,13 @@ export class FrigateCardTimelineCore extends LitElement {
|
|||||||
: results
|
: results
|
||||||
.clone()
|
.clone()
|
||||||
.resetSelectedResult()
|
.resetSelectedResult()
|
||||||
.selectBestResult((media) => findBestMediaIndex(media, targetTime), {
|
.selectBestResult(
|
||||||
|
(media) => findBestMediaIndex(media, targetTime, this.view?.camera),
|
||||||
|
{
|
||||||
allCameras: true,
|
allCameras: true,
|
||||||
main: true,
|
main: true,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const desiredView: FrigateCardView = this.mini
|
const desiredView: FrigateCardView = this.mini
|
||||||
? targetTime >= new Date()
|
? targetTime >= new Date()
|
||||||
@@ -899,8 +903,7 @@ export class FrigateCardTimelineCore extends LitElement {
|
|||||||
maxItems: this.timelineConfig.clustering_threshold,
|
maxItems: this.timelineConfig.clustering_threshold,
|
||||||
|
|
||||||
clusterCriteria: (first: TimelineItem, second: TimelineItem): boolean => {
|
clusterCriteria: (first: TimelineItem, second: TimelineItem): boolean => {
|
||||||
const media = this.view?.queryResults?.getSelectedResult();
|
const selectedIDs = this._getAllSelectedMediaIDsFromView();
|
||||||
const selectedId = media?.getID();
|
|
||||||
const firstMedia = (<FrigateCardTimelineItem>first).media;
|
const firstMedia = (<FrigateCardTimelineItem>first).media;
|
||||||
const secondMedia = (<FrigateCardTimelineItem>second).media;
|
const secondMedia = (<FrigateCardTimelineItem>second).media;
|
||||||
|
|
||||||
@@ -910,8 +913,8 @@ export class FrigateCardTimelineCore extends LitElement {
|
|||||||
return (
|
return (
|
||||||
first.type !== 'background' &&
|
first.type !== 'background' &&
|
||||||
first.type === second.type &&
|
first.type === second.type &&
|
||||||
first.id !== selectedId &&
|
!selectedIDs.includes(first.id) &&
|
||||||
second.id !== selectedId &&
|
!selectedIDs.includes(second.id) &&
|
||||||
!!firstMedia &&
|
!!firstMedia &&
|
||||||
!!secondMedia &&
|
!!secondMedia &&
|
||||||
ViewMediaClassifier.isEvent(firstMedia) &&
|
ViewMediaClassifier.isEvent(firstMedia) &&
|
||||||
@@ -966,6 +969,18 @@ export class FrigateCardTimelineCore extends LitElement {
|
|||||||
return !!this.hass && !!this.cameraManager;
|
return !!this.hass && !!this.cameraManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected _getAllSelectedMediaIDsFromView(): IdType[] {
|
||||||
|
return (
|
||||||
|
this.view?.queryResults?.getMultipleSelectedResults({
|
||||||
|
main: true,
|
||||||
|
...(this.view.isGrid() && { allCameras: true }),
|
||||||
|
}) ?? []
|
||||||
|
)
|
||||||
|
.filter((media) => ViewMediaClassifier.isEvent(media))
|
||||||
|
.map((media) => media.getID())
|
||||||
|
.filter(isTruthy);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the timeline from the view object.
|
* Update the timeline from the view object.
|
||||||
*/
|
*/
|
||||||
@@ -1030,8 +1045,11 @@ export class FrigateCardTimelineCore extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const currentSelection = this._timeline.getSelection();
|
const currentSelection = this._timeline.getSelection();
|
||||||
const mediaID = media?.getID();
|
const mediaIDsToSelect = this._getAllSelectedMediaIDsFromView();
|
||||||
const needToSelect = mediaID && mediaIsEvent && !currentSelection.includes(mediaID);
|
|
||||||
|
const needToSelect = mediaIDsToSelect.some(
|
||||||
|
(mediaID) => !currentSelection.includes(mediaID),
|
||||||
|
);
|
||||||
|
|
||||||
if (needToSelect) {
|
if (needToSelect) {
|
||||||
if (this._isClustering()) {
|
if (this._isClustering()) {
|
||||||
@@ -1039,12 +1057,14 @@ export class FrigateCardTimelineCore extends LitElement {
|
|||||||
// update the dataset to ensure the newly selected item cannot be included
|
// update the dataset to ensure the newly selected item cannot be included
|
||||||
// in a cluster.
|
// in a cluster.
|
||||||
|
|
||||||
|
for (const mediaID of mediaIDsToSelect) {
|
||||||
// Need to this rewrite prior to setting the selection (just below), or
|
// Need to this rewrite prior to setting the selection (just below), or
|
||||||
// the selection will be lost on rewrite.
|
// the selection will be lost on rewrite.
|
||||||
this._timelineSource?.rewriteEvent(mediaID);
|
this._timelineSource?.rewriteEvent(mediaID);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this._timeline?.setSelection([mediaID], {
|
this._timeline?.setSelection(mediaIDsToSelect, {
|
||||||
focus: false,
|
focus: false,
|
||||||
animation: {
|
animation: {
|
||||||
animation: false,
|
animation: false,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { ifDefined } from 'lit/directives/if-defined.js';
|
|||||||
import { createRef, Ref, ref } from 'lit/directives/ref.js';
|
import { createRef, Ref, ref } from 'lit/directives/ref.js';
|
||||||
import basicBlockStyle from '../scss/basic-block.scss';
|
import basicBlockStyle from '../scss/basic-block.scss';
|
||||||
import { CameraManager } from '../camera-manager/manager.js';
|
import { CameraManager } from '../camera-manager/manager.js';
|
||||||
import { dispatchMessageEvent, renderProgressIndicator } from '../components/message.js';
|
import { dispatchMessageEvent, renderMessage, renderProgressIndicator } from '../components/message.js';
|
||||||
import { localize } from '../localize/localize.js';
|
import { localize } from '../localize/localize.js';
|
||||||
import '../patches/ha-hls-player';
|
import '../patches/ha-hls-player';
|
||||||
import viewerCarouselStyle from '../scss/viewer-carousel.scss';
|
import viewerCarouselStyle from '../scss/viewer-carousel.scss';
|
||||||
@@ -136,7 +136,14 @@ export class FrigateCardViewer extends LitElement {
|
|||||||
// timeline).
|
// timeline).
|
||||||
const mediaType = this.view.getDefaultMediaType();
|
const mediaType = this.view.getDefaultMediaType();
|
||||||
if (!mediaType) {
|
if (!mediaType) {
|
||||||
return;
|
// Directly render an error message (instead of dispatching it upwards)
|
||||||
|
// to preserve the mini-timeline if the user scans into an area with no
|
||||||
|
// media.
|
||||||
|
return renderMessage({
|
||||||
|
type: 'info',
|
||||||
|
message: localize('common.no_media'),
|
||||||
|
icon: 'mdi:multimedia',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mediaType === 'recordings') {
|
if (mediaType === 'recordings') {
|
||||||
|
|||||||
+8
-17
@@ -108,12 +108,14 @@ export class FrigateCardError extends Error {
|
|||||||
const viewDisplayModeSchema = z.enum(['single', 'grid']);
|
const viewDisplayModeSchema = z.enum(['single', 'grid']);
|
||||||
export type ViewDisplayMode = z.infer<typeof viewDisplayModeSchema>;
|
export type ViewDisplayMode = z.infer<typeof viewDisplayModeSchema>;
|
||||||
|
|
||||||
const viewDisplaySchema = z.object({
|
const viewDisplaySchema = z
|
||||||
|
.object({
|
||||||
mode: viewDisplayModeSchema,
|
mode: viewDisplayModeSchema,
|
||||||
grid_selected_width_factor: z.number().min(0).optional(),
|
grid_selected_width_factor: z.number().min(0).optional(),
|
||||||
grid_max_columns: z.number().min(0).optional(),
|
grid_max_columns: z.number().min(0).optional(),
|
||||||
grid_columns: z.number().min(0).optional(),
|
grid_columns: z.number().min(0).optional(),
|
||||||
}).optional();
|
})
|
||||||
|
.optional();
|
||||||
export type ViewDisplayConfig = z.infer<typeof viewDisplaySchema>;
|
export type ViewDisplayConfig = z.infer<typeof viewDisplaySchema>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1134,7 +1136,9 @@ const menuConfigSchema = z
|
|||||||
mute: hiddenButtonSchema.default(menuConfigDefault.buttons.mute),
|
mute: hiddenButtonSchema.default(menuConfigDefault.buttons.mute),
|
||||||
play: hiddenButtonSchema.default(menuConfigDefault.buttons.play),
|
play: hiddenButtonSchema.default(menuConfigDefault.buttons.play),
|
||||||
screenshot: hiddenButtonSchema.default(menuConfigDefault.buttons.screenshot),
|
screenshot: hiddenButtonSchema.default(menuConfigDefault.buttons.screenshot),
|
||||||
display_mode: visibleButtonSchema.default(menuConfigDefault.buttons.display_mode),
|
display_mode: visibleButtonSchema.default(
|
||||||
|
menuConfigDefault.buttons.display_mode,
|
||||||
|
),
|
||||||
})
|
})
|
||||||
.default(menuConfigDefault.buttons),
|
.default(menuConfigDefault.buttons),
|
||||||
button_size: z.number().min(BUTTON_SIZE_MIN).default(menuConfigDefault.button_size),
|
button_size: z.number().min(BUTTON_SIZE_MIN).default(menuConfigDefault.button_size),
|
||||||
@@ -1164,10 +1168,6 @@ const viewerConfigDefault = {
|
|||||||
},
|
},
|
||||||
thumbnails: thumbnailControlsDefaults,
|
thumbnails: thumbnailControlsDefaults,
|
||||||
timeline: miniTimelineConfigDefault,
|
timeline: miniTimelineConfigDefault,
|
||||||
title: {
|
|
||||||
mode: 'popup-bottom-right' as const,
|
|
||||||
duration_seconds: 2,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const viewerNextPreviousControlConfigSchema = nextPreviousControlConfigSchema.extend({
|
const viewerNextPreviousControlConfigSchema = nextPreviousControlConfigSchema.extend({
|
||||||
@@ -1215,16 +1215,7 @@ const viewerConfigSchema = z
|
|||||||
timeline: miniTimelineConfigSchema.default(
|
timeline: miniTimelineConfigSchema.default(
|
||||||
viewerConfigDefault.controls.timeline,
|
viewerConfigDefault.controls.timeline,
|
||||||
),
|
),
|
||||||
title: titleControlConfigSchema
|
title: titleControlConfigSchema.optional(),
|
||||||
.extend({
|
|
||||||
mode: titleControlConfigSchema.shape.mode.default(
|
|
||||||
viewerConfigDefault.controls.title.mode,
|
|
||||||
),
|
|
||||||
duration_seconds: titleControlConfigSchema.shape.duration_seconds.default(
|
|
||||||
viewerConfigDefault.controls.title.duration_seconds,
|
|
||||||
),
|
|
||||||
})
|
|
||||||
.default(viewerConfigDefault.controls.title),
|
|
||||||
})
|
})
|
||||||
.default(viewerConfigDefault.controls),
|
.default(viewerConfigDefault.controls),
|
||||||
layout: mediaLayoutConfigSchema.optional(),
|
layout: mediaLayoutConfigSchema.optional(),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import isEqual from 'lodash-es/isEqual';
|
||||||
import throttle from 'lodash-es/throttle';
|
import throttle from 'lodash-es/throttle';
|
||||||
import Masonry from 'masonry-layout';
|
import Masonry from 'masonry-layout';
|
||||||
import { MediaLoadedInfo, ViewDisplayConfig } from '../types';
|
import { MediaLoadedInfo, ViewDisplayConfig } from '../types';
|
||||||
@@ -27,6 +28,7 @@ export interface MediaGridSelected {
|
|||||||
export interface MediaGridConstructorOptions {
|
export interface MediaGridConstructorOptions {
|
||||||
selected?: GridID;
|
selected?: GridID;
|
||||||
idAttribute?: string;
|
idAttribute?: string;
|
||||||
|
displayConfig?: ViewDisplayConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MediaGridController {
|
export class MediaGridController {
|
||||||
@@ -62,14 +64,26 @@ export class MediaGridController {
|
|||||||
this._idAttribute = options?.idAttribute ?? 'grid-id';
|
this._idAttribute = options?.idAttribute ?? 'grid-id';
|
||||||
this._hostWidth = this._host.getBoundingClientRect().width;
|
this._hostWidth = this._host.getBoundingClientRect().width;
|
||||||
this._hostResizeObserver.observe(host);
|
this._hostResizeObserver.observe(host);
|
||||||
|
this._displayConfig = options?.displayConfig ?? null;
|
||||||
|
|
||||||
this._calculateGridContentsFromHost();
|
|
||||||
this._mutationObserver.observe(host, { childList: true });
|
this._mutationObserver.observe(host, { childList: true });
|
||||||
|
// Need to separately listen for slotchanges since mutation observer will
|
||||||
|
// not be called for shadom DOM slotted changes.
|
||||||
|
if (host instanceof HTMLSlotElement) {
|
||||||
|
host.addEventListener('slotchange', this._calculateGridContentsFromHost);
|
||||||
|
}
|
||||||
|
this._calculateGridContentsFromHost();
|
||||||
}
|
}
|
||||||
|
|
||||||
public destroy(): void {
|
public destroy(): void {
|
||||||
this._hostResizeObserver.disconnect();
|
this._hostResizeObserver.disconnect();
|
||||||
this._cellResizeObserver.disconnect();
|
this._cellResizeObserver.disconnect();
|
||||||
|
|
||||||
|
this._mutationObserver.disconnect();
|
||||||
|
if (this._host instanceof HTMLSlotElement) {
|
||||||
|
this._host.removeEventListener('slotchange', this._calculateGridContentsFromHost);
|
||||||
|
}
|
||||||
|
|
||||||
this._mediaLoadedInfoMap.clear();
|
this._mediaLoadedInfoMap.clear();
|
||||||
this._masonry?.destroy?.();
|
this._masonry?.destroy?.();
|
||||||
this._masonry = null;
|
this._masonry = null;
|
||||||
@@ -81,9 +95,11 @@ export class MediaGridController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public setDisplayConfig(displayConfig: ViewDisplayConfig | null): void {
|
public setDisplayConfig(displayConfig: ViewDisplayConfig | null): void {
|
||||||
|
if (!isEqual(displayConfig, this._displayConfig)) {
|
||||||
this._displayConfig = displayConfig;
|
this._displayConfig = displayConfig;
|
||||||
this._calculateGridContentsFromHost();
|
this._calculateGridContentsFromHost();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public getGridContents(): MediaGridContents {
|
public getGridContents(): MediaGridContents {
|
||||||
return this._gridContents;
|
return this._gridContents;
|
||||||
@@ -127,11 +143,11 @@ export class MediaGridController {
|
|||||||
this._updateSelectedStylesOnElements();
|
this._updateSelectedStylesOnElements();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _calculateGridContentsFromHost(): void {
|
protected _calculateGridContentsFromHost = (): void => {
|
||||||
let childrenElements: Element[];
|
let childrenElements: Element[];
|
||||||
|
|
||||||
if (this._host instanceof HTMLSlotElement) {
|
if (this._host instanceof HTMLSlotElement) {
|
||||||
childrenElements = this._host.assignedElements({ flatten: true });
|
childrenElements = this._host.assignedElements();
|
||||||
} else {
|
} else {
|
||||||
childrenElements = [...this._host.children];
|
childrenElements = [...this._host.children];
|
||||||
}
|
}
|
||||||
@@ -145,7 +161,7 @@ export class MediaGridController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._setGridContents(gridContents);
|
this._setGridContents(gridContents);
|
||||||
}
|
};
|
||||||
|
|
||||||
protected _setGridContents(elements: MediaGridContents): void {
|
protected _setGridContents(elements: MediaGridContents): void {
|
||||||
this._gridContents = elements;
|
this._gridContents = elements;
|
||||||
|
|||||||
@@ -169,10 +169,11 @@ export const executeMediaQueryForView = async (
|
|||||||
|
|
||||||
const queryResults = new MediaQueriesResults({ results: mediaArray });
|
const queryResults = new MediaQueriesResults({ results: mediaArray });
|
||||||
let viewerContext: ViewContext | undefined = {};
|
let viewerContext: ViewContext | undefined = {};
|
||||||
|
const cameraID = options?.targetCameraID ?? view.camera;
|
||||||
|
|
||||||
if (options?.select === 'time' && options?.targetTime) {
|
if (options?.select === 'time' && options?.targetTime) {
|
||||||
queryResults.selectBestResult((media) =>
|
queryResults.selectBestResult((media) =>
|
||||||
findBestMediaIndex(media, options.targetTime as Date),
|
findBestMediaIndex(media, options.targetTime as Date, cameraID),
|
||||||
);
|
);
|
||||||
viewerContext = {
|
viewerContext = {
|
||||||
mediaViewer: {
|
mediaViewer: {
|
||||||
@@ -186,7 +187,7 @@ export const executeMediaQueryForView = async (
|
|||||||
query: query,
|
query: query,
|
||||||
queryResults: queryResults,
|
queryResults: queryResults,
|
||||||
view: options?.targetView,
|
view: options?.targetView,
|
||||||
camera: options?.targetCameraID,
|
camera: cameraID,
|
||||||
})
|
})
|
||||||
.mergeInContext(viewerContext);
|
.mergeInContext(viewerContext);
|
||||||
};
|
};
|
||||||
@@ -201,11 +202,13 @@ export const executeMediaQueryForView = async (
|
|||||||
export const findBestMediaIndex = (
|
export const findBestMediaIndex = (
|
||||||
mediaArray: ViewMedia[],
|
mediaArray: ViewMedia[],
|
||||||
targetTime: Date,
|
targetTime: Date,
|
||||||
|
favorCameraID?: string,
|
||||||
): number | null => {
|
): number | null => {
|
||||||
let bestMatch:
|
let bestMatch:
|
||||||
| {
|
| {
|
||||||
index: number;
|
index: number;
|
||||||
duration: number;
|
duration: number;
|
||||||
|
cameraID: string;
|
||||||
}
|
}
|
||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
@@ -215,8 +218,21 @@ export const findBestMediaIndex = (
|
|||||||
|
|
||||||
if (media.includesTime(targetTime) && start && end) {
|
if (media.includesTime(targetTime) && start && end) {
|
||||||
const duration = end.getTime() - start.getTime();
|
const duration = end.getTime() - start.getTime();
|
||||||
if (!bestMatch || duration > bestMatch.duration) {
|
|
||||||
bestMatch = { index: i, duration: duration };
|
if (
|
||||||
|
// No best match so far ...
|
||||||
|
!bestMatch ||
|
||||||
|
// ... or there is a best-match, but it's from a non-favored camera (unlike this one) ...
|
||||||
|
(favorCameraID &&
|
||||||
|
bestMatch.cameraID !== favorCameraID &&
|
||||||
|
media.getCameraID() === favorCameraID) ||
|
||||||
|
// ... or this match is longer and either there's no favored camera or this is it.
|
||||||
|
(duration > bestMatch.duration &&
|
||||||
|
(!favorCameraID ||
|
||||||
|
bestMatch.cameraID !== favorCameraID ||
|
||||||
|
media.getCameraID() === favorCameraID))
|
||||||
|
) {
|
||||||
|
bestMatch = { index: i, duration: duration, cameraID: media.getCameraID() };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -183,6 +183,25 @@ export class MediaQueriesResults {
|
|||||||
public getSelectedResult(cameraID?: string): ViewMedia | null {
|
public getSelectedResult(cameraID?: string): ViewMedia | null {
|
||||||
return this.getSlice(cameraID)?.getSelectedResult() ?? null;
|
return this.getSlice(cameraID)?.getSelectedResult() ?? null;
|
||||||
}
|
}
|
||||||
|
public getMultipleSelectedResults(
|
||||||
|
criteria?: ResultSliceSelectionCriteria,
|
||||||
|
): ViewMedia[] {
|
||||||
|
const results: ViewMedia[] = [];
|
||||||
|
if (!criteria || criteria.main) {
|
||||||
|
const mainResult = this.getSelectedResult();
|
||||||
|
if (mainResult) {
|
||||||
|
results.push(mainResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const cameraIDs = this._getCameraIDsFromCriteria(criteria);
|
||||||
|
for (const cameraID of cameraIDs ?? []) {
|
||||||
|
const result = this.getSelectedResult(cameraID);
|
||||||
|
if (result) {
|
||||||
|
results.push(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
public hasSelectedResult(cameraID?: string): boolean {
|
public hasSelectedResult(cameraID?: string): boolean {
|
||||||
return this.getSlice(cameraID)?.hasSelectedResult() ?? false;
|
return this.getSlice(cameraID)?.hasSelectedResult() ?? false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ const createSlotParent = (): HTMLElement => {
|
|||||||
const createSlotHost = (options?: {
|
const createSlotHost = (options?: {
|
||||||
children?: HTMLElement[];
|
children?: HTMLElement[];
|
||||||
parent?: HTMLElement;
|
parent?: HTMLElement;
|
||||||
}): HTMLElement => {
|
}): HTMLSlotElement => {
|
||||||
const parent = options?.parent ?? createSlotParent();
|
const parent = options?.parent ?? createSlotParent();
|
||||||
const slot = document.createElement('slot');
|
const slot = document.createElement('slot');
|
||||||
parent.shadowRoot?.append(slot);
|
parent.shadowRoot?.append(slot);
|
||||||
@@ -292,6 +292,32 @@ describe('MediaGridController', () => {
|
|||||||
expect(controller.getSelected()).toBeNull();
|
expect(controller.getSelected()).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should replace children of a slot when they change', () => {
|
||||||
|
const children = createChildren();
|
||||||
|
const slotParent = createSlotParent();
|
||||||
|
const host = createSlotHost({ children: children, parent: slotParent });
|
||||||
|
|
||||||
|
const controller = createController(host, { selected: '1' });
|
||||||
|
|
||||||
|
expect(controller.getSelected()).toBe('1');
|
||||||
|
expect(controller.getGridSize()).toBe(3);
|
||||||
|
|
||||||
|
children.forEach((child) => slotParent.removeChild(child));
|
||||||
|
const newChildren = createChildren(['one', 'two', 'three']);
|
||||||
|
newChildren.forEach((child) => slotParent.append(child));
|
||||||
|
|
||||||
|
host.dispatchEvent(new Event('slotchange'));
|
||||||
|
|
||||||
|
expect(controller.getGridContents()).toEqual(
|
||||||
|
new Map([
|
||||||
|
['one', newChildren[0]],
|
||||||
|
['two', newChildren[1]],
|
||||||
|
['three', newChildren[2]],
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
expect(controller.getSelected()).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
it('should construct masonry correctly', () => {
|
it('should construct masonry correctly', () => {
|
||||||
const children = createChildren();
|
const children = createChildren();
|
||||||
const host = createHost({ children: children });
|
const host = createHost({ children: children });
|
||||||
|
|||||||
@@ -54,11 +54,13 @@ const generateViewMedia = (
|
|||||||
index: number,
|
index: number,
|
||||||
base: Date,
|
base: Date,
|
||||||
durationSeconds: number,
|
durationSeconds: number,
|
||||||
|
cameraID?: string,
|
||||||
): ViewMedia => {
|
): ViewMedia => {
|
||||||
return new TestViewMedia({
|
return new TestViewMedia({
|
||||||
id: `id-${index}`,
|
id: `id-${index}`,
|
||||||
startTime: base,
|
startTime: base,
|
||||||
endTime: add(base, { seconds: durationSeconds }),
|
endTime: add(base, { seconds: durationSeconds }),
|
||||||
|
...(cameraID && { cameraID: cameraID }),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -449,4 +451,21 @@ describe('findBestMediaIndex', () => {
|
|||||||
|
|
||||||
expect(findBestMediaIndex(mediaArray, add(now, { seconds: 30 }))).toBe(1);
|
expect(findBestMediaIndex(mediaArray, add(now, { seconds: 30 }))).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should find best media index respecting favored cameraID', async () => {
|
||||||
|
const now = new Date();
|
||||||
|
const mediaArray = [
|
||||||
|
generateViewMedia(0, now, 60, 'less-good-camera'),
|
||||||
|
generateViewMedia(1, now, 120, 'less-good-camera'),
|
||||||
|
generateViewMedia(2, now, 10, 'favored-camera'),
|
||||||
|
generateViewMedia(3, now, 35, 'favored-camera'),
|
||||||
|
generateViewMedia(4, now, 40, 'favored-camera'),
|
||||||
|
generateViewMedia(5, now, 30, 'favored-camera'),
|
||||||
|
generateViewMedia(6, now, 300, 'less-good-camera'),
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(
|
||||||
|
findBestMediaIndex(mediaArray, add(now, { seconds: 30 }), 'favored-camera'),
|
||||||
|
).toBe(4);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ describe('dispatchViewContextChangeEvent', () => {
|
|||||||
|
|
||||||
expect(results.selectBestResult((_media: ViewMedia[]) => null)).toEqual(results);
|
expect(results.selectBestResult((_media: ViewMedia[]) => null)).toEqual(results);
|
||||||
expect(results.getSelectedResult()).toBeNull();
|
expect(results.getSelectedResult()).toBeNull();
|
||||||
|
expect(results.getMultipleSelectedResults()).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should function with basic results', () => {
|
it('should function with basic results', () => {
|
||||||
@@ -230,4 +231,16 @@ describe('dispatchViewContextChangeEvent', () => {
|
|||||||
expect(results.getSelectedResult('office')?.getID()).toBe('id-office-42');
|
expect(results.getSelectedResult('office')?.getID()).toBe('id-office-42');
|
||||||
expect(results.getSelectedResult('kitchen')?.getID()).toBe('id-kitchen-42');
|
expect(results.getSelectedResult('kitchen')?.getID()).toBe('id-kitchen-42');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should get multiple selected results', () => {
|
||||||
|
const results = new MediaQueriesResults({
|
||||||
|
results: generateViewMediaArray(),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
results
|
||||||
|
.getMultipleSelectedResults({ main: true, allCameras: true })
|
||||||
|
.map((media) => media.getID()),
|
||||||
|
).toEqual(['id-office-99', 'id-kitchen-99', 'id-office-99']);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user