Next.js app that plays real .vpx tables via @valknar/vpinball-wasm (WebAssembly Visual Pinball). Demo mode with the bundled default table, drag-and-drop custom table upload, a cabinet-styled UI with a coin-door HUD (fullscreen/stats/info/eject), a custom 404 page, PWA support (hand-rolled service worker), and a static Docker Compose deployment behind nginx. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012hQoM3jJT1Lx7CMTciMzvD
39 lines
1.1 KiB
TypeScript
39 lines
1.1 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect } from "react";
|
|
import type { attachTouchControls as AttachTouchControls } from "@valknar/vpinball-wasm";
|
|
|
|
const ENGINE_BASE_URL = "/vendor/vpinball-wasm";
|
|
|
|
function isTouchDevice(): boolean {
|
|
return typeof window !== "undefined" && ("ontouchstart" in window || navigator.maxTouchPoints > 0);
|
|
}
|
|
|
|
/** Attaches the engine's on-screen touch overlay to `containerRef` while `active` is true. */
|
|
export function useTouchControls(
|
|
containerRef: React.RefObject<HTMLElement | null>,
|
|
active: boolean,
|
|
): void {
|
|
useEffect(() => {
|
|
if (!active || !isTouchDevice()) return;
|
|
const container = containerRef.current;
|
|
if (!container) return;
|
|
|
|
let handle: { detach: () => void } | undefined;
|
|
let cancelled = false;
|
|
|
|
(async () => {
|
|
const { attachTouchControls } = (await import(
|
|
/* webpackIgnore: true */ `${ENGINE_BASE_URL}/index.js`
|
|
)) as { attachTouchControls: typeof AttachTouchControls };
|
|
if (cancelled) return;
|
|
handle = attachTouchControls({ container });
|
|
})();
|
|
|
|
return () => {
|
|
cancelled = true;
|
|
handle?.detach();
|
|
};
|
|
}, [containerRef, active]);
|
|
}
|