From c1abcf5de8057e6fdd9a7f085fc85b588713c2d7 Mon Sep 17 00:00:00 2001 From: Thomas Sparks <69657545+thsparks@users.noreply.github.com> Date: Thu, 27 Oct 2022 17:53:00 -0600 Subject: [PATCH] Use Deep Links for Copying Multiplayer Join Code, Update Host Lobby Appearance (#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. --- .../src/components/ArcadeSimulator.tsx | 4 +- multiplayer/src/components/CopyButton.tsx | 53 +++++++++++++ multiplayer/src/components/HostLobby.tsx | 75 +++++++++---------- multiplayer/src/components/JoinCodeLabel.tsx | 60 ++++----------- multiplayer/src/util/index.ts | 2 - 5 files changed, 109 insertions(+), 85 deletions(-) create mode 100644 multiplayer/src/components/CopyButton.tsx diff --git a/multiplayer/src/components/ArcadeSimulator.tsx b/multiplayer/src/components/ArcadeSimulator.tsx index 58cabb487815..5e9a92e6d4df 100644 --- a/multiplayer/src/components/ArcadeSimulator.tsx +++ b/multiplayer/src/components/ArcadeSimulator.tsx @@ -156,7 +156,9 @@ export default function Render() {
); } diff --git a/multiplayer/src/components/CopyButton.tsx b/multiplayer/src/components/CopyButton.tsx new file mode 100644 index 000000000000..c28ad2890124 --- /dev/null +++ b/multiplayer/src/components/CopyButton.tsx @@ -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 ( + + ); +} diff --git a/multiplayer/src/components/HostLobby.tsx b/multiplayer/src/components/HostLobby.tsx index 936ac250abaf..1c7ebdd69517 100644 --- a/multiplayer/src/components/HostLobby.tsx +++ b/multiplayer/src/components/HostLobby.tsx @@ -1,16 +1,16 @@ 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(null); - const inviteString = lf("Invite anyone to join your game instantly. Just send them a link!"); const onStartGameClick = async () => { pxt.tickEvent("mp.hostlobby.startgame"); @@ -18,48 +18,47 @@ export default function Render() { 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 (
- {inviteString} + {inviteStringSegments[0]} + { + + {shortLink} + + } + {inviteStringSegments[1]}
-
- - +
+ {joinCode && ( +
+
{lf("Code:")}
+
{joinCode}
+
+ +
)}
diff --git a/multiplayer/src/util/index.ts b/multiplayer/src/util/index.ts index 612b075a0bd8..96f3c1a697de 100644 --- a/multiplayer/src/util/index.ts +++ b/multiplayer/src/util/index.ts @@ -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;