Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
43 changes: 37 additions & 6 deletions app/pingpong/CameraPanel.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -27,6 +27,7 @@ export default function CameraPanel({
onCameraLost,
}: Props) {
const video = useRef<HTMLVideoElement>(null);
const videoSize = useRef("");
const stream = useRef<MediaStream | null>(null);
const objectUrl = useRef<string | null>(null);
const request = useRef(0);
Expand Down Expand Up @@ -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++;
Expand All @@ -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);
Expand Down Expand Up @@ -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(() => {
Expand Down Expand Up @@ -282,12 +307,18 @@ export default function CameraPanel({
<span className={styles.kicker}>02 / Cámara</span>
<span className={styles.localBadge}>● Solo en tu teléfono</span>
</div>
<div className={styles.videoFrame} style={{ aspectRatio: aspect }}>
<div
className={styles.videoFrame}
style={
{ aspectRatio: aspect, "--camera-aspect": aspect } as CSSProperties
}
>
<video
ref={video}
playsInline
muted
onLoadedMetadata={metadata}
onResize={metadata}
onEnded={() => {
callbacks.current.onCameraLost();
setError("Terminó el video. Revisa el marcador.");
Expand Down Expand Up @@ -468,7 +499,7 @@ export default function CameraPanel({
</span>
</div>
{!calibrated ? (
<p className={styles.instructions}>
<p id="calibration-help" className={styles.instructions}>
{corners.length < 4 ? (
<>
Toca la esquina{" "}
Expand All @@ -480,7 +511,7 @@ export default function CameraPanel({
)}
</p>
) : (
<p className={styles.instructions}>
<p id="calibration-help" className={styles.instructions}>
Mesa calibrada.{" "}
{active
? "El conteo experimental está activo."
Expand Down
47 changes: 30 additions & 17 deletions app/pingpong/PingPongClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand Down Expand Up @@ -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(
Expand All @@ -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(() => {
Expand Down Expand Up @@ -445,7 +450,11 @@ export default function PingPongClient() {
const modalOpen = help || !!pending || sideChange || finishDialog;

return (
<main id="main" lang="es" className={styles.app}>
<main
id="main"
lang="es"
className={`${styles.app} ${screen === "match" ? styles.matchScreen : ""}`}
>
<div className={styles.container} inert={modalOpen ? true : undefined}>
<header className={styles.header}>
<button
Expand Down Expand Up @@ -674,6 +683,21 @@ export default function PingPongClient() {
<h1>{completed ? "Buen partido." : "Un punto más."}</h1>
</div>
<div className={styles.matchToggles}>
<button
className={styles.smallButton}
disabled={
completed ||
sideChange ||
(!ready && match.mode !== "manual" && paused)
}
onClick={() => {
setPaused(!paused);
setPending(null);
}}
>
{paused ? "▶ Reanudar" : "Ⅱ Pausa"}
</button>

<button
className={styles.smallButton}
aria-pressed={sound}
Expand Down Expand Up @@ -717,7 +741,10 @@ export default function PingPongClient() {
}
key={player}
>
<div className={styles.playerName}>
<div
className={styles.playerName}
title={match.players[player]}
>
{match.players[player]}
</div>
<div
Expand Down Expand Up @@ -799,20 +826,6 @@ export default function PingPongClient() {
>
Corregir último
</button>
<button
className={styles.smallButton}
disabled={
completed ||
sideChange ||
(!ready && match.mode !== "manual" && paused)
}
onClick={() => {
setPaused(!paused);
setPending(null);
}}
>
{paused ? "▶ Reanudar" : "Ⅱ Pausa"}
</button>
<button
className={styles.smallButton}
disabled={completed || paused}
Expand Down
Loading
Loading