Skip to content

Commit 561bb31

Browse files
committed
Add url share feat
1 parent 429dcbc commit 561bb31

File tree

10 files changed

+115
-30
lines changed

10 files changed

+115
-30
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"react": "^18.3.1",
2424
"react-dom": "^18.3.1",
2525
"react-icons": "^5.3.0",
26+
"use-clipboard-copy": "^0.2.0",
2627
"use-custom-compare": "^1.4.0",
2728
"usehooks-ts": "^3.1.0"
2829
},

src/App.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ const queryClient = new QueryClient();
1010
export default function App() {
1111
const [isGameStarted, setIsGameStarted] = useState(false);
1212

13+
const startGame = () => {
14+
setIsGameStarted(true);
15+
window.history.replaceState({}, document.title, "/");
16+
};
17+
1318
return (
1419
<QueryClientProvider client={queryClient}>
1520
<AppContextProvider>
@@ -18,7 +23,7 @@ export default function App() {
1823
<GameView />
1924
</GameContextProvider>
2025
) : (
21-
<InitialView startGame={() => setIsGameStarted(true)} />
26+
<InitialView startGame={startGame} />
2227
)}
2328
</AppContextProvider>
2429
</QueryClientProvider>

src/components/Button/Button.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const Button: FC<ButtonProps> = ({
3030
className={cx(
3131
"text-yellow-400 font-['Pixelify_Sans'] bg-cover bg-center bg-no-repeat flex justify-center items-center shadow-lg",
3232
"text-shadow-custom hover:scale-[1.017] hover:shadow-xl transition-all duration-300 uppercase text-3xl",
33+
"disabled:opacity-50 disabled:cursor-not-allowed disabled:scale-100",
3334
className,
3435
)}
3536
style={{ backgroundImage: `url(${tick ? buttonBgTick : buttonBg})` }}

src/components/DoomCanvas/DoomCanvas.tsx

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,9 @@ const DoomCanvas: React.FC = () => {
1111
const keys = useKeys();
1212

1313
useEffect(() => {
14-
// Prevent effect from running twice
15-
1614
if (!keys.address) return;
1715

16+
// Prevent effect from running twice
1817
if (isEffectRan.current) return;
1918
isEffectRan.current = true;
2019

@@ -32,6 +31,7 @@ const DoomCanvas: React.FC = () => {
3231

3332
canvas.addEventListener("webglcontextlost", handleContextLost, false);
3433

34+
// Initialize HydraMultiplayer
3535
window.HydraMultiplayer = new HydraMultiplayer(
3636
keys,
3737
"http://localhost:4001",
@@ -71,18 +71,10 @@ const DoomCanvas: React.FC = () => {
7171
"-nomusic",
7272
"-config",
7373
"default.cfg",
74+
...(gameData.code ? ["-connect", "1"] : ["-server", "-deathmatch"]),
75+
...(gameData.petName ? ["-pet", gameData.petName] : []),
7476
];
75-
if (gameData.code) {
76-
args.push("-connect");
77-
args.push("1");
78-
} else {
79-
args.push("-server");
80-
args.push("-deathmatch");
81-
}
82-
if (gameData.petName) {
83-
args.push("-pet");
84-
args.push(gameData.petName);
85-
}
77+
8678
window.callMain(args);
8779
},
8880
};

src/components/GameView/GameView.tsx

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { MdContentCopy } from "react-icons/md";
2+
import { ClipboardAPI, useClipboard } from "use-clipboard-copy";
13
import Card from "../Card";
24
import DoomCanvas from "../DoomCanvas";
35
import GlobalTotals from "../GlobalTotals";
@@ -8,8 +10,23 @@ import MusicPlayer from "../MusicPlayer";
810
import RestartButton from "../RestartButton";
911
import StatsCard from "../StatsCard";
1012
import TopLinks from "../TopLinks";
13+
import { useCallback } from "react";
14+
import { FaRegCircleCheck } from "react-icons/fa6";
15+
import { useAppContext } from "../../context/useAppContext";
1116

1217
const GameView = () => {
18+
const { gameData } = useAppContext();
19+
const urlClipboard = useClipboard({ copiedTimeout: 1500 });
20+
21+
const urlClipboardCopy = useCallback(
22+
(clipboard: ClipboardAPI, value: string) => {
23+
clipboard.copy(value);
24+
},
25+
[],
26+
);
27+
28+
const gameUrl = `${window.location.origin}/join/1efabc`;
29+
1330
return (
1431
<Layout>
1532
<TopLinks />
@@ -33,9 +50,31 @@ const GameView = () => {
3350
/>
3451
<HydraHeadLiveTxs />
3552
</div>
36-
<Card className="h-[40rem]">
37-
<DoomCanvas />
38-
</Card>
53+
<div className="flex flex-col gap-6">
54+
<Card className="h-[40rem]">
55+
<DoomCanvas />
56+
</Card>
57+
{gameData.type === "new" && (
58+
<Card className="px-4 py-2 text-center text-xl text-white flex items-center gap-2 justify-center">
59+
Share this URL with friends{" "}
60+
<a
61+
className="text-yellow-400 underline"
62+
href={gameUrl}
63+
target="_blank"
64+
>
65+
{gameUrl}
66+
</a>
67+
{urlClipboard.copied ? (
68+
<FaRegCircleCheck className="text-green-600" />
69+
) : (
70+
<MdContentCopy
71+
role="button"
72+
onClick={() => urlClipboardCopy(urlClipboard, gameUrl)}
73+
/>
74+
)}
75+
</Card>
76+
)}
77+
</div>
3978
<div className="w-80 flex flex-col gap-4">
4079
<GlobalTPS size="sm" titleAlign="left" />
4180
<Card className="text-white py-3 px-4 text-sm leading-3">

src/components/InitialView/InitialView.tsx

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FC, useState } from "react";
1+
import { FC, useEffect, useState } from "react";
22
import Button from "../Button";
33
import GlobalLeaderBoard from "../GlobalLeaderBoard";
44
import GlobalTPS from "../GlobalTPS";
@@ -8,20 +8,43 @@ import SelectContinentDialog from "../SelectContinentDialog";
88
import Layout from "../Layout";
99
import GlobalTotals from "../GlobalTotals";
1010
import SetNameModal from "../SetNameModal";
11+
import { useAppContext } from "../../context/useAppContext";
1112

1213
interface InitialViewProps {
1314
startGame: () => void;
1415
}
1516

1617
const InitialView: FC<InitialViewProps> = ({ startGame }) => {
17-
const [modalData, setModalData] = useState({ title: "", submit: () => {} });
18+
const { setGameData } = useAppContext();
19+
const pathSegments = window.location.pathname.split("/").filter(Boolean);
20+
const [modalData, setModalData] = useState({
21+
title: "Join Multiplayer",
22+
submit: () => {
23+
startGame();
24+
},
25+
});
1826
const [isWelcomeModalOpen, setIsWelcomeModalOpen] = useState(false);
19-
const [isNameModalOpen, setIsNameModalOpen] = useState(false);
27+
const [isNameModalOpen, setIsNameModalOpen] = useState(
28+
pathSegments[0] === "join",
29+
);
2030
const [isSelectContinentModalOpen, setIsSelectContinentModalOpen] =
2131
useState(false);
32+
const code = pathSegments[1];
33+
34+
useEffect(() => {
35+
if (code) {
36+
setGameData((prev) => ({ ...prev, code: code, type: "join" }));
37+
}
38+
}, [code, setGameData]);
2239

2340
const handleClickPlaySolo = () => {
2441
startGame();
42+
setGameData((prev) => ({
43+
...prev,
44+
code: "",
45+
petName: "",
46+
type: undefined,
47+
}));
2548
};
2649

2750
const handleClickStartMultiplayer = () => {
@@ -32,6 +55,7 @@ const InitialView: FC<InitialViewProps> = ({ startGame }) => {
3255
},
3356
});
3457
setIsNameModalOpen(true);
58+
setGameData((prev) => ({ ...prev, type: "new" }));
3559
};
3660

3761
const handleClickJoinMultiplayer = () => {
@@ -41,6 +65,7 @@ const InitialView: FC<InitialViewProps> = ({ startGame }) => {
4165
startGame();
4266
},
4367
});
68+
setGameData((prev) => ({ ...prev, type: "join" }));
4469
setIsNameModalOpen(true);
4570
};
4671

src/components/SetNameModal/SetNameModal.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,15 @@ const SetNameModal: FC<SetNameModalProps> = ({
3535
setGameData((prev) => ({ ...prev, petName: generateRandomName() }));
3636
};
3737

38+
const isButtonDisabled =
39+
(title === "Join Multiplayer" && !gameData.code) || !gameData.petName;
40+
3841
return (
3942
<Modal isOpen={isOpen} close={close}>
4043
<div className="text-center text-4xl flex flex-col gap-12">
4144
<h1 className="text-5xl">{title}</h1>
42-
<p className="text-white">Enter your display name</p>
4345
{title === "Join Multiplayer" && (
44-
<div className="flex items-center gap-5">
46+
<div className="flex items-center gap-5 justify-center">
4547
<label htmlFor="code">Code:</label>
4648
<input
4749
className="border-2 border-white bg-transparent px-2 h-12 w-80 text-2xl focus:outline-none"
@@ -52,7 +54,7 @@ const SetNameModal: FC<SetNameModalProps> = ({
5254
/>
5355
</div>
5456
)}
55-
57+
<p className="text-white">Enter your display name</p>
5658
<div className="flex items-center gap-5">
5759
<button
5860
className="hover:scale-[1.05] transition-transform"
@@ -67,7 +69,12 @@ const SetNameModal: FC<SetNameModalProps> = ({
6769
type="text"
6870
value={gameData.petName}
6971
/>
70-
<Button tick className="text-2xl w-40 h-12" onClick={submit}>
72+
<Button
73+
className="text-2xl w-40 h-12"
74+
disabled={isButtonDisabled}
75+
onClick={submit}
76+
tick
77+
>
7178
Go
7279
</Button>
7380
</div>

src/context/useAppContext.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import { UseQueryResult } from "@tanstack/react-query";
44
import { REGIONS } from "../constants";
55

66
interface GameData {
7-
petName: string;
87
code: string;
8+
petName: string;
9+
type?: "new" | "join";
910
}
1011

1112
interface AppContextInterface {

vite.config.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@ export default defineConfig({
1616
target: "ES2022",
1717
},
1818
define: {
19-
'import.meta.env.SERVER_URL': JSON.stringify(process.env.SERVER_URL),
20-
'import.meta.env.REGION': JSON.stringify(process.env.REGION),
21-
'import.meta.env.CABINET_KEY': JSON.stringify(process.env.CABINET_KEY),
22-
'import.meta.env.PERSISTENT_SESSION': JSON.stringify(process.env.PERSISTENT_SESSION),
23-
}
19+
"import.meta.env.SERVER_URL": JSON.stringify(process.env.SERVER_URL),
20+
"import.meta.env.REGION": JSON.stringify(process.env.REGION),
21+
"import.meta.env.CABINET_KEY": JSON.stringify(process.env.CABINET_KEY),
22+
"import.meta.env.PERSISTENT_SESSION": JSON.stringify(
23+
process.env.PERSISTENT_SESSION,
24+
),
25+
},
2426
});

yarn.lock

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,6 +1038,11 @@ cli-truncate@^4.0.0:
10381038
slice-ansi "^5.0.0"
10391039
string-width "^7.0.0"
10401040

1041+
clipboard-copy@^3.0.0:
1042+
version "3.2.0"
1043+
resolved "https://registry.yarnpkg.com/clipboard-copy/-/clipboard-copy-3.2.0.tgz#3c5b8651d3512dcfad295d77a9eb09e7fac8d5fb"
1044+
integrity sha512-vooFaGFL6ulEP1liiaWFBmmfuPm3cY3y7T9eB83ZTnYc/oFeAKsq3NcDrOkBC8XaauEE8zHQwI7k0+JSYiVQSQ==
1045+
10411046
color-convert@^1.9.0:
10421047
version "1.9.3"
10431048
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
@@ -2524,6 +2529,13 @@ uri-js@^4.2.2:
25242529
dependencies:
25252530
punycode "^2.1.0"
25262531

2532+
use-clipboard-copy@^0.2.0:
2533+
version "0.2.0"
2534+
resolved "https://registry.yarnpkg.com/use-clipboard-copy/-/use-clipboard-copy-0.2.0.tgz#e1f31f2b21e369bc79b5d7b358e2c8aece6ef264"
2535+
integrity sha512-f0PMMwZ2/Hh9/54L12capx4s6ASdd6edNJxg2OcqWVNM8BPvtOSmNFIN1Dg/q//fPp8MpUZceHfr7cnWOS0RxA==
2536+
dependencies:
2537+
clipboard-copy "^3.0.0"
2538+
25272539
use-custom-compare@^1.4.0:
25282540
version "1.4.0"
25292541
resolved "https://registry.yarnpkg.com/use-custom-compare/-/use-custom-compare-1.4.0.tgz#3b750ae0487d3c8e8c38ed2da8b633450981e4e9"

0 commit comments

Comments
 (0)