@@ -0,0 +1,31 @@
|
||||
import {
|
||||
EventViewMedia,
|
||||
RecordingViewMedia,
|
||||
ViewFolder,
|
||||
ViewItem,
|
||||
ViewMedia,
|
||||
} from './item';
|
||||
|
||||
export class ViewItemClassifier {
|
||||
public static isMedia(item?: ViewItem | null): item is ViewMedia {
|
||||
return item instanceof ViewMedia;
|
||||
}
|
||||
public static isFolder(item?: ViewItem | null): item is ViewFolder {
|
||||
return item instanceof ViewFolder;
|
||||
}
|
||||
public static isEvent(item?: ViewItem | null): item is EventViewMedia {
|
||||
return this.isMedia(item) && (this.isClip(item) || this.isSnapshot(item));
|
||||
}
|
||||
public static isRecording(item?: ViewItem | null): item is RecordingViewMedia {
|
||||
return this.isMedia(item) && item.getMediaType() === 'recording';
|
||||
}
|
||||
public static isClip(item?: ViewItem | null): boolean {
|
||||
return this.isMedia(item) && item.getMediaType() === 'clip';
|
||||
}
|
||||
public static isSnapshot(item?: ViewItem | null): boolean {
|
||||
return this.isMedia(item) && item.getMediaType() === 'snapshot';
|
||||
}
|
||||
public static isVideo(item?: ViewItem | null): boolean {
|
||||
return this.isMedia(item) && (this.isClip(item) || this.isRecording(item));
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,37 @@
|
||||
export type ViewMediaType = 'clip' | 'snapshot' | 'recording';
|
||||
import { FolderConfig } from '../config/schema/folders';
|
||||
|
||||
export enum ViewMediaType {
|
||||
Clip = 'clip',
|
||||
Snapshot = 'snapshot',
|
||||
Recording = 'recording',
|
||||
}
|
||||
|
||||
export enum VideoContentType {
|
||||
MP4 = 'mp4',
|
||||
HLS = 'hls',
|
||||
}
|
||||
|
||||
export interface ViewMediaSourceOptions {
|
||||
cameraID?: string;
|
||||
folder?: FolderConfig;
|
||||
}
|
||||
|
||||
export class ViewMedia {
|
||||
protected _mediaType: ViewMediaType;
|
||||
protected _cameraID: string;
|
||||
protected _cameraID: string | null;
|
||||
protected _folder: FolderConfig | null;
|
||||
|
||||
constructor(mediaType: ViewMediaType, cameraID: string) {
|
||||
constructor(mediaType: ViewMediaType, options?: ViewMediaSourceOptions) {
|
||||
this._mediaType = mediaType;
|
||||
this._cameraID = cameraID;
|
||||
this._cameraID = options?.cameraID ?? null;
|
||||
this._folder = options?.folder ?? null;
|
||||
}
|
||||
public getCameraID(): string {
|
||||
public getCameraID(): string | null {
|
||||
return this._cameraID;
|
||||
}
|
||||
public getFolder(): FolderConfig | null {
|
||||
return this._folder;
|
||||
}
|
||||
public getMediaType(): ViewMediaType {
|
||||
return this._mediaType;
|
||||
}
|
||||
@@ -46,6 +62,9 @@ export class ViewMedia {
|
||||
public getThumbnail(): string | null {
|
||||
return null;
|
||||
}
|
||||
public getIcon(): string | null {
|
||||
return null;
|
||||
}
|
||||
public isFavorite(): boolean | null {
|
||||
return null;
|
||||
}
|
||||
@@ -76,3 +95,49 @@ export interface EventViewMedia extends ViewMedia {
|
||||
export interface RecordingViewMedia extends ViewMedia {
|
||||
getEventCount(): number | null;
|
||||
}
|
||||
|
||||
interface ViewFolderParameters {
|
||||
icon?: string | null;
|
||||
id?: string | null;
|
||||
title?: string | null;
|
||||
thumbnail?: string | null;
|
||||
}
|
||||
|
||||
export class ViewFolder {
|
||||
private _folder: FolderConfig;
|
||||
|
||||
private _icon: string | null;
|
||||
private _id: string | null;
|
||||
private _title: string | null;
|
||||
private _thumbnail: string | null;
|
||||
|
||||
constructor(folder: FolderConfig, params?: ViewFolderParameters) {
|
||||
this._folder = folder;
|
||||
|
||||
this._icon = params?.icon ?? null;
|
||||
this._id = params?.id ?? null;
|
||||
this._title = params?.title ?? null;
|
||||
this._thumbnail = params?.thumbnail ?? null;
|
||||
}
|
||||
|
||||
public getFolder(): FolderConfig {
|
||||
return this._folder;
|
||||
}
|
||||
public getID(): string | null {
|
||||
return this._id;
|
||||
}
|
||||
public getTitle(): string | null {
|
||||
return this._title;
|
||||
}
|
||||
public getThumbnail(): string | null {
|
||||
return this._thumbnail;
|
||||
}
|
||||
public getIcon(): string | null {
|
||||
return this._icon;
|
||||
}
|
||||
public isFavorite(): boolean | null {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export type ViewItem = ViewMedia | ViewFolder;
|
||||
@@ -1,19 +0,0 @@
|
||||
import { ViewMedia, RecordingViewMedia, EventViewMedia } from './media';
|
||||
|
||||
export class ViewMediaClassifier {
|
||||
public static isEvent(media: ViewMedia): media is EventViewMedia {
|
||||
return this.isClip(media) || this.isSnapshot(media);
|
||||
}
|
||||
public static isRecording(media: ViewMedia): media is RecordingViewMedia {
|
||||
return media.getMediaType() === 'recording';
|
||||
}
|
||||
public static isClip(media: ViewMedia): boolean {
|
||||
return media.getMediaType() === 'clip';
|
||||
}
|
||||
public static isSnapshot(media: ViewMedia): boolean {
|
||||
return media.getMediaType() === 'snapshot';
|
||||
}
|
||||
public static isVideo(media: ViewMedia): boolean {
|
||||
return this.isClip(media) || this.isRecording(media);
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
import { EventMediaQueries, MediaQueries, RecordingMediaQueries } from './media-queries';
|
||||
|
||||
export type MediaQueriesType = 'event' | 'recording';
|
||||
type MediaType = 'clips' | 'snapshots' | 'recordings';
|
||||
|
||||
export class MediaQueriesClassifier {
|
||||
public static areEventQueries(
|
||||
queries?: MediaQueries | null,
|
||||
): queries is EventMediaQueries {
|
||||
return queries instanceof EventMediaQueries;
|
||||
}
|
||||
|
||||
public static areRecordingQueries(
|
||||
queries?: MediaQueries | null,
|
||||
): queries is RecordingMediaQueries {
|
||||
return queries instanceof RecordingMediaQueries;
|
||||
}
|
||||
|
||||
public static areClipsQueries(queries?: MediaQueries | null): boolean {
|
||||
return (
|
||||
this.areEventQueries(queries) &&
|
||||
!!queries?.getQueries()?.every((query) => query.hasClip)
|
||||
);
|
||||
}
|
||||
|
||||
public static areSnapshotQueries(queries?: MediaQueries | null): boolean {
|
||||
return (
|
||||
this.areEventQueries(queries) &&
|
||||
!!queries?.getQueries()?.every((query) => query.hasSnapshot)
|
||||
);
|
||||
}
|
||||
|
||||
public static getQueriesType(queries?: MediaQueries | null): MediaQueriesType | null {
|
||||
return this.areEventQueries(queries)
|
||||
? 'event'
|
||||
: this.areRecordingQueries(queries)
|
||||
? 'recording'
|
||||
: null;
|
||||
}
|
||||
|
||||
public static getMediaType(queries?: MediaQueries | null): MediaType | null {
|
||||
return this.areClipsQueries(queries)
|
||||
? 'clips'
|
||||
: this.areSnapshotQueries(queries)
|
||||
? 'snapshots'
|
||||
: this.areRecordingQueries(queries)
|
||||
? 'recordings'
|
||||
: null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { EventMediaQuery, FolderViewQuery, Query, RecordingMediaQuery } from './query';
|
||||
|
||||
export type QueryType = 'event' | 'recording' | 'folder';
|
||||
type MediaType = 'clips' | 'snapshots' | 'recordings';
|
||||
|
||||
export class QueryClassifier {
|
||||
public static isEventQuery(query?: Query | null): query is EventMediaQuery {
|
||||
return query instanceof EventMediaQuery;
|
||||
}
|
||||
|
||||
public static isRecordingQuery(query?: Query | null): query is RecordingMediaQuery {
|
||||
return query instanceof RecordingMediaQuery;
|
||||
}
|
||||
|
||||
public static isMediaQuery(
|
||||
query?: Query | null,
|
||||
): query is EventMediaQuery | RecordingMediaQuery {
|
||||
return this.isEventQuery(query) || this.isRecordingQuery(query);
|
||||
}
|
||||
|
||||
public static isFolderQuery(query?: Query | null): query is FolderViewQuery {
|
||||
return query instanceof FolderViewQuery;
|
||||
}
|
||||
|
||||
public static isClipsQuery(query?: Query | null): boolean {
|
||||
return (
|
||||
this.isEventQuery(query) && !!query?.getQuery()?.every((query) => query.hasClip)
|
||||
);
|
||||
}
|
||||
|
||||
public static isSnapshotQuery(query?: Query | null): boolean {
|
||||
return (
|
||||
this.isEventQuery(query) &&
|
||||
!!query?.getQuery()?.every((query) => query.hasSnapshot)
|
||||
);
|
||||
}
|
||||
|
||||
public static getQueryType(query?: Query | null): QueryType | null {
|
||||
return this.isEventQuery(query)
|
||||
? 'event'
|
||||
: this.isRecordingQuery(query)
|
||||
? 'recording'
|
||||
: this.isFolderQuery(query)
|
||||
? 'folder'
|
||||
: null;
|
||||
}
|
||||
|
||||
public static getMediaType(query?: Query | null): MediaType | null {
|
||||
return this.isClipsQuery(query)
|
||||
? 'clips'
|
||||
: this.isSnapshotQuery(query)
|
||||
? 'snapshots'
|
||||
: this.isRecordingQuery(query)
|
||||
? 'recordings'
|
||||
: null;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,18 @@
|
||||
import { isSuperset } from '../utils/basic.js';
|
||||
import { ViewMedia } from './media.js';
|
||||
import { ViewItemClassifier } from './item-classifier.js';
|
||||
import { ViewItem } from './item.js';
|
||||
|
||||
type CameraResultSlices = Map<string, ResultSlice>;
|
||||
type SelectApproach = 'first' | 'last';
|
||||
|
||||
interface ResultSliceOptions {
|
||||
results?: ViewMedia[];
|
||||
results?: ViewItem[];
|
||||
selectedIndex?: number | null;
|
||||
selectApproach?: SelectApproach;
|
||||
}
|
||||
|
||||
class ResultSlice {
|
||||
protected _results: ViewMedia[];
|
||||
protected _results: ViewItem[];
|
||||
protected _selectedIndex: number | null;
|
||||
|
||||
constructor(options?: ResultSliceOptions) {
|
||||
@@ -40,7 +41,7 @@ class ResultSlice {
|
||||
});
|
||||
}
|
||||
|
||||
public getResults(): ViewMedia[] {
|
||||
public getResults(): ViewItem[] {
|
||||
return this._results;
|
||||
}
|
||||
public getSelectedIndex(): number | null {
|
||||
@@ -52,10 +53,10 @@ class ResultSlice {
|
||||
public hasResults(): boolean {
|
||||
return this.getResultsCount() !== 0;
|
||||
}
|
||||
public getResult(index?: number): ViewMedia | null {
|
||||
public getResult(index?: number): ViewItem | null {
|
||||
return index === undefined ? null : this._results[index];
|
||||
}
|
||||
public getSelectedResult(): ViewMedia | null {
|
||||
public getSelectedResult(): ViewItem | null {
|
||||
const index = this.getSelectedIndex();
|
||||
return index !== null ? this.getResult(index) : null;
|
||||
}
|
||||
@@ -71,7 +72,7 @@ class ResultSlice {
|
||||
this._selectedIndex = index;
|
||||
}
|
||||
}
|
||||
public selectResultIfFound(func: (media: ViewMedia) => boolean): void {
|
||||
public selectResultIfFound(func: (item: ViewItem) => boolean): void {
|
||||
for (const [index, result] of this._results.entries()) {
|
||||
if (func(result)) {
|
||||
this.selectIndex(index);
|
||||
@@ -79,7 +80,7 @@ class ResultSlice {
|
||||
}
|
||||
}
|
||||
}
|
||||
public selectBestResult(func: (media: ViewMedia[]) => number | null): void {
|
||||
public selectBestResult(func: (item: ViewItem[]) => number | null): void {
|
||||
const resultIndex = func(this._results);
|
||||
if (resultIndex !== null) {
|
||||
this.selectIndex(resultIndex);
|
||||
@@ -93,7 +94,7 @@ interface ResultSliceSelectionCriteria {
|
||||
allCameras?: boolean;
|
||||
}
|
||||
|
||||
export class MediaQueriesResults {
|
||||
export class QueryResults {
|
||||
protected _resultsTimestamp: Date | null = null;
|
||||
protected _main: ResultSlice;
|
||||
protected _cameras: CameraResultSlices = new Map();
|
||||
@@ -105,31 +106,33 @@ export class MediaQueriesResults {
|
||||
}
|
||||
|
||||
protected _buildByCameraSlices(selectApproach?: SelectApproach): void {
|
||||
const cameraMap: Map<string, ViewMedia[]> = new Map();
|
||||
const cameraMap: Map<string, ViewItem[]> = new Map();
|
||||
for (const result of this._main.getResults()) {
|
||||
const cameraID = result.getCameraID();
|
||||
const media: ViewMedia[] = cameraMap.get(cameraID) ?? [];
|
||||
media.push(result);
|
||||
cameraMap.set(cameraID, media);
|
||||
const cameraID = ViewItemClassifier.isMedia(result) ? result.getCameraID() : null;
|
||||
if (cameraID) {
|
||||
const items: ViewItem[] = cameraMap.get(cameraID) ?? [];
|
||||
items.push(result);
|
||||
cameraMap.set(cameraID, items);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [cameraID, media] of cameraMap.entries()) {
|
||||
for (const [cameraID, items] of cameraMap.entries()) {
|
||||
this._cameras.set(
|
||||
cameraID,
|
||||
new ResultSlice({
|
||||
results: media,
|
||||
results: items,
|
||||
selectApproach: selectApproach,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public clone(): MediaQueriesResults {
|
||||
public clone(): QueryResults {
|
||||
// Shallow clone -- will reuse the same results object (as there are no
|
||||
// methods that support modification of the results themselves, and since
|
||||
// changing the index on a consistent set of results is a very common
|
||||
// operation).
|
||||
const copy = new MediaQueriesResults();
|
||||
const copy = new QueryResults();
|
||||
copy._resultsTimestamp = this._resultsTimestamp;
|
||||
copy._main = this._main.clone();
|
||||
|
||||
@@ -139,22 +142,22 @@ export class MediaQueriesResults {
|
||||
return copy;
|
||||
}
|
||||
|
||||
public isSupersetOf(that: MediaQueriesResults): boolean {
|
||||
const thisMediaIDs = new Set(this._main.getResults()?.map((media) => media.getID()));
|
||||
const thatMediaIDs = new Set(that._main.getResults()?.map((media) => media.getID()));
|
||||
public isSupersetOf(that: QueryResults): boolean {
|
||||
const thisItemIDs = new Set(this._main.getResults()?.map((item) => item.getID()));
|
||||
const thatItemIDs = new Set(that._main.getResults()?.map((item) => item.getID()));
|
||||
|
||||
if (
|
||||
!thisMediaIDs.size ||
|
||||
!thatMediaIDs.size ||
|
||||
// If either media sets contain a null identifier (i.e. a media item with
|
||||
// no ID) we must assume this is not a subset as multiple media items may
|
||||
// reduce to the same null identifier above.
|
||||
thisMediaIDs.has(null) ||
|
||||
thatMediaIDs.has(null)
|
||||
!thisItemIDs.size ||
|
||||
!thatItemIDs.size ||
|
||||
// If either item sets contain a null identifier (i.e. an item item with
|
||||
// no ID) we must assume this is not a subset as multiple items may reduce
|
||||
// to the same null identifier above.
|
||||
thisItemIDs.has(null) ||
|
||||
thatItemIDs.has(null)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return isSuperset(thisMediaIDs, thatMediaIDs);
|
||||
return isSuperset(thisItemIDs, thatItemIDs);
|
||||
}
|
||||
|
||||
public getCameraIDs(): Set<string> {
|
||||
@@ -165,7 +168,7 @@ export class MediaQueriesResults {
|
||||
return cameraID ? this._cameras.get(cameraID) ?? null : this._main;
|
||||
}
|
||||
|
||||
public getResults(cameraID?: string): ViewMedia[] | null {
|
||||
public getResults(cameraID?: string): ViewItem[] | null {
|
||||
return this.getSlice(cameraID)?.getResults() ?? null;
|
||||
}
|
||||
public getResultsCount(cameraID?: string): number {
|
||||
@@ -174,19 +177,19 @@ export class MediaQueriesResults {
|
||||
public hasResults(cameraID?: string): boolean {
|
||||
return this.getSlice(cameraID)?.getResultsCount() !== 0;
|
||||
}
|
||||
public getResult(index?: number, cameraID?: string): ViewMedia | null {
|
||||
public getResult(index?: number, cameraID?: string): ViewItem | null {
|
||||
return this.getSlice(cameraID)?.getResult(index) ?? null;
|
||||
}
|
||||
public getSelectedIndex(cameraID?: string): number | null {
|
||||
return this.getSlice(cameraID)?.getSelectedIndex() ?? null;
|
||||
}
|
||||
public getSelectedResult(cameraID?: string): ViewMedia | null {
|
||||
public getSelectedResult(cameraID?: string): ViewItem | null {
|
||||
return this.getSlice(cameraID)?.getSelectedResult() ?? null;
|
||||
}
|
||||
public getMultipleSelectedResults(
|
||||
criteria?: ResultSliceSelectionCriteria,
|
||||
): ViewMedia[] {
|
||||
const results: ViewMedia[] = [];
|
||||
): ViewItem[] {
|
||||
const results: ViewItem[] = [];
|
||||
if (!criteria || criteria.main) {
|
||||
const mainResult = this.getSelectedResult();
|
||||
if (mainResult) {
|
||||
@@ -205,7 +208,7 @@ export class MediaQueriesResults {
|
||||
public hasSelectedResult(cameraID?: string): boolean {
|
||||
return this.getSlice(cameraID)?.hasSelectedResult() ?? false;
|
||||
}
|
||||
public resetSelectedResult(cameraID?: string): MediaQueriesResults {
|
||||
public resetSelectedResult(cameraID?: string): QueryResults {
|
||||
this.getSlice(cameraID)?.resetSelectedResult();
|
||||
return this;
|
||||
}
|
||||
@@ -213,7 +216,7 @@ export class MediaQueriesResults {
|
||||
return this._resultsTimestamp;
|
||||
}
|
||||
|
||||
public selectIndex(index: number, cameraID?: string): MediaQueriesResults {
|
||||
public selectIndex(index: number, cameraID?: string): QueryResults {
|
||||
this.getSlice(cameraID)?.selectIndex(index);
|
||||
if (!cameraID) {
|
||||
// If the main selection is changed, it must also change the matching
|
||||
@@ -223,20 +226,23 @@ export class MediaQueriesResults {
|
||||
return this;
|
||||
}
|
||||
|
||||
public demoteMainSelectionToCameraSelection(): MediaQueriesResults {
|
||||
public demoteMainSelectionToCameraSelection(): QueryResults {
|
||||
const selected = this.getSelectedResult();
|
||||
if (selected) {
|
||||
const cameraID = selected.getCameraID();
|
||||
const cameraID = ViewItemClassifier.isMedia(selected)
|
||||
? selected?.getCameraID()
|
||||
: null;
|
||||
|
||||
if (selected && cameraID) {
|
||||
this.resetSelectedResult(cameraID);
|
||||
this.selectResultIfFound((media) => media === selected, { cameraID: cameraID });
|
||||
this.selectResultIfFound((item) => item === selected, { cameraID: cameraID });
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public promoteCameraSelectionToMainSelection(cameraID: string): MediaQueriesResults {
|
||||
public promoteCameraSelectionToMainSelection(cameraID: string): QueryResults {
|
||||
const selected = this.getSelectedResult(cameraID);
|
||||
this.resetSelectedResult();
|
||||
this.selectResultIfFound((media) => media === selected);
|
||||
this.selectResultIfFound((item) => item === selected);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -251,9 +257,9 @@ export class MediaQueriesResults {
|
||||
}
|
||||
|
||||
public selectResultIfFound(
|
||||
func: (media: ViewMedia) => boolean,
|
||||
func: (item: ViewItem) => boolean,
|
||||
criteria?: ResultSliceSelectionCriteria,
|
||||
): MediaQueriesResults {
|
||||
): QueryResults {
|
||||
if (!criteria || criteria?.main) {
|
||||
this._main.selectResultIfFound(func);
|
||||
this.demoteMainSelectionToCameraSelection();
|
||||
@@ -265,9 +271,9 @@ export class MediaQueriesResults {
|
||||
return this;
|
||||
}
|
||||
public selectBestResult(
|
||||
func: (media: ViewMedia[]) => number | null,
|
||||
func: (item: ViewItem[]) => number | null,
|
||||
criteria?: ResultSliceSelectionCriteria,
|
||||
): MediaQueriesResults {
|
||||
): QueryResults {
|
||||
if (!criteria || criteria.main) {
|
||||
this._main.selectBestResult(func);
|
||||
this.demoteMainSelectionToCameraSelection();
|
||||
@@ -1,59 +1,61 @@
|
||||
import cloneDeep from 'lodash-es/cloneDeep.js';
|
||||
import isEqual from 'lodash-es/isEqual.js';
|
||||
import uniqWith from 'lodash-es/uniqWith.js';
|
||||
import { cloneDeep, isEqual, uniqWith } from 'lodash-es';
|
||||
import { EventQuery, MediaQuery, RecordingQuery } from '../camera-manager/types.js';
|
||||
import { FolderQuery } from '../card-controller/folders/types.js';
|
||||
import { setify } from '../utils/basic.js';
|
||||
|
||||
export type MediaQueries = EventMediaQueries | RecordingMediaQueries;
|
||||
export type MediaQueries = EventMediaQuery | RecordingMediaQuery;
|
||||
export type Query = MediaQueries | FolderViewQuery;
|
||||
|
||||
class MediaQueriesBase<T extends MediaQuery> {
|
||||
protected _queries: T[] | null = null;
|
||||
class ViewQuery<T> {
|
||||
protected _query: T | null = null;
|
||||
|
||||
public constructor(queries?: T[]) {
|
||||
if (queries) {
|
||||
this._queries = queries;
|
||||
public constructor(query?: T) {
|
||||
if (query) {
|
||||
this._query = query;
|
||||
}
|
||||
}
|
||||
|
||||
public getQuery(): T | null {
|
||||
return this._query;
|
||||
}
|
||||
|
||||
public setQuery(query: T): this {
|
||||
this._query = query;
|
||||
return this;
|
||||
}
|
||||
|
||||
public clone(): this {
|
||||
return cloneDeep(this);
|
||||
}
|
||||
}
|
||||
|
||||
public getQueries(): T[] | null {
|
||||
return this._queries;
|
||||
}
|
||||
|
||||
public setQueries(queries: T[]): this {
|
||||
this._queries = queries;
|
||||
return this;
|
||||
}
|
||||
|
||||
class MediaQueryBase<T extends MediaQuery> extends ViewQuery<T[]> {
|
||||
public getQueryCameraIDs(): Set<string> | null {
|
||||
if (!this._queries) {
|
||||
if (!this._query) {
|
||||
return null;
|
||||
}
|
||||
const cameraIDs: Set<string> = new Set();
|
||||
this._queries.forEach((query) =>
|
||||
this._query.forEach((query) =>
|
||||
[...query.cameraIDs].forEach((cameraID) => cameraIDs.add(cameraID)),
|
||||
);
|
||||
return cameraIDs;
|
||||
}
|
||||
|
||||
public setQueryCameraIDs(cameraIDs: string | Set<string>): this {
|
||||
if (!this._queries) {
|
||||
if (!this._query) {
|
||||
return this;
|
||||
}
|
||||
const rewrittenQueries: T[] = [];
|
||||
this._queries.forEach((query) =>
|
||||
this._query.forEach((query) =>
|
||||
rewrittenQueries.push({ ...query, cameraIDs: setify(cameraIDs) }),
|
||||
);
|
||||
this._queries = uniqWith(rewrittenQueries, isEqual);
|
||||
this._query = uniqWith(rewrittenQueries, isEqual);
|
||||
return this;
|
||||
}
|
||||
|
||||
public hasQueriesForCameraIDs(cameraIDs: Set<string>) {
|
||||
for (const cameraID of cameraIDs) {
|
||||
if (!this._queries?.some((query) => query.cameraIDs.has(cameraID))) {
|
||||
if (!this._query?.some((query) => query.cameraIDs.has(cameraID))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -64,9 +66,9 @@ class MediaQueriesBase<T extends MediaQuery> {
|
||||
// Queries are typically a single item, so quadratic complexity here is
|
||||
// likely still a lot better than going to the network for a new set of
|
||||
// query results.
|
||||
for (const thatQuery of that.getQueries() ?? []) {
|
||||
for (const thatQuery of that.getQuery() ?? []) {
|
||||
let haveMatch = false;
|
||||
for (const thisQuery of this._queries ?? []) {
|
||||
for (const thisQuery of this._query ?? []) {
|
||||
// Compare the query except the times, and then separately compare the
|
||||
// times taking into account whether source time is larger than target
|
||||
// time.
|
||||
@@ -99,9 +101,9 @@ class MediaQueriesBase<T extends MediaQuery> {
|
||||
}
|
||||
}
|
||||
|
||||
export class EventMediaQueries extends MediaQueriesBase<EventQuery> {
|
||||
export class EventMediaQuery extends MediaQueryBase<EventQuery> {
|
||||
public convertToClipsQueries(): this {
|
||||
for (const query of this._queries ?? []) {
|
||||
for (const query of this._query ?? []) {
|
||||
delete query.hasSnapshot;
|
||||
query.hasClip = true;
|
||||
}
|
||||
@@ -109,4 +111,6 @@ export class EventMediaQueries extends MediaQueriesBase<EventQuery> {
|
||||
}
|
||||
}
|
||||
|
||||
export class RecordingMediaQueries extends MediaQueriesBase<RecordingQuery> {}
|
||||
export class RecordingMediaQuery extends MediaQueryBase<RecordingQuery> {}
|
||||
|
||||
export class FolderViewQuery extends ViewQuery<FolderQuery> {}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface ViewItemCapabilities {
|
||||
canFavorite: boolean;
|
||||
canDownload: boolean;
|
||||
}
|
||||
@@ -16,8 +16,9 @@ export const getCameraIDsForViewName = (
|
||||
};
|
||||
|
||||
switch (viewName) {
|
||||
case 'image':
|
||||
case 'diagnostics':
|
||||
case 'image':
|
||||
case 'folder':
|
||||
return cameraManager.getStore().getCameraIDs();
|
||||
|
||||
case 'live':
|
||||
|
||||
+10
-10
@@ -1,9 +1,9 @@
|
||||
import merge from 'lodash-es/merge';
|
||||
import { merge } from 'lodash-es';
|
||||
import { ViewContext } from 'view';
|
||||
import { AdvancedCameraCardView } from '../config/schema/common/const';
|
||||
import { ViewDisplayMode } from '../config/schema/common/display';
|
||||
import { MediaQueries } from './media-queries';
|
||||
import { MediaQueriesResults } from './media-queries-results';
|
||||
import { Query } from './query';
|
||||
import { QueryResults } from './query-results';
|
||||
|
||||
declare module 'view' {
|
||||
interface ViewContext {
|
||||
@@ -16,8 +16,8 @@ declare module 'view' {
|
||||
interface ViewEvolveParameters {
|
||||
view?: AdvancedCameraCardView;
|
||||
camera?: string;
|
||||
query?: MediaQueries | null;
|
||||
queryResults?: MediaQueriesResults | null;
|
||||
query?: Query | null;
|
||||
queryResults?: QueryResults | null;
|
||||
context?: ViewContext | null;
|
||||
displayMode?: ViewDisplayMode | null;
|
||||
}
|
||||
@@ -37,8 +37,8 @@ export const mergeViewContext = (
|
||||
export class View {
|
||||
public view: AdvancedCameraCardView;
|
||||
public camera: string;
|
||||
public query: MediaQueries | null;
|
||||
public queryResults: MediaQueriesResults | null;
|
||||
public query: Query | null;
|
||||
public queryResults: QueryResults | null;
|
||||
public context: ViewContext | null;
|
||||
public displayMode: ViewDisplayMode | null;
|
||||
|
||||
@@ -123,10 +123,10 @@ export class View {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a view is a gallery.
|
||||
* Determine if a view is a media gallery.
|
||||
*/
|
||||
public isGalleryView(): boolean {
|
||||
return ['clips', 'snapshots', 'recordings'].includes(this.view);
|
||||
public isMediaGalleryView(): boolean {
|
||||
return ['clips', 'folder', 'snapshots', 'recordings'].includes(this.view);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user