feat: Add support for Frigate reviews / detections [initial PR] (#2315)
- Add support for Frigate reviews / detections. - Add support for GenAI metadata. - Significant internal refactor to more flexible "UnifiedQuery" to allow mixing cameras with simple metadata and review metadata (e.g. a timeline view of a Frigate camera with reviews, and a Reolink camera with simple metadata). - Add support for folder media as camera media. There are a few more PRs to commit prior to this going live, but commiting this for now due to the scale of the change. BREAKING CHANGE: `media_type` and `events_type` are retired under `live`, `viewer` and `timeline` configuration sections, instead media type is associated (once) with the camera under `media`.
This commit is contained in:
@@ -86,6 +86,57 @@ class ResultSlice {
|
||||
this.selectIndex(resultIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an item from this slice, preserving selection where possible.
|
||||
* If the selected item is removed, selects the previous item.
|
||||
* @param item The item to remove.
|
||||
* @returns true if the item was found and removed, false otherwise.
|
||||
*/
|
||||
public removeItem(item: ViewItem): boolean {
|
||||
const index = this._results.indexOf(item);
|
||||
if (index === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy-on-write: Create a new array reference only when modifying
|
||||
this._results = [...this._results];
|
||||
this._results.splice(index, 1);
|
||||
|
||||
// Adjust selection: if removed was selected, clamp to valid range; if after, decrement
|
||||
if (this._selectedIndex !== null && this._selectedIndex >= index) {
|
||||
if (this._selectedIndex === index) {
|
||||
// Removed the selected item: clamp to new valid range or null if empty
|
||||
this._selectedIndex =
|
||||
this._results.length > 0 ? Math.min(index, this._results.length - 1) : null;
|
||||
} else {
|
||||
// Removed an item before the selected one: decrement
|
||||
this._selectedIndex = this._selectedIndex - 1;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace an item in this slice with a new item (e.g., a clone with updated state).
|
||||
* Selection is preserved if the replaced item was selected.
|
||||
* @param oldItem The item to replace.
|
||||
* @param newItem The new item to insert in its place.
|
||||
* @returns true if the item was found and replaced, false otherwise.
|
||||
*/
|
||||
public replaceItem(oldItem: ViewItem, newItem: ViewItem): boolean {
|
||||
const index = this._results.indexOf(oldItem);
|
||||
if (index === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// When an item is replaced, we create a new array so that users can simply
|
||||
// compare the results objects to determine equality (e.g. trigger Lit
|
||||
// rendering for thumbnails that are marked as reviewed).
|
||||
this._results = [...this._results];
|
||||
this._results[index] = newItem;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
interface ResultSliceSelectionCriteria {
|
||||
@@ -142,6 +193,44 @@ export class QueryResults {
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a specific item from the results.
|
||||
* Note: This mutates the current instance. Use clone() first if needed.
|
||||
* @param item The item to remove from results.
|
||||
* @returns This QueryResults instance for chaining.
|
||||
*/
|
||||
public removeItem(item: ViewItem): QueryResults {
|
||||
if (!this._main.removeItem(item)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
// Also remove from the relevant camera slice
|
||||
const cameraID = ViewItemClassifier.isMedia(item) ? item.getCameraID() : null;
|
||||
if (cameraID) {
|
||||
this._cameras.get(cameraID)?.removeItem(item);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace an item with a new item (e.g., a clone with updated state).
|
||||
* Note: This mutates the current instance. Use clone() first if needed.
|
||||
* @param oldItem The item to replace.
|
||||
* @param newItem The new item to insert in its place.
|
||||
* @returns This QueryResults instance for chaining.
|
||||
*/
|
||||
public replaceItem(oldItem: ViewItem, newItem: ViewItem): QueryResults {
|
||||
if (!this._main.replaceItem(oldItem, newItem)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
const cameraID = ViewItemClassifier.isMedia(oldItem) ? oldItem.getCameraID() : null;
|
||||
if (cameraID) {
|
||||
this._cameras.get(cameraID)?.replaceItem(oldItem, newItem);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
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()));
|
||||
|
||||
Reference in New Issue
Block a user