Spaces:
Running
Running
File size: 5,468 Bytes
d5c6d34 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
import Button from "./Button";
import { THEME } from "../constants";
interface WebcamCaptureProps {
isRunning: boolean;
onToggleRunning: () => void;
error?: string | null;
imageSize?: number;
onImageSizeChange?: (size: number) => void;
}
export default function WebcamCapture({
isRunning,
onToggleRunning,
error,
imageSize,
onImageSizeChange,
}: WebcamCaptureProps) {
const hasError = Boolean(error);
const statusConfig = hasError
? {
text: "SIGNAL LOSS",
color: "bg-[var(--mistral-red)]",
border: "border-[var(--mistral-red)]",
}
: isRunning
? {
text: "LIVE FEED",
color: "bg-[var(--mistral-orange)] animate-pulse",
border: "border-[var(--mistral-orange)]",
}
: {
text: "PAUSED",
color: "bg-[var(--mistral-orange-dark)]",
border: "border-[var(--mistral-beige-dark)]",
};
return (
<>
{/* Controls Layer */}
<div className="absolute inset-0 z-20 flex flex-col justify-between p-6 pointer-events-none">
{/* Top Bar */}
<div className="flex justify-between items-start pointer-events-auto">
{/* Status Indicator */}
<div
className="backdrop-blur-md border rounded-sm px-3 py-1.5 flex items-center space-x-3 shadow-sm"
style={{
backgroundColor: `${THEME.beigeLight}E6`,
borderColor: THEME.beigeDark,
}}
>
<div className="relative flex h-2.5 w-2.5">
{isRunning && !hasError && (
<span
className={`animate-ping absolute inline-flex h-full w-full rounded-full opacity-75 ${statusConfig.color}`}
></span>
)}
<span
className={`relative inline-flex rounded-full h-2.5 w-2.5 ${statusConfig.color}`}
></span>
</div>
<span
className="text-xs font-mono font-bold tracking-widest"
style={{ color: THEME.textBlack }}
>
{statusConfig.text}
</span>
</div>
{/* Controls */}
<div className="flex gap-2 items-center">
{/* Resolution Slider */}
{imageSize && onImageSizeChange && (
<div
className="hidden md:flex items-center gap-3 backdrop-blur-md border rounded-sm px-3 py-1.5 shadow-sm mr-2 group relative"
style={{
backgroundColor: `${THEME.beigeLight}E6`,
borderColor: THEME.beigeDark,
}}
>
<div className="flex flex-col items-start gap-1 my-1">
<span className="text-[8px] font-mono text-gray-400 uppercase tracking-wider leading-none mb-1">
Input Size: {imageSize}px
</span>
<input
type="range"
min="256"
max="960"
step="32"
value={imageSize}
onChange={(e) => onImageSizeChange(Number(e.target.value))}
className="w-24 h-1 bg-gray-200 rounded-lg appearance-none cursor-pointer accent-[var(--mistral-orange)]"
/>
</div>
{/* Tooltip */}
<div
className="absolute top-full right-0 mt-2 w-54 p-2 text-white text-[10px] rounded shadow-xl opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-50 font-mono"
style={{ backgroundColor: THEME.textBlack }}
>
<p className="mb-1">
<span style={{ color: THEME.mistralOrange }}><</span>{" "}
Lower = Faster (Less accurate)
</p>
<p>
<span style={{ color: THEME.mistralOrange }}>></span>{" "}
Higher = Slower (More accurate)
</p>
</div>
</div>
)}
<Button
onClick={onToggleRunning}
aria-label={isRunning ? "Pause captioning" : "Resume captioning"}
className="backdrop-blur-md bg-white/90 hover:bg-white border hover:border-[var(--mistral-orange)] hover:text-[var(--mistral-orange)] p-2.5 rounded-sm shadow-sm transition-all"
>
{isRunning ? (
<svg
className="w-6 h-6"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v4a1 1 0 102 0V8a1 1 0 00-1-1z"
clipRule="evenodd"
/>
</svg>
) : (
<svg
className="w-6 h-6"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z"
clipRule="evenodd"
/>
</svg>
)}
</Button>
</div>
</div>
</div>
</>
);
}
|