Skip to content
Open
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
11 changes: 11 additions & 0 deletions site/src/components/IOSTapOverlay.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';

export function IOSTapOverlay ({ isIOSDevice, isPlaying, onPlay }) {
if (!isIOSDevice || isPlaying) return null;
return (
<div
onClick={onPlay}
style={{ position: "absolute", top: 0, left: 0, right: 0, bottom: 0 }}
/>
);
}
15 changes: 13 additions & 2 deletions site/src/components/NetplayCart.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import Link from '@docusaurus/Link';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import BrowserOnly from '@docusaurus/BrowserOnly';
import { AspectRatio } from './AspectRatio';
import { useIOSTapToPlay } from './useIOSTapToPlay';
import { IOSTapOverlay } from './IOSTapOverlay';
import { Giscus } from "@giscus/react";
import { RWebShare } from "react-web-share";
import { MdSaveAlt, MdShare } from "react-icons/md";

function Embed () {
function Embed ({ isIOSDevice, isPlaying, onPlay }) {
const peerId = location.hash.substring(1);
return (
<AspectRatio width={1} height={1} className="game-embed-wrapper">
Expand All @@ -18,17 +20,24 @@ function Embed () {
frameBorder="0"
className="game-embed">
</iframe>
<IOSTapOverlay isIOSDevice={isIOSDevice} isPlaying={isPlaying} onPlay={onPlay} />
</AspectRatio>
);
}

export default function NetplayCart () {
const { isIOSDevice, isPlaying, onPlay: handlePlay } = useIOSTapToPlay(".game-embed");

return (
<Layout title="Netplay">
<main>
<div className="container game-container">
<BrowserOnly>{() => Embed()}</BrowserOnly>
<BrowserOnly>{() => (
<Embed isIOSDevice={isIOSDevice} isPlaying={isPlaying} onPlay={handlePlay} />
)}</BrowserOnly>

{!isPlaying && (
<>
<div className="text--center margin-bottom--lg">
<small>Controls: Arrows, X, Z</small>
</div>
Expand All @@ -47,6 +56,8 @@ export default function NetplayCart () {

<b><i>How does this work?</i></b>
<p>Check out the <Link href="/docs/guides/multiplayer">documentation</Link> for more details about building multiplayer games with WASM-4.</p>
</>
)}
</div>
</main>
</Layout>
Expand Down
14 changes: 12 additions & 2 deletions site/src/components/PlayCart.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import React from 'react';
import Layout from '@theme/Layout';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import { AspectRatio } from './AspectRatio';
import { useIOSTapToPlay } from './useIOSTapToPlay';
import { IOSTapOverlay } from './IOSTapOverlay';
import { Giscus } from "@giscus/react";
import { RWebShare } from "react-web-share";
import { MdSaveAlt, MdShare } from "react-icons/md";

function Embed ({ slug, title, author }) {
function Embed ({ slug, isIOSDevice, isPlaying, onPlay }) {
let params = "?url="+encodeURIComponent(`/carts/${slug}.wasm`);
params += "&disk-prefix="+encodeURIComponent(slug);
// params += "&screenshot="+encodeURIComponent(`/carts/${slug}.png`);
Expand All @@ -16,6 +18,7 @@ function Embed ({ slug, title, author }) {
// if (author) {
// params += "&author="+encodeURIComponent(author);
// }

return (
<AspectRatio width={1} height={1} className="game-embed-wrapper">
<iframe
Expand All @@ -24,13 +27,16 @@ function Embed ({ slug, title, author }) {
frameBorder="0"
className="game-embed">
</iframe>
<IOSTapOverlay isIOSDevice={isIOSDevice} isPlaying={isPlaying} onPlay={onPlay} />
</AspectRatio>
);
}

export default function PlayCart ({ cart }) {
const {siteConfig} = useDocusaurusContext();

const { isIOSDevice, isPlaying, onPlay: handlePlay } = useIOSTapToPlay(".game-embed");

const dateString = new Intl.DateTimeFormat(undefined, {
day: "numeric",
month: "long",
Expand All @@ -44,8 +50,10 @@ export default function PlayCart ({ cart }) {
image={`https://wasm4.org/carts/${cart.slug}.png`}>
<main>
<div className="container game-container">
<Embed {... cart}/>
<Embed {...cart} isIOSDevice={isIOSDevice} isPlaying={isPlaying} onPlay={handlePlay} />

{!isPlaying && (
<>
<div className="text--center margin-bottom--lg">
<small>P1 controls: Arrows, X, Z / P2 controls: ESDF, Tab, Q</small>
</div>
Expand Down Expand Up @@ -112,6 +120,8 @@ export default function PlayCart ({ cart }) {
reactionsEnabled="1"
emitMetadata="0"
/>
</>
)}
</div>
</main>
</Layout>
Expand Down
18 changes: 18 additions & 0 deletions site/src/components/forwardTapToIframe.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Replays a tap that landed on the parent-page overlay as a real touch
// press inside the iframe, so the runtime's own fullscreen-on-touch fires.
export function forwardTapToIframe (event, iframeSelector) {
const iframe = document.querySelector(iframeSelector);
if (!iframe || !iframe.contentWindow) return;

const rect = iframe.getBoundingClientRect();
const pointerInit = {
bubbles: true,
cancelable: true,
pointerType: "touch",
isPrimary: true,
clientX: event.clientX - rect.left,
clientY: event.clientY - rect.top,
};
iframe.contentWindow.dispatchEvent(new PointerEvent("pointerdown", pointerInit));
iframe.contentWindow.dispatchEvent(new PointerEvent("pointerup", pointerInit));
}
7 changes: 7 additions & 0 deletions site/src/components/isIOS.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function isIOS () {
if (typeof navigator === "undefined") return false;

// iPadOS 13+ reports a Mac desktop UA, but exposes multi-touch unlike any real Mac.
return /iPhone|iPad|iPod/i.test(navigator.userAgent)
|| (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);
}
19 changes: 19 additions & 0 deletions site/src/components/useIOSTapToPlay.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useEffect, useState } from 'react';
import { isIOS } from './isIOS';
import { forwardTapToIframe } from './forwardTapToIframe';

export function useIOSTapToPlay (iframeSelector) {
const [isIOSDevice, setIsIOSDevice] = useState(false);
const [isPlaying, setIsPlaying] = useState(false);

useEffect(() => {
setIsIOSDevice(isIOS());
}, []);

const onPlay = (event) => {
forwardTapToIframe(event, iframeSelector);
setIsPlaying(true);
};

return { isIOSDevice, isPlaying, onPlay };
}
Loading