Skip to content

Commit

Permalink
Use Deep Links for Copying Multiplayer Join Code, Update Host Lobby A…
Browse files Browse the repository at this point in the history
…ppearance (#9153)

This updates the host lobby appearance to match the latest designs more closely, and it will copy a deep link rather than the code itself when the copy button is pressed (on both the host lobby and the game page).

Also sneaking in a fix to make the sim use the full screen width when on a smaller screen.
  • Loading branch information
thsparks authored Oct 27, 2022
1 parent a918d90 commit c1abcf5
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 85 deletions.
4 changes: 3 additions & 1 deletion multiplayer/src/components/ArcadeSimulator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,9 @@ export default function Render() {
<div
id="sim-container"
ref={simContainerRef}
className={"tw-h-[calc(100vh-16rem)] tw-w-[calc(100vw-6rem)]"}
className={
"tw-h-[calc(100vh-16rem)] tw-w-screen md:tw-w-[calc(100vw-6rem)]"
}
/>
);
}
53 changes: 53 additions & 0 deletions multiplayer/src/components/CopyButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { faCopy } from "@fortawesome/free-regular-svg-icons";
import { faCheck } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useContext, useEffect, useState } from "react";
import { AppStateContext } from "../state/AppStateContext";

export default function Render(props: {
copyValue: string;
title: string;
eventName?: string;
}) {
const { state } = useContext(AppStateContext);
const [copySuccessful, setCopySuccessful] = useState(false);
const copyTimeoutMs = 2500;

const copyValue = async () => {
if (props.eventName) pxt.tickEvent(props.eventName);
if (state.gameState?.joinCode) {
navigator.clipboard.writeText(props.copyValue);
setCopySuccessful(true);
}
};

useEffect(() => {
if (copySuccessful) {
let resetCopyTimer = setTimeout(() => {
setCopySuccessful(false);
}, copyTimeoutMs);
return () => {
clearTimeout(resetCopyTimer);
};
}
}, [copySuccessful]);

return (
<button onClick={copyValue} title={props.title}>
<div>
{!copySuccessful && (
<FontAwesomeIcon
icon={faCopy}
className="hover:tw-scale-110 tw-ease-linear tw-duration-[50ms]"
/>
)}
{copySuccessful && (
<FontAwesomeIcon
icon={faCheck}
className="tw-text-green-600"
/>
)}
</div>
</button>
);
}
75 changes: 37 additions & 38 deletions multiplayer/src/components/HostLobby.tsx
Original file line number Diff line number Diff line change
@@ -1,65 +1,64 @@
import { useContext, useRef, useState } from "react";
import { Button } from "react-common/components/controls/Button";
import { Input } from "react-common/components/controls/Input";
import { Link } from "react-common/components/controls/Link";
import { startGameAsync } from "../epics";
import { clearModal } from "../state/actions";
import { AppStateContext } from "../state/AppStateContext";
import { makeJoinLink, SHORT_LINK } from "../util";
import CopyButton from "./CopyButton";
import Loading from "./Loading";
import PresenceBar from "./PresenceBar";

export default function Render() {
const { state, dispatch } = useContext(AppStateContext);
const [copySuccessful, setCopySuccessful] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);
const inviteString = lf("Invite anyone to join your game instantly. Just send them a link!");

const onStartGameClick = async () => {
pxt.tickEvent("mp.hostlobby.startgame");
dispatch(clearModal());
await startGameAsync();
};

const handleCopyClick = () => {
pxt.tickEvent("mp.hostlobby.copyjoinlink");
if (pxt.BrowserUtils.isIpcRenderer()) {
if (inputRef.current) {
setCopySuccessful(
pxt.BrowserUtils.legacyCopyText(inputRef.current)
);
}
} else {
navigator.clipboard.writeText(joinLink);
setCopySuccessful(true);
}
};
const joinCode = state.gameState?.joinCode;
if (!joinCode) {
return null;
}

const handleCopyBlur = () => {
setCopySuccessful(false);
};
// To get a link in the middle of the invite string, we actually preserve the {0} and insert the link manually as an html element later.
const inviteString = lf("Go to {0} and enter code", "{0}");
const inviteStringSegments = inviteString.split("{0}");
const shortLink = SHORT_LINK();

// Insert a space to make the join code easier to read.
const displayJoinCode = joinCode?.slice(0, 3) + " " + joinCode?.slice(3);
const joinDeepLink = makeJoinLink(joinCode);

