* match face wizard with camera and classification wizards

* remove review detail dialog and link chip to detail stream in history

* remove footer on explore images and move to overlay

* use consistent overlay button styles

* spacing tweak

* ensure selected ring stays on top of gradients

* fix z-index

* match object lifecycle with details
This commit is contained in:
Josh Hawkins
2025-10-24 11:08:59 -06:00
committed by GitHub
parent 49f5d595ea
commit eb51eb3c9d
13 changed files with 341 additions and 209 deletions
@@ -811,7 +811,10 @@ function StateTrainGrid({
image={data.filename}
onRefresh={onRefresh}
>
<TbCategoryPlus className="size-7 cursor-pointer p-1 text-gray-200 hover:rounded-full hover:bg-primary-foreground/40" />
<div className="group relative inline-flex items-center justify-center">
<div className="pointer-events-none absolute inset-0 m-auto size-5 scale-95 rounded-full bg-black opacity-0 blur-sm transition-all duration-200 group-hover:scale-100 group-hover:opacity-100 group-hover:blur-xl" />
<TbCategoryPlus className="relative z-10 size-5 cursor-pointer text-white/85 hover:text-white" />
</div>
</ClassificationSelectionDialog>
</ClassificationCard>
</div>
@@ -958,7 +961,10 @@ function ObjectTrainGrid({
image={data.filename}
onRefresh={onRefresh}
>
<TbCategoryPlus className="size-7 cursor-pointer p-1 text-gray-200 hover:rounded-full hover:bg-primary-foreground/40" />
<div className="group relative inline-flex items-center justify-center">
<div className="pointer-events-none absolute inset-0 m-auto size-5 scale-95 rounded-full bg-black opacity-0 blur-sm transition-all duration-200 group-hover:scale-100 group-hover:opacity-100 group-hover:blur-xl" />
<TbCategoryPlus className="relative z-10 size-5 cursor-pointer text-white/85 hover:text-white" />
</div>
</ClassificationSelectionDialog>
</>
)}
+9 -9
View File
@@ -53,8 +53,6 @@ import { cn } from "@/lib/utils";
import { FilterList, LAST_24_HOURS_KEY } from "@/types/filter";
import { GiSoundWaves } from "react-icons/gi";
import useKeyboardListener from "@/hooks/use-keyboard-listener";
import ReviewDetailDialog from "@/components/overlay/detail/ReviewDetailDialog";
import { useTimelineZoom } from "@/hooks/use-timeline-zoom";
import { useTranslation } from "react-i18next";
@@ -398,6 +396,7 @@ export default function EventView({
onSelectAllReviews={onSelectAllReviews}
setSelectedReviews={setSelectedReviews}
pullLatestData={pullLatestData}
onOpenRecording={onOpenRecording}
/>
)}
{severity == "significant_motion" && (
@@ -441,6 +440,7 @@ type DetectionReviewProps = {
onSelectAllReviews: () => void;
setSelectedReviews: (reviews: ReviewSegment[]) => void;
pullLatestData: () => void;
onOpenRecording: (recordingInfo: RecordingStartingPoint) => void;
};
function DetectionReview({
contentRef,
@@ -460,15 +460,12 @@ function DetectionReview({
onSelectAllReviews,
setSelectedReviews,
pullLatestData,
onOpenRecording,
}: DetectionReviewProps) {
const { t } = useTranslation(["views/events"]);
const reviewTimelineRef = useRef<HTMLDivElement>(null);
// detail
const [reviewDetail, setReviewDetail] = useState<ReviewSegment>();
// preview
const [previewTime, setPreviewTime] = useState<number>();
@@ -688,8 +685,6 @@ function DetectionReview({
return (
<>
<ReviewDetailDialog review={reviewDetail} setReview={setReviewDetail} />
<div
ref={contentRef}
className="no-scrollbar flex flex-1 flex-wrap content-start gap-2 overflow-y-auto md:gap-4"
@@ -750,7 +745,12 @@ function DetectionReview({
detail: boolean,
) => {
if (detail) {
setReviewDetail(review);
onOpenRecording({
camera: review.camera,
startTime: review.start_time - REVIEW_PADDING,
severity: review.severity,
timelineType: "detail",
});
} else {
onSelectReview(review, ctrl);
}
+8 -1
View File
@@ -53,6 +53,7 @@ import {
ASPECT_VERTICAL_LAYOUT,
ASPECT_WIDE_LAYOUT,
RecordingSegment,
RecordingStartingPoint,
} from "@/types/record";
import { useResizeObserver } from "@/hooks/resize-observer";
import { cn } from "@/lib/utils";
@@ -141,9 +142,15 @@ export function RecordingView({
// timeline
const [recording] = useOverlayState<RecordingStartingPoint>(
"recording",
undefined,
false,
);
const [timelineType, setTimelineType] = useOverlayState<TimelineType>(
"timelineType",
"timeline",
recording?.timelineType ?? "timeline",
);
const chunkedTimeRange = useMemo(
+30 -30
View File
@@ -577,7 +577,7 @@ export default function SearchView({
>
<div
className={cn(
"aspect-square w-full overflow-hidden rounded-t-lg border",
"relative aspect-square w-full overflow-hidden rounded-lg",
)}
>
<SearchThumbnail
@@ -634,38 +634,38 @@ export default function SearchView({
</Tooltip>
</div>
)}
<div className="absolute bottom-0 left-0 right-0 z-30 bg-gradient-to-t from-black/70 to-transparent p-2">
<SearchThumbnailFooter
searchResult={value}
columns={columns}
findSimilar={() => {
if (config?.semantic_search.enabled) {
setSimilaritySearch(value);
}
}}
refreshResults={refresh}
showObjectLifecycle={() =>
onSelectSearch(value, false, "object_lifecycle")
}
showSnapshot={() =>
onSelectSearch(value, false, "snapshot")
}
addTrigger={() => {
if (
config?.semantic_search.enabled &&
value.data.type == "object"
) {
navigate(
`/settings?page=triggers&camera=${value.camera}&event_id=${value.id}`,
);
}
}}
/>
</div>
</div>
<div
className={`review-item-ring pointer-events-none absolute inset-0 z-10 size-full rounded-lg outline outline-[3px] -outline-offset-[2.8px] ${selected ? `shadow-selected outline-selected` : "outline-transparent duration-500"}`}
className={`review-item-ring pointer-events-none absolute inset-0 z-30 size-full rounded-lg outline outline-[3px] -outline-offset-[2.8px] ${selected ? `shadow-selected outline-selected` : "outline-transparent duration-500"}`}
/>
<div className="flex w-full grow items-center justify-between rounded-b-lg border border-t-0 bg-card p-3 text-card-foreground">
<SearchThumbnailFooter
searchResult={value}
columns={columns}
findSimilar={() => {
if (config?.semantic_search.enabled) {
setSimilaritySearch(value);
}
}}
refreshResults={refresh}
showObjectLifecycle={() =>
onSelectSearch(value, false, "object_lifecycle")
}
showSnapshot={() =>
onSelectSearch(value, false, "snapshot")
}
addTrigger={() => {
if (
config?.semantic_search.enabled &&
value.data.type == "object"
) {
navigate(
`/settings?page=triggers&camera=${value.camera}&event_id=${value.id}`,
);
}
}}
/>
</div>
</div>
);
})}