diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 74e82d5..f1082db 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -43,7 +43,7 @@ jobs: run: bun run build - name: Install browser - run: bunx playwright install --with-deps chromium + run: bunx playwright install --with-deps chromium webkit - name: Browser tests run: bun run test:e2e diff --git a/app/pingpong/CameraPanel.tsx b/app/pingpong/CameraPanel.tsx index f1b9249..fbe97fc 100644 --- a/app/pingpong/CameraPanel.tsx +++ b/app/pingpong/CameraPanel.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useRef, useState } from "react"; +import { useEffect, useRef, useState, type CSSProperties } from "react"; import { validCorners, type Corners, type XY } from "./vision/geometry"; import type { Suggestion, VisionResult } from "./vision/tracker"; import styles from "./pingpong.module.css"; @@ -27,6 +27,7 @@ export default function CameraPanel({ onCameraLost, }: Props) { const video = useRef(null); + const videoSize = useRef(""); const stream = useRef(null); const objectUrl = useRef(null); const request = useRef(0); @@ -55,6 +56,20 @@ export default function CameraPanel({ useEffect(() => { callbacks.current.onReady(!!source && calibrated && !trackingError); }, [source, calibrated, trackingError]); + useEffect(() => { + const rotate = () => { + setCorners([]); + setResult(null); + callbacks.current.onReady(false); + callbacks.current.onCameraLost(); + }; + window.addEventListener("orientationchange", rotate); + window.screen.orientation?.addEventListener("change", rotate); + return () => { + window.removeEventListener("orientationchange", rotate); + window.screen.orientation?.removeEventListener("change", rotate); + }; + }, []); useEffect( () => () => { request.current++; @@ -65,6 +80,7 @@ export default function CameraPanel({ ); function releaseSource() { + videoSize.current = ""; stream.current?.getTracks().forEach((track) => track.stop()); stream.current = null; if (objectUrl.current) URL.revokeObjectURL(objectUrl.current); @@ -140,8 +156,17 @@ export default function CameraPanel({ setGeneration((value) => value + 1); } function metadata() { - if (video.current?.videoWidth) - setAspect(video.current.videoWidth / video.current.videoHeight); + const element = video.current; + if (!element?.videoWidth || !element.videoHeight) return; + const size = `${element.videoWidth}:${element.videoHeight}`; + if (videoSize.current && videoSize.current !== size) { + setCorners([]); + setResult(null); + callbacks.current.onReady(false); + callbacks.current.onCameraLost(); + } + videoSize.current = size; + setAspect(element.videoWidth / element.videoHeight); } useEffect(() => { @@ -282,12 +307,18 @@ export default function CameraPanel({ 02 / Cámara ● Solo en tu teléfono -
+
{!calibrated ? ( -

+

{corners.length < 4 ? ( <> Toca la esquina{" "} @@ -480,7 +511,7 @@ export default function CameraPanel({ )}

) : ( -

+

Mesa calibrada.{" "} {active ? "El conteo experimental está activo." diff --git a/app/pingpong/PingPongClient.tsx b/app/pingpong/PingPongClient.tsx index a2fc1fd..5b0199b 100644 --- a/app/pingpong/PingPongClient.tsx +++ b/app/pingpong/PingPongClient.tsx @@ -134,6 +134,9 @@ export default function PingPongClient() { (item) => !item.finished && scoreMatch(item).winner === null, ); + useEffect(() => { + window.scrollTo({ top: 0, left: 0, behavior: "instant" }); + }, [screen]); useEffect(() => { activeMatch.current = match; }, [match]); @@ -202,7 +205,7 @@ export default function PingPongClient() { }; document.addEventListener("visibilitychange", visibility); const rotate = () => { - if (activeMatch.current?.mode !== "manual") { + if (activeMatch.current && activeMatch.current.mode !== "manual") { setPaused(true); setReady(false); setMessage( @@ -211,9 +214,11 @@ export default function PingPongClient() { } }; window.addEventListener("orientationchange", rotate); + window.screen.orientation?.addEventListener("change", rotate); return () => { document.removeEventListener("visibilitychange", visibility); window.removeEventListener("orientationchange", rotate); + window.screen.orientation?.removeEventListener("change", rotate); }; }, []); useEffect(() => { @@ -445,7 +450,11 @@ export default function PingPongClient() { const modalOpen = help || !!pending || sideChange || finishDialog; return ( -

+
+ + -