forked from Github/frigate
Review improvements (#11879)
* Update segment even when number of active objects is the same * add score to frigate+ chip * Add support for selecting zones * Add api support for filtering on zones * Adjust UI * Update filtering logic * Clean up
This commit is contained in:
@@ -30,6 +30,7 @@ import MobileReviewSettingsDrawer, {
|
||||
} from "../overlay/MobileReviewSettingsDrawer";
|
||||
import useOptimisticState from "@/hooks/use-optimistic-state";
|
||||
import FilterSwitch from "./FilterSwitch";
|
||||
import { FilterList } from "@/types/filter";
|
||||
|
||||
const REVIEW_FILTERS = [
|
||||
"cameras",
|
||||
@@ -53,7 +54,7 @@ type ReviewFilterGroupProps = {
|
||||
reviewSummary?: ReviewSummary;
|
||||
filter?: ReviewFilter;
|
||||
motionOnly: boolean;
|
||||
filterLabels?: string[];
|
||||
filterList?: FilterList;
|
||||
onUpdateFilter: (filter: ReviewFilter) => void;
|
||||
setMotionOnly: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
};
|
||||
@@ -64,15 +65,15 @@ export default function ReviewFilterGroup({
|
||||
reviewSummary,
|
||||
filter,
|
||||
motionOnly,
|
||||
filterLabels,
|
||||
filterList,
|
||||
onUpdateFilter,
|
||||
setMotionOnly,
|
||||
}: ReviewFilterGroupProps) {
|
||||
const { data: config } = useSWR<FrigateConfig>("config");
|
||||
|
||||
const allLabels = useMemo<string[]>(() => {
|
||||
if (filterLabels) {
|
||||
return filterLabels;
|
||||
if (filterList?.labels) {
|
||||
return filterList.labels;
|
||||
}
|
||||
|
||||
if (!config) {
|
||||
@@ -99,14 +100,43 @@ export default function ReviewFilterGroup({
|
||||
});
|
||||
|
||||
return [...labels].sort();
|
||||
}, [config, filterLabels, filter]);
|
||||
}, [config, filterList, filter]);
|
||||
|
||||
const allZones = useMemo<string[]>(() => {
|
||||
if (filterList?.zones) {
|
||||
return filterList.zones;
|
||||
}
|
||||
|
||||
if (!config) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const zones = new Set<string>();
|
||||
const cameras = filter?.cameras || Object.keys(config.cameras);
|
||||
|
||||
cameras.forEach((camera) => {
|
||||
if (camera == "birdseye") {
|
||||
return;
|
||||
}
|
||||
const cameraConfig = config.cameras[camera];
|
||||
cameraConfig.review.alerts.required_zones.forEach((zone) => {
|
||||
zones.add(zone);
|
||||
});
|
||||
cameraConfig.review.detections.required_zones.forEach((zone) => {
|
||||
zones.add(zone);
|
||||
});
|
||||
});
|
||||
|
||||
return [...zones].sort();
|
||||
}, [config, filterList, filter]);
|
||||
|
||||
const filterValues = useMemo(
|
||||
() => ({
|
||||
cameras: Object.keys(config?.cameras || {}),
|
||||
labels: Object.values(allLabels || {}),
|
||||
zones: Object.values(allZones || {}),
|
||||
}),
|
||||
[config, allLabels],
|
||||
[config, allLabels, allZones],
|
||||
);
|
||||
|
||||
const groups = useMemo(() => {
|
||||
@@ -189,12 +219,17 @@ export default function ReviewFilterGroup({
|
||||
selectedLabels={filter?.labels}
|
||||
currentSeverity={currentSeverity}
|
||||
showAll={filter?.showAll == true}
|
||||
allZones={filterValues.zones}
|
||||
selectedZones={filter?.zones}
|
||||
setShowAll={(showAll) => {
|
||||
onUpdateFilter({ ...filter, showAll });
|
||||
}}
|
||||
updateLabelFilter={(newLabels) => {
|
||||
onUpdateFilter({ ...filter, labels: newLabels });
|
||||
}}
|
||||
updateZoneFilter={(newZones) =>
|
||||
onUpdateFilter({ ...filter, zones: newZones })
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{isMobile && mobileSettingsFeatures.length > 0 && (
|
||||
@@ -204,6 +239,7 @@ export default function ReviewFilterGroup({
|
||||
currentSeverity={currentSeverity}
|
||||
reviewSummary={reviewSummary}
|
||||
allLabels={allLabels}
|
||||
allZones={allZones}
|
||||
onUpdateFilter={onUpdateFilter}
|
||||
// not applicable as exports are not used
|
||||
camera=""
|
||||
@@ -495,21 +531,30 @@ type GeneralFilterButtonProps = {
|
||||
selectedLabels: string[] | undefined;
|
||||
currentSeverity?: ReviewSeverity;
|
||||
showAll: boolean;
|
||||
allZones: string[];
|
||||
selectedZones?: string[];
|
||||
setShowAll: (showAll: boolean) => void;
|
||||
updateLabelFilter: (labels: string[] | undefined) => void;
|
||||
updateZoneFilter: (zones: string[] | undefined) => void;
|
||||
};
|
||||
function GeneralFilterButton({
|
||||
allLabels,
|
||||
selectedLabels,
|
||||
currentSeverity,
|
||||
showAll,
|
||||
allZones,
|
||||
selectedZones,
|
||||
setShowAll,
|
||||
updateLabelFilter,
|
||||
updateZoneFilter,
|
||||
}: GeneralFilterButtonProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [currentLabels, setCurrentLabels] = useState<string[] | undefined>(
|
||||
selectedLabels,
|
||||
);
|
||||
const [currentZones, setCurrentZones] = useState<string[] | undefined>(
|
||||
selectedZones,
|
||||
);
|
||||
|
||||
const trigger = (
|
||||
<Button
|
||||
@@ -534,6 +579,11 @@ function GeneralFilterButton({
|
||||
currentLabels={currentLabels}
|
||||
currentSeverity={currentSeverity}
|
||||
showAll={showAll}
|
||||
allZones={allZones}
|
||||
selectedZones={selectedZones}
|
||||
currentZones={currentZones}
|
||||
setCurrentZones={setCurrentZones}
|
||||
updateZoneFilter={updateZoneFilter}
|
||||
setShowAll={setShowAll}
|
||||
updateLabelFilter={updateLabelFilter}
|
||||
setCurrentLabels={setCurrentLabels}
|
||||
@@ -584,9 +634,14 @@ type GeneralFilterContentProps = {
|
||||
currentLabels: string[] | undefined;
|
||||
currentSeverity?: ReviewSeverity;
|
||||
showAll?: boolean;
|
||||
allZones?: string[];
|
||||
selectedZones?: string[];
|
||||
currentZones?: string[];
|
||||
setShowAll?: (showAll: boolean) => void;
|
||||
updateLabelFilter: (labels: string[] | undefined) => void;
|
||||
setCurrentLabels: (labels: string[] | undefined) => void;
|
||||
updateZoneFilter?: (zones: string[] | undefined) => void;
|
||||
setCurrentZones?: (zones: string[] | undefined) => void;
|
||||
onClose: () => void;
|
||||
};
|
||||
export function GeneralFilterContent({
|
||||
@@ -595,9 +650,14 @@ export function GeneralFilterContent({
|
||||
currentLabels,
|
||||
currentSeverity,
|
||||
showAll,
|
||||
allZones,
|
||||
selectedZones,
|
||||
currentZones,
|
||||
setShowAll,
|
||||
updateLabelFilter,
|
||||
setCurrentLabels,
|
||||
updateZoneFilter,
|
||||
setCurrentZones,
|
||||
onClose,
|
||||
}: GeneralFilterContentProps) {
|
||||
return (
|
||||
@@ -622,7 +682,7 @@ export function GeneralFilterContent({
|
||||
<DropdownMenuSeparator />
|
||||
</div>
|
||||
)}
|
||||
<div className="my-2.5 flex items-center justify-between">
|
||||
<div className="mb-5 mt-2.5 flex items-center justify-between">
|
||||
<Label
|
||||
className="mx-2 cursor-pointer text-primary"
|
||||
htmlFor="allLabels"
|
||||
@@ -640,7 +700,6 @@ export function GeneralFilterContent({
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<DropdownMenuSeparator />
|
||||
<div className="my-2.5 flex flex-col gap-2.5">
|
||||
{allLabels.map((item) => (
|
||||
<FilterSwitch
|
||||
@@ -666,6 +725,53 @@ export function GeneralFilterContent({
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{allZones && setCurrentZones && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<div className="mb-5 mt-2.5 flex items-center justify-between">
|
||||
<Label
|
||||
className="mx-2 cursor-pointer text-primary"
|
||||
htmlFor="allZones"
|
||||
>
|
||||
All Zones
|
||||
</Label>
|
||||
<Switch
|
||||
className="ml-1"
|
||||
id="allZones"
|
||||
checked={currentZones == undefined}
|
||||
onCheckedChange={(isChecked) => {
|
||||
if (isChecked) {
|
||||
setCurrentZones(undefined);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="my-2.5 flex flex-col gap-2.5">
|
||||
{allZones.map((item) => (
|
||||
<FilterSwitch
|
||||
label={item.replaceAll("_", " ")}
|
||||
isChecked={currentZones?.includes(item) ?? false}
|
||||
onCheckedChange={(isChecked) => {
|
||||
if (isChecked) {
|
||||
const updatedZones = currentZones ? [...currentZones] : [];
|
||||
|
||||
updatedZones.push(item);
|
||||
setCurrentZones(updatedZones);
|
||||
} else {
|
||||
const updatedZones = currentZones ? [...currentZones] : [];
|
||||
|
||||
// can not deselect the last item
|
||||
if (updatedZones.length > 1) {
|
||||
updatedZones.splice(updatedZones.indexOf(item), 1);
|
||||
setCurrentZones(updatedZones);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
<div className="flex items-center justify-evenly p-2">
|
||||
<Button
|
||||
@@ -675,6 +781,10 @@ export function GeneralFilterContent({
|
||||
updateLabelFilter(currentLabels);
|
||||
}
|
||||
|
||||
if (updateZoneFilter && selectedZones != currentZones) {
|
||||
updateZoneFilter(currentZones);
|
||||
}
|
||||
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -36,6 +36,7 @@ type MobileReviewSettingsDrawerProps = {
|
||||
mode: ExportMode;
|
||||
reviewSummary?: ReviewSummary;
|
||||
allLabels: string[];
|
||||
allZones: string[];
|
||||
onUpdateFilter: (filter: ReviewFilter) => void;
|
||||
setRange: (range: TimeRange | undefined) => void;
|
||||
setMode: (mode: ExportMode) => void;
|
||||
@@ -51,6 +52,7 @@ export default function MobileReviewSettingsDrawer({
|
||||
mode,
|
||||
reviewSummary,
|
||||
allLabels,
|
||||
allZones,
|
||||
onUpdateFilter,
|
||||
setRange,
|
||||
setMode,
|
||||
@@ -104,6 +106,9 @@ export default function MobileReviewSettingsDrawer({
|
||||
const [currentLabels, setCurrentLabels] = useState<string[] | undefined>(
|
||||
filter?.labels,
|
||||
);
|
||||
const [currentZones, setCurrentZones] = useState<string[] | undefined>(
|
||||
filter?.zones,
|
||||
);
|
||||
|
||||
if (!isMobile) {
|
||||
return;
|
||||
@@ -222,7 +227,7 @@ export default function MobileReviewSettingsDrawer({
|
||||
);
|
||||
} else if (drawerMode == "filter") {
|
||||
content = (
|
||||
<div className="scrollbar-container flex h-auto w-full flex-col overflow-y-auto">
|
||||
<div className="scrollbar-container flex h-auto w-full flex-col overflow-y-auto overflow-x-hidden">
|
||||
<div className="relative mb-2 h-8 w-full">
|
||||
<div
|
||||
className="absolute left-0 text-selected"
|
||||
@@ -240,6 +245,13 @@ export default function MobileReviewSettingsDrawer({
|
||||
currentLabels={currentLabels}
|
||||
currentSeverity={currentSeverity}
|
||||
showAll={filter?.showAll == true}
|
||||
allZones={allZones}
|
||||
selectedZones={filter?.zones}
|
||||
currentZones={currentZones}
|
||||
setCurrentZones={setCurrentZones}
|
||||
updateZoneFilter={(newZones) =>
|
||||
onUpdateFilter({ ...filter, zones: newZones })
|
||||
}
|
||||
setShowAll={(showAll) => {
|
||||
onUpdateFilter({ ...filter, showAll });
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user