feat: Implement basic general folder support (#2051)

- Related: #1748
This commit is contained in:
Dermot Duffy
2025-05-21 19:59:21 -07:00
committed by GitHub
parent 2eb0d9e35e
commit c6a4c8aea2
350 changed files with 12837 additions and 4509 deletions
+116
View File
@@ -0,0 +1,116 @@
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 = EventMediaQuery | RecordingMediaQuery;
export type Query = MediaQueries | FolderViewQuery;
class ViewQuery<T> {
protected _query: T | null = null;
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);
}
}
class MediaQueryBase<T extends MediaQuery> extends ViewQuery<T[]> {
public getQueryCameraIDs(): Set<string> | null {
if (!this._query) {
return null;
}
const cameraIDs: Set<string> = new Set();
this._query.forEach((query) =>
[...query.cameraIDs].forEach((cameraID) => cameraIDs.add(cameraID)),
);
return cameraIDs;
}
public setQueryCameraIDs(cameraIDs: string | Set<string>): this {
if (!this._query) {
return this;
}
const rewrittenQueries: T[] = [];
this._query.forEach((query) =>
rewrittenQueries.push({ ...query, cameraIDs: setify(cameraIDs) }),
);
this._query = uniqWith(rewrittenQueries, isEqual);
return this;
}
public hasQueriesForCameraIDs(cameraIDs: Set<string>) {
for (const cameraID of cameraIDs) {
if (!this._query?.some((query) => query.cameraIDs.has(cameraID))) {
return false;
}
}
return true;
}
public isSupersetOf(that: MediaQueries): boolean {
// 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.getQuery() ?? []) {
let haveMatch = false;
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.
if (
isEqual(
{
...thisQuery,
end: null,
start: null,
},
{ ...thatQuery, end: null, start: null },
) &&
((!thisQuery.start && !thatQuery.start) ||
(thisQuery.start &&
thatQuery.start &&
thisQuery.start <= thatQuery.start)) &&
((!thisQuery.end && !thatQuery.end) ||
(thisQuery.end && thatQuery.end && thisQuery.end >= thatQuery.end))
) {
haveMatch = true;
break;
}
}
if (!haveMatch) {
return false;
}
}
return true;
}
}
export class EventMediaQuery extends MediaQueryBase<EventQuery> {
public convertToClipsQueries(): this {
for (const query of this._query ?? []) {
delete query.hasSnapshot;
query.hasClip = true;
}
return this;
}
}
export class RecordingMediaQuery extends MediaQueryBase<RecordingQuery> {}
export class FolderViewQuery extends ViewQuery<FolderQuery> {}