fix: polish webcam mirror toggle

This commit is contained in:
AjTheSpidey
2026-05-24 08:59:54 +08:00
parent 2349272b21
commit 2cf09a88dc
15 changed files with 64 additions and 18 deletions
@@ -1227,12 +1227,14 @@ export function SettingsPanel({
</div>
{webcamLayoutPreset !== "no-webcam" && (
<div className="mt-2 flex items-center justify-between p-2 rounded-lg editor-control-surface">
<div className="text-[10px] font-medium text-slate-300">Mirror webcam</div>
<div className="text-[10px] font-medium text-slate-300">
{t("layout.mirrorWebcam")}
</div>
<Switch
checked={webcamMirrored}
onCheckedChange={onWebcamMirroredChange}
className="data-[state=checked]:bg-[#34B27B] scale-90"
aria-label="Mirror webcam"
aria-label={t("layout.mirrorWebcam")}
/>
</div>
)}
+2 -1
View File
@@ -45,7 +45,8 @@
"dualFrame": "إطار مزدوج",
"webcamShape": "شكل الكاميرا",
"webcamSize": "حجم كاميرا الويب",
"noWebcam": "بدون كاميرا"
"noWebcam": "بدون كاميرا",
"mirrorWebcam": "عكس كاميرا الويب"
},
"effects": {
"title": "تأثيرات الفيديو",
+2 -1
View File
@@ -45,7 +45,8 @@
"dualFrame": "Dual Frame",
"noWebcam": "No Webcam",
"webcamShape": "Camera Shape",
"webcamSize": "Webcam Size"
"webcamSize": "Webcam Size",
"mirrorWebcam": "Mirror webcam"
},
"effects": {
"title": "Video Effects",
+2 -1
View File
@@ -45,7 +45,8 @@
"dualFrame": "Marco dual",
"webcamShape": "Forma de cámara",
"webcamSize": "Tamaño de cámara",
"noWebcam": "Sin cámara"
"noWebcam": "Sin cámara",
"mirrorWebcam": "Reflejar cámara"
},
"effects": {
"title": "Efectos de video",
+2 -1
View File
@@ -46,7 +46,8 @@
"dualFrame": "Double cadre",
"webcamShape": "Forme de la caméra",
"webcamSize": "Taille de la caméra",
"noWebcam": "Sans webcam"
"noWebcam": "Sans webcam",
"mirrorWebcam": "Inverser la webcam"
},
"effects": {
"title": "Effets vidéo",
+3 -1
View File
@@ -1,5 +1,6 @@
{
"zoom": {
"previewHold": "Tieni premuto per vedere lo zoom",
"level": "Livello zoom",
"customScale": "Zoom personalizzato",
"selectRegion": "Seleziona una regione zoom da regolare",
@@ -44,7 +45,8 @@
"dualFrame": "Doppio frame",
"noWebcam": "Nessuna webcam",
"webcamShape": "Forma fotocamera",
"webcamSize": "Dimensione webcam"
"webcamSize": "Dimensione webcam",
"mirrorWebcam": "Specchia webcam"
},
"effects": {
"title": "Effetti video",
+2 -1
View File
@@ -45,7 +45,8 @@
"dualFrame": "デュアルフレーム",
"webcamShape": "カメラの形状",
"webcamSize": "カメラのサイズ",
"noWebcam": "Webカメラなし"
"noWebcam": "Webカメラなし",
"mirrorWebcam": "Webカメラを反転"
},
"effects": {
"title": "動画効果",
+2 -1
View File
@@ -45,7 +45,8 @@
"webcamShape": "카메라 모양",
"webcamSize": "웹캠 크기",
"dualFrame": "듀얼 프레임",
"noWebcam": "웹캠 없음"
"noWebcam": "웹캠 없음",
"mirrorWebcam": "웹캠 미러링"
},
"effects": {
"title": "비디오 효과",
+2 -1
View File
@@ -45,7 +45,8 @@
"dualFrame": "Двойной кадр",
"webcamShape": "Форма камеры",
"webcamSize": "Размер веб-камеры",
"noWebcam": "Без веб-камеры"
"noWebcam": "Без веб-камеры",
"mirrorWebcam": "Зеркалить веб-камеру"
},
"effects": {
"title": "Видеоэффекты",
+2 -1
View File
@@ -45,7 +45,8 @@
"webcamShape": "Kamera Şekli",
"dualFrame": "Çift Kare",
"webcamSize": "Webcam Boyutu",
"noWebcam": "Web kamerası yok"
"noWebcam": "Web kamerası yok",
"mirrorWebcam": "Web kamerasını aynala"
},
"effects": {
"title": "Video Efektleri",
+2 -1
View File
@@ -45,7 +45,8 @@
"dualFrame": "Khung kép",
"webcamShape": "Hình dạng máy ảnh",
"webcamSize": "Kích thước Webcam",
"noWebcam": "Không có webcam"
"noWebcam": "Không có webcam",
"mirrorWebcam": "Lật webcam"
},
"effects": {
"title": "Hiệu ứng video",
+2 -1
View File
@@ -45,7 +45,8 @@
"dualFrame": "双画框",
"webcamShape": "摄像头形状",
"webcamSize": "摄像头大小",
"noWebcam": "无摄像头"
"noWebcam": "无摄像头",
"mirrorWebcam": "镜像摄像头"
},
"effects": {
"title": "视频效果",
+2 -1
View File
@@ -46,7 +46,8 @@
"dualFrame": "雙畫框",
"webcamShape": "攝影機形狀",
"webcamSize": "攝影機大小",
"noWebcam": "無網路攝影機"
"noWebcam": "無網路攝影機",
"mirrorWebcam": "鏡像攝影機"
},
"effects": {
"title": "影片效果",
+28
View File
@@ -66,4 +66,32 @@ describe("drawWebcamFrameImage", () => {
["restore"],
]);
});
it("restores the canvas context if mirrored drawing fails", () => {
const { calls, ctx } = createMockCanvasContext();
const frame = {} as CanvasImageSource;
const error = new Error("draw failed");
ctx.drawImage = () => {
calls.push(["drawImage", frame, 12, 8, 640, 360, 0, 0, 320, 180]);
throw error;
};
expect(() =>
drawWebcamFrameImage(
ctx,
frame,
{ x: 12, y: 8, width: 640, height: 360 },
{ x: 100, y: 50, width: 320, height: 180 },
true,
),
).toThrow(error);
expect(calls).toEqual([
["save"],
["translate", 420, 50],
["scale", -1, 1],
["drawImage", frame, 12, 8, 640, 360, 0, 0, 320, 180],
["restore"],
]);
});
});
+7 -4
View File
@@ -19,10 +19,13 @@ export function drawWebcamFrameImage(
) {
if (mirrored) {
ctx.save();
ctx.translate(dest.x + dest.width, dest.y);
ctx.scale(-1, 1);
ctx.drawImage(image, crop.x, crop.y, crop.width, crop.height, 0, 0, dest.width, dest.height);
ctx.restore();
try {
ctx.translate(dest.x + dest.width, dest.y);
ctx.scale(-1, 1);
ctx.drawImage(image, crop.x, crop.y, crop.width, crop.height, 0, 0, dest.width, dest.height);
} finally {
ctx.restore();
}
return;
}