const joinLink = `${state.gameState?.joinCode}`; // TODO multiplayer : create full link
return (
<div className="tw-flex tw-flex-col tw-gap-1 tw-items-center tw-justify-between tw-bg-white tw-py-[3rem] tw-px-[7rem] tw-shadow-lg tw-rounded-lg">
<div className="tw-mt-3 tw-text-lg tw-text-center tw-text-neutral-700">
{inviteString}
{inviteStringSegments[0]}
{
<Link
href={shortLink}
target="_blank"
className="tw-text-primary-color tw-font-bold hover:tw-text-orange-300"
>
{shortLink}
</Link>
}
{inviteStringSegments[1]}
</div>
<div className="common-input-attached-button tw-mt-5 tw-w-full">
<Input
ariaLabel={lf("join game link")}
handleInputRef={inputRef}
initialValue={joinLink}
readOnly={true}
/>
<Button
className={copySuccessful ? "green" : "primary"}
title={lf("Copy link")}
label={copySuccessful ? lf("Copied!") : lf("Copy")}
leftIcon="fas fa-link"
onClick={handleCopyClick}
onBlur={handleCopyBlur}
/>
<div className="tw-text-4xl tw-mt-4 tw-flex tw-flex-row tw-items-center">
{displayJoinCode}
<div className="tw-ml-2 tw-text-[75%]">
<CopyButton
copyValue={joinDeepLink}
title={lf("Copy join link")}
eventName="mp.hostlobby.copyjoinlink"
/>
</div>
</div>
<Button
className={"teal tw-m-5"}
className={"primary tw-mt-5 tw-mb-7 tw-font-sans"}
label={lf("Start Game")}
title={lf("Start Game")}
onClick={onStartGameClick}
Expand Down
60 changes: 16 additions & 44 deletions multiplayer/src/components/JoinCodeLabel.tsx
Original file line number Diff line number Diff line change
@@ -1,54 +1,26 @@
import { faCopy } from "@fortawesome/free-regular-svg-icons";
import { faCheck } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useContext, useEffect, useState } from "react";
import { AppStateContext } from "../state/AppStateContext";
import { makeJoinLink } from "../util";
import CopyButton from "./CopyButton";

export default function Render() {
const { state } = useContext(AppStateContext);
const [copySuccessful, setCopySuccessful] = useState(false);
const copyTimeoutMs = 2500;

const copyJoinCode = async () => {
pxt.tickEvent("mp.copyjoincode");
if (state.gameState?.joinCode) {
navigator.clipboard.writeText(state.gameState?.joinCode);
setCopySuccessful(true);
}
};

useEffect(() => {
if (copySuccessful) {
let resetCopyTimer = setTimeout(() => {
setCopySuccessful(false);
}, copyTimeoutMs);
return () => {
clearTimeout(resetCopyTimer);
};
}
}, [copySuccessful]);

const joinCode = state.gameState?.joinCode;
const joinDeepLink = joinCode ? makeJoinLink(joinCode) : "";
return (
<div className="tw-justify-self-center">
{state.gameState?.joinCode && (
<div>
{lf("Join Code: {0}", state.gameState?.joinCode)}
<button onClick={copyJoinCode} title={lf("Copy Join Code")}>
<div className="tw-text-sm tw-ml-1">
{!copySuccessful && (
<FontAwesomeIcon
icon={faCopy}
className="hover:tw-scale-110 tw-ease-linear tw-duration-[50ms] tw-mb-[0.1rem]"
/>
)}
{copySuccessful && (
<FontAwesomeIcon
icon={faCheck}
className="tw-text-green-600 tw-mb-[0.1rem]"
/>
)}
</div>
</button>
<div>
{joinCode && (
<div className="tw-flex tw-flex-row tw-items-center tw-align-middle">
<div className="tw-font-bold">{lf("Code:")}</div>
<div className="tw-mx-1">{joinCode}</div>
<div className="tw-text-[75%]">
<CopyButton
copyValue={joinDeepLink}
title={lf("Copy join link")}
eventName="mp.copyjoinlink"
/>
</div>
</div>
)}
</div>
Expand Down
2 changes: 0 additions & 2 deletions multiplayer/src/util/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,6 @@ export function cleanupJoinCode(
joinCode: string | undefined
): string | undefined {
if (!joinCode) return undefined;
joinCode = joinCode.trim();
if (joinCode.length !== 6) return undefined;
joinCode = joinCode.toUpperCase().replace(/[^A-Z0-9]/g, "");
if (joinCode.length !== 6) return undefined;
return joinCode;
Expand Down

0 comments on commit c1abcf5

Please sign in to comment.