step 1 tweaks

This commit is contained in:
Josh Hawkins 2025-10-11 05:59:28 -05:00
parent be896789c7
commit 9bcbd9a26e

View File

@ -21,7 +21,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod"; import { z } from "zod";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useState, useCallback, useMemo } from "react"; import { useState, useCallback, useMemo } from "react";
import { LuCircleCheck, LuEye, LuEyeOff } from "react-icons/lu"; import { LuEye, LuEyeOff } from "react-icons/lu";
import ActivityIndicator from "@/components/indicators/activity-indicator"; import ActivityIndicator from "@/components/indicators/activity-indicator";
import axios from "axios"; import axios from "axios";
import { toast } from "sonner"; import { toast } from "sonner";
@ -34,21 +34,24 @@ import {
CAMERA_BRAND_VALUES, CAMERA_BRAND_VALUES,
TestResult, TestResult,
FfprobeStream, FfprobeStream,
StreamRole,
StreamConfig,
} from "@/types/cameraWizard"; } from "@/types/cameraWizard";
import { FaCircleCheck } from "react-icons/fa6";
type Step1NameCameraProps = { type Step1NameCameraProps = {
wizardData: Partial<WizardFormData>; wizardData: Partial<WizardFormData>;
onUpdate: (data: Partial<WizardFormData>) => void; onUpdate: (data: Partial<WizardFormData>) => void;
onNext: (data?: Partial<WizardFormData>) => void;
onCancel: () => void; onCancel: () => void;
onNext?: () => void;
canProceed?: boolean; canProceed?: boolean;
}; };
export default function Step1NameCamera({ export default function Step1NameCamera({
wizardData, wizardData,
onUpdate, onUpdate,
onCancel,
onNext, onNext,
onCancel,
}: Step1NameCameraProps) { }: Step1NameCameraProps) {
const { t } = useTranslation(["views/settings"]); const { t } = useTranslation(["views/settings"]);
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
@ -143,7 +146,7 @@ export default function Step1NameCamera({
const streamUrl = generateStreamUrl(data); const streamUrl = generateStreamUrl(data);
if (!streamUrl) { if (!streamUrl) {
toast.error(t("cameraWizard.step1.errors.noUrl")); toast.error(t("cameraWizard.commonErrors.noUrl"));
return; return;
} }
@ -191,13 +194,13 @@ export default function Step1NameCamera({
const ffprobeData = probeData.stdout; const ffprobeData = probeData.stdout;
const streams = ffprobeData.streams || []; const streams = ffprobeData.streams || [];
// Extract video stream info
const videoStream = streams.find( const videoStream = streams.find(
(s: FfprobeStream) => (s: FfprobeStream) =>
s.codec_type === "video" || s.codec_type === "video" ||
s.codec_name?.includes("h264") || s.codec_name?.includes("h264") ||
s.codec_name?.includes("h265"), s.codec_name?.includes("h265"),
); );
const audioStream = streams.find( const audioStream = streams.find(
(s: FfprobeStream) => (s: FfprobeStream) =>
s.codec_type === "audio" || s.codec_type === "audio" ||
@ -205,7 +208,6 @@ export default function Step1NameCamera({
s.codec_name?.includes("mp3"), s.codec_name?.includes("mp3"),
); );
// Calculate resolution
const resolution = videoStream const resolution = videoStream
? `${videoStream.width}x${videoStream.height}` ? `${videoStream.width}x${videoStream.height}`
: undefined; : undefined;
@ -243,7 +245,7 @@ export default function Step1NameCamera({
success: false, success: false,
error: error, error: error,
}); });
toast.error(t("cameraWizard.step1.testFailed", { error })); toast.error(t("cameraWizard.commonErrors.testFailed", { error }));
} }
} catch (error) { } catch (error) {
const axiosError = error as { const axiosError = error as {
@ -259,7 +261,9 @@ export default function Step1NameCamera({
success: false, success: false,
error: errorMessage, error: errorMessage,
}); });
toast.error(t("cameraWizard.step1.testFailed", { error: errorMessage })); toast.error(
t("cameraWizard.commonErrors.testFailed", { error: errorMessage }),
);
} finally { } finally {
setIsTesting(false); setIsTesting(false);
} }
@ -269,6 +273,28 @@ export default function Step1NameCamera({
onUpdate(data); onUpdate(data);
}; };
const handleContinue = useCallback(() => {
const data = form.getValues();
const streamUrl = generateStreamUrl(data);
const streamId = `stream_${Date.now()}`;
const streamConfig: StreamConfig = {
id: streamId,
url: streamUrl,
roles: ["detect" as StreamRole],
resolution: testResult?.resolution,
testResult: testResult || undefined,
userTested: false,
};
const updatedData = {
...data,
streams: [streamConfig],
};
onNext(updatedData);
}, [form, generateStreamUrl, testResult, onNext]);
return ( return (
<div className="space-y-6"> <div className="space-y-6">
{!testResult?.success && ( {!testResult?.success && (
@ -451,7 +477,7 @@ export default function Step1NameCamera({
{testResult?.success && ( {testResult?.success && (
<div className="p-4"> <div className="p-4">
<div className="mb-3 flex flex-row items-center gap-2 text-sm font-medium text-success"> <div className="mb-3 flex flex-row items-center gap-2 text-sm font-medium text-success">
<LuCircleCheck /> <FaCircleCheck className="size-4" />
{t("cameraWizard.step1.testSuccess")} {t("cameraWizard.step1.testSuccess")}
</div> </div>
@ -473,7 +499,7 @@ export default function Step1NameCamera({
{testResult.resolution && ( {testResult.resolution && (
<div> <div>
<span className="text-secondary-foreground"> <span className="text-secondary-foreground">
{t("cameraWizard.step1.testResultLabels.resolution")}: {t("cameraWizard.testResultLabels.resolution")}:
</span>{" "} </span>{" "}
<span className="text-primary">{testResult.resolution}</span> <span className="text-primary">{testResult.resolution}</span>
</div> </div>
@ -481,7 +507,7 @@ export default function Step1NameCamera({
{testResult.videoCodec && ( {testResult.videoCodec && (
<div> <div>
<span className="text-secondary-foreground"> <span className="text-secondary-foreground">
{t("cameraWizard.step1.testResultLabels.video")}: {t("cameraWizard.testResultLabels.video")}:
</span>{" "} </span>{" "}
<span className="text-primary">{testResult.videoCodec}</span> <span className="text-primary">{testResult.videoCodec}</span>
</div> </div>
@ -489,7 +515,7 @@ export default function Step1NameCamera({
{testResult.audioCodec && ( {testResult.audioCodec && (
<div> <div>
<span className="text-secondary-foreground"> <span className="text-secondary-foreground">
{t("cameraWizard.step1.testResultLabels.audio")}: {t("cameraWizard.testResultLabels.audio")}:
</span>{" "} </span>{" "}
<span className="text-primary">{testResult.audioCodec}</span> <span className="text-primary">{testResult.audioCodec}</span>
</div> </div>
@ -497,7 +523,7 @@ export default function Step1NameCamera({
{testResult.fps && ( {testResult.fps && (
<div> <div>
<span className="text-secondary-foreground"> <span className="text-secondary-foreground">
{t("cameraWizard.step1.testResultLabels.fps")}: {t("cameraWizard.testResultLabels.fps")}:
</span>{" "} </span>{" "}
<span className="text-primary">{testResult.fps}</span> <span className="text-primary">{testResult.fps}</span>
</div> </div>
@ -520,27 +546,7 @@ export default function Step1NameCamera({
{testResult?.success ? ( {testResult?.success ? (
<Button <Button
type="button" type="button"
onClick={() => { onClick={handleContinue}
// Auto-populate stream and proceed to next step
const data = form.getValues();
const streamUrl = generateStreamUrl(data);
const streamId = `stream_${Date.now()}`;
onUpdate({
...data,
streams: [
{
id: streamId,
url: streamUrl,
roles: ["detect"],
resolution: testResult.resolution,
testResult: testResult || undefined,
},
],
});
onNext?.();
}}
variant="select" variant="select"
className="flex items-center justify-center gap-2 sm:flex-1" className="flex items-center justify-center gap-2 sm:flex-1"
> >