mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-12-06 13:34:13 +03:00
UI tweaks (#20811)
* camera wizard input mobile font zooming * ensure the selected page is visible when navigating via url on mobile * Filter detail stream to only show items from within the review item * remove incorrect classes causing extra scroll in detail stream * change button label * fix mobile menu button highlight issue --------- Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
This commit is contained in:
parent
81faa8899d
commit
4638c22c16
@ -385,7 +385,7 @@ export default function Step1NameCamera({
|
|||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
className="h-8"
|
className="text-md h-8"
|
||||||
placeholder={t(
|
placeholder={t(
|
||||||
"cameraWizard.step1.cameraNamePlaceholder",
|
"cameraWizard.step1.cameraNamePlaceholder",
|
||||||
)}
|
)}
|
||||||
@ -475,7 +475,7 @@ export default function Step1NameCamera({
|
|||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
className="h-8"
|
className="text-md h-8"
|
||||||
placeholder="192.168.1.100"
|
placeholder="192.168.1.100"
|
||||||
{...field}
|
{...field}
|
||||||
/>
|
/>
|
||||||
@ -495,7 +495,7 @@ export default function Step1NameCamera({
|
|||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
className="h-8"
|
className="text-md h-8"
|
||||||
placeholder={t(
|
placeholder={t(
|
||||||
"cameraWizard.step1.usernamePlaceholder",
|
"cameraWizard.step1.usernamePlaceholder",
|
||||||
)}
|
)}
|
||||||
@ -518,7 +518,7 @@ export default function Step1NameCamera({
|
|||||||
<FormControl>
|
<FormControl>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Input
|
<Input
|
||||||
className="h-8 pr-10"
|
className="text-md h-8 pr-10"
|
||||||
type={showPassword ? "text" : "password"}
|
type={showPassword ? "text" : "password"}
|
||||||
placeholder={t(
|
placeholder={t(
|
||||||
"cameraWizard.step1.passwordPlaceholder",
|
"cameraWizard.step1.passwordPlaceholder",
|
||||||
@ -558,7 +558,7 @@ export default function Step1NameCamera({
|
|||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
className="h-8"
|
className="text-md h-8"
|
||||||
placeholder="rtsp://username:password@host:port/path"
|
placeholder="rtsp://username:password@host:port/path"
|
||||||
{...field}
|
{...field}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -458,6 +458,7 @@ function ReviewGroup({
|
|||||||
<EventList
|
<EventList
|
||||||
key={event.id}
|
key={event.id}
|
||||||
event={event}
|
event={event}
|
||||||
|
review={review}
|
||||||
effectiveTime={effectiveTime}
|
effectiveTime={effectiveTime}
|
||||||
annotationOffset={annotationOffset}
|
annotationOffset={annotationOffset}
|
||||||
onSeek={onSeek}
|
onSeek={onSeek}
|
||||||
@ -492,6 +493,7 @@ function ReviewGroup({
|
|||||||
|
|
||||||
type EventListProps = {
|
type EventListProps = {
|
||||||
event: Event;
|
event: Event;
|
||||||
|
review: ReviewSegment;
|
||||||
effectiveTime?: number;
|
effectiveTime?: number;
|
||||||
annotationOffset: number;
|
annotationOffset: number;
|
||||||
onSeek: (ts: number, play?: boolean) => void;
|
onSeek: (ts: number, play?: boolean) => void;
|
||||||
@ -499,6 +501,7 @@ type EventListProps = {
|
|||||||
};
|
};
|
||||||
function EventList({
|
function EventList({
|
||||||
event,
|
event,
|
||||||
|
review,
|
||||||
effectiveTime,
|
effectiveTime,
|
||||||
annotationOffset,
|
annotationOffset,
|
||||||
onSeek,
|
onSeek,
|
||||||
@ -617,6 +620,7 @@ function EventList({
|
|||||||
|
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<ObjectTimeline
|
<ObjectTimeline
|
||||||
|
review={review}
|
||||||
eventId={event.id}
|
eventId={event.id}
|
||||||
onSeek={handleTimelineClick}
|
onSeek={handleTimelineClick}
|
||||||
effectiveTime={effectiveTime}
|
effectiveTime={effectiveTime}
|
||||||
@ -765,6 +769,7 @@ function LifecycleItem({
|
|||||||
|
|
||||||
// Fetch and render timeline entries for a single event id on demand.
|
// Fetch and render timeline entries for a single event id on demand.
|
||||||
function ObjectTimeline({
|
function ObjectTimeline({
|
||||||
|
review,
|
||||||
eventId,
|
eventId,
|
||||||
onSeek,
|
onSeek,
|
||||||
effectiveTime,
|
effectiveTime,
|
||||||
@ -772,6 +777,7 @@ function ObjectTimeline({
|
|||||||
startTime,
|
startTime,
|
||||||
endTime,
|
endTime,
|
||||||
}: {
|
}: {
|
||||||
|
review: ReviewSegment;
|
||||||
eventId: string;
|
eventId: string;
|
||||||
onSeek: (ts: number, play?: boolean) => void;
|
onSeek: (ts: number, play?: boolean) => void;
|
||||||
effectiveTime?: number;
|
effectiveTime?: number;
|
||||||
@ -780,13 +786,27 @@ function ObjectTimeline({
|
|||||||
endTime?: number;
|
endTime?: number;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation("views/events");
|
const { t } = useTranslation("views/events");
|
||||||
const { data: timeline, isValidating } = useSWR<TrackingDetailsSequence[]>([
|
const { data: fullTimeline, isValidating } = useSWR<
|
||||||
|
TrackingDetailsSequence[]
|
||||||
|
>([
|
||||||
"timeline",
|
"timeline",
|
||||||
{
|
{
|
||||||
source_id: eventId,
|
source_id: eventId,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const timeline = useMemo(() => {
|
||||||
|
if (!fullTimeline) {
|
||||||
|
return fullTimeline;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fullTimeline.filter(
|
||||||
|
(t) =>
|
||||||
|
t.timestamp >= review.start_time &&
|
||||||
|
(review.end_time == undefined || t.timestamp <= review.end_time),
|
||||||
|
);
|
||||||
|
}, [fullTimeline, review]);
|
||||||
|
|
||||||
if (isValidating && (!timeline || timeline.length === 0)) {
|
if (isValidating && (!timeline || timeline.length === 0)) {
|
||||||
return <ActivityIndicator className="ml-2 size-3" />;
|
return <ActivityIndicator className="ml-2 size-3" />;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -157,9 +157,11 @@ function MobileMenuItem({
|
|||||||
const { t } = useTranslation(["views/settings"]);
|
const { t } = useTranslation(["views/settings"]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<div
|
||||||
variant="ghost"
|
className={cn(
|
||||||
className={cn("w-full justify-between pr-2", className)}
|
"inline-flex h-10 w-full cursor-pointer items-center justify-between whitespace-nowrap rounded-md px-4 py-2 pr-2 text-sm font-medium text-primary-variant disabled:pointer-events-none disabled:opacity-50",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onSelect(item.key);
|
onSelect(item.key);
|
||||||
onClose?.();
|
onClose?.();
|
||||||
@ -167,7 +169,7 @@ function MobileMenuItem({
|
|||||||
>
|
>
|
||||||
<div className="smart-capitalize">{t("menu." + item.key)}</div>
|
<div className="smart-capitalize">{t("menu." + item.key)}</div>
|
||||||
<LuChevronRight className="size-4" />
|
<LuChevronRight className="size-4" />
|
||||||
</Button>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,6 +275,9 @@ export default function Settings() {
|
|||||||
} else {
|
} else {
|
||||||
setPageToggle(page as SettingsType);
|
setPageToggle(page as SettingsType);
|
||||||
}
|
}
|
||||||
|
if (isMobile) {
|
||||||
|
setContentMobileOpen(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// don't clear url params if we're creating a new object mask
|
// don't clear url params if we're creating a new object mask
|
||||||
return !(searchParams.has("object_mask") || searchParams.has("event_id"));
|
return !(searchParams.has("object_mask") || searchParams.has("event_id"));
|
||||||
@ -282,6 +287,9 @@ export default function Settings() {
|
|||||||
const cameraNames = cameras.map((c) => c.name);
|
const cameraNames = cameras.map((c) => c.name);
|
||||||
if (cameraNames.includes(camera)) {
|
if (cameraNames.includes(camera)) {
|
||||||
setSelectedCamera(camera);
|
setSelectedCamera(camera);
|
||||||
|
if (isMobile) {
|
||||||
|
setContentMobileOpen(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// don't clear url params if we're creating a new object mask or trigger
|
// don't clear url params if we're creating a new object mask or trigger
|
||||||
return !(searchParams.has("object_mask") || searchParams.has("event_id"));
|
return !(searchParams.has("object_mask") || searchParams.has("event_id"));
|
||||||
|
|||||||
@ -970,7 +970,6 @@ function Timeline({
|
|||||||
"relative overflow-hidden",
|
"relative overflow-hidden",
|
||||||
isDesktop
|
isDesktop
|
||||||
? cn(
|
? cn(
|
||||||
"no-scrollbar overflow-y-auto",
|
|
||||||
timelineType == "timeline"
|
timelineType == "timeline"
|
||||||
? "w-[100px] flex-shrink-0"
|
? "w-[100px] flex-shrink-0"
|
||||||
: timelineType == "detail"
|
: timelineType == "detail"
|
||||||
|
|||||||
@ -717,11 +717,11 @@ export default function CameraSettingsView({
|
|||||||
<div className="flex w-full flex-row items-center gap-2 pt-2 md:w-[25%]">
|
<div className="flex w-full flex-row items-center gap-2 pt-2 md:w-[25%]">
|
||||||
<Button
|
<Button
|
||||||
className="flex flex-1"
|
className="flex flex-1"
|
||||||
aria-label={t("button.cancel", { ns: "common" })}
|
aria-label={t("button.reset", { ns: "common" })}
|
||||||
onClick={onCancel}
|
onClick={onCancel}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<Trans>button.cancel</Trans>
|
<Trans>button.reset</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="select"
|
variant="select"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user