-
Notifications
You must be signed in to change notification settings - Fork 587
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use Deep Links for Copying Multiplayer Join Code, Update Host Lobby Appearance #9153
Changes from all commits
1307cdd
2d3b7d2
df2b29d
3803f41
468c6f4
3299030
1fd548a
ee46d4e
cc899a7
63fc259
2bb1962
7f98b91
7f77ae5
72ca9f4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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> | ||
); | ||
} |
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]} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Assuming the number of elements in inviteStringSegments feels brittle to me. And assuming the link is in the middle segment isn't a safe assumption. I think you will need to iterate over the segments, and replace the one matching "{0}" with the link. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, currently we split on {0} so there's no actual segment containing it. The only scenario where there wouldn't be 2 segments is if it was at the beginning or the end, so would it be better if we simply checked startsWith and endsWith {0} and reacted accordingly? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess if {0} ended up in multiple places that'd also mess things up but I don't expect that would happen as a result of localization. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. split will leave an empty string at beginning / end if the the string starts with / ends with it so that would still be fine, e.g. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I would split on whitespace and find the {0}. You could optimize by rejoining the contiguous word sequences before and after it. Do this in a useMemo. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But there a ways of solving it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the benefit? Feels like unnecessary complexity to me. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Leaving this as-is for now b/c time crunch, but we can follow up later to understand concerns and address as needed. |
||
{ | ||
<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} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice :)