Removed video and added text
This commit is contained in:
parent
30e957e937
commit
54c7100c2c
@ -16,7 +16,8 @@
|
|||||||
"react-modal": "^3.11.2",
|
"react-modal": "^3.11.2",
|
||||||
"react-scripts": "3.4.0",
|
"react-scripts": "3.4.0",
|
||||||
"shortid": "^2.2.15",
|
"shortid": "^2.2.15",
|
||||||
"theme-ui": "^0.3.1"
|
"theme-ui": "^0.3.1",
|
||||||
|
"uuid": "^7.0.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"predeploy": "yarn build",
|
"predeploy": "yarn build",
|
||||||
|
@ -3,7 +3,7 @@ import { IconButton, Flex, Box, Label, Text } from "theme-ui";
|
|||||||
|
|
||||||
import Modal from "./Modal";
|
import Modal from "./Modal";
|
||||||
|
|
||||||
function AddPartyMemberButton({ streamId }) {
|
function AddPartyMemberButton({ peerId }) {
|
||||||
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
|
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
|
||||||
function openModal() {
|
function openModal() {
|
||||||
setIsAddModalOpen(true);
|
setIsAddModalOpen(true);
|
||||||
@ -32,7 +32,7 @@ function AddPartyMemberButton({ streamId }) {
|
|||||||
<Box>
|
<Box>
|
||||||
<Label p={2}>Other people can join using your ID › ʕ•ᴥ•ʔ</Label>
|
<Label p={2}>Other people can join using your ID › ʕ•ᴥ•ʔ</Label>
|
||||||
<Box p={2} bg="hsla(230, 20%, 0%, 20%)">
|
<Box p={2} bg="hsla(230, 20%, 0%, 20%)">
|
||||||
<Text>{streamId}</Text>
|
<Text>{peerId}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -1,106 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { Flex, IconButton } from "theme-ui";
|
|
||||||
|
|
||||||
function SocialIcon() {
|
|
||||||
return (
|
|
||||||
<IconButton aria-label="Social View">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
enableBackground="new 0 0 24 24"
|
|
||||||
height="24"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
width="24"
|
|
||||||
fill="currentcolor"
|
|
||||||
>
|
|
||||||
<g>
|
|
||||||
<rect fill="none" height="24" width="24" />
|
|
||||||
<rect fill="none" height="24" width="24" />
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<g />
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<path
|
|
||||||
d="M16.67,13.13C18.04,14.06,19,15.32,19,17v3h3c0.55,0,1-0.45,1-1v-2 C23,14.82,19.43,13.53,16.67,13.13z"
|
|
||||||
fillRule="evenodd"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<circle cx="9" cy="8" fillRule="evenodd" r="4" />
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path
|
|
||||||
d="M15,12c2.21,0,4-1.79,4-4c0-2.21-1.79-4-4-4c-0.47,0-0.91,0.1-1.33,0.24 C14.5,5.27,15,6.58,15,8s-0.5,2.73-1.33,3.76C14.09,11.9,14.53,12,15,12z"
|
|
||||||
fillRule="evenodd"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path
|
|
||||||
d="M9,13c-2.67,0-8,1.34-8,4v2c0,0.55,0.45,1,1,1h14c0.55,0,1-0.45,1-1v-2 C17,14.34,11.67,13,9,13z"
|
|
||||||
fillRule="evenodd"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</IconButton>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function EncounterIcon() {
|
|
||||||
return (
|
|
||||||
<IconButton aria-label="Encounter View">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
height="24"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
width="24"
|
|
||||||
fill="currentcolor"
|
|
||||||
>
|
|
||||||
<path d="M0 0h24v24H0V0z" fill="none" />
|
|
||||||
<path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.9 13.98l2.1 2.53 3.1-3.99c.2-.26.6-.26.8.01l3.51 4.68c.25.33.01.8-.4.8H6.02c-.42 0-.65-.48-.39-.81L8.12 14c.19-.26.57-.27.78-.02z" />
|
|
||||||
</svg>
|
|
||||||
</IconButton>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function GameViewSwitch({ view, onViewChange }) {
|
|
||||||
return (
|
|
||||||
<Flex sx={{ width: "128px", height: "32px" }} m={2}>
|
|
||||||
<Flex
|
|
||||||
color={view === "social" ? "text" : "highlight"}
|
|
||||||
sx={{
|
|
||||||
flexGrow: 1,
|
|
||||||
borderRadius: "32px 0 0 32px",
|
|
||||||
justifyContent: "center",
|
|
||||||
borderWidth: "1px",
|
|
||||||
borderStyle: "solid",
|
|
||||||
borderColor: "text",
|
|
||||||
alignItems: "center",
|
|
||||||
transform: "translate(0.5px, 0)"
|
|
||||||
}}
|
|
||||||
onClick={() => onViewChange("social")}
|
|
||||||
>
|
|
||||||
<SocialIcon />
|
|
||||||
</Flex>
|
|
||||||
<Flex
|
|
||||||
color={view === "encounter" ? "text" : "highlight"}
|
|
||||||
sx={{
|
|
||||||
flexGrow: 1,
|
|
||||||
borderRadius: "0 32px 32px 0",
|
|
||||||
justifyContent: "center",
|
|
||||||
borderWidth: "1px",
|
|
||||||
borderStyle: "solid",
|
|
||||||
borderColor: "text",
|
|
||||||
alignItems: "center",
|
|
||||||
transform: "translate(-0.5px, 0)"
|
|
||||||
}}
|
|
||||||
onClick={() => onViewChange("encounter")}
|
|
||||||
>
|
|
||||||
<EncounterIcon />
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GameViewSwitch;
|
|
18
src/components/Message.js
Normal file
18
src/components/Message.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Box, Text, Flex } from "theme-ui";
|
||||||
|
|
||||||
|
function Message({ message }) {
|
||||||
|
const timeFormatted = new Date(message.time).toLocaleTimeString("en-US");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ width: "100%" }} my={1}>
|
||||||
|
<Flex>
|
||||||
|
<Text variant="heading">{message.nickname}</Text>
|
||||||
|
{/* <Text variant="caption">{timeFormatted}</Text> */}
|
||||||
|
</Flex>
|
||||||
|
<Text variant="message">{message.text}</Text>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Message;
|
@ -1,78 +1,80 @@
|
|||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
|
import { Flex, Box, Input, Text } from "theme-ui";
|
||||||
|
import { v4 as uuid } from "uuid";
|
||||||
|
|
||||||
import { Flex, Box } from "theme-ui";
|
|
||||||
|
|
||||||
import PartyVideo from "./PartyVideo";
|
|
||||||
import AddPartyMemberButton from "./AddPartyMemberButton";
|
import AddPartyMemberButton from "./AddPartyMemberButton";
|
||||||
import GameViewSwitch from "./GameViewSwitch";
|
import Message from "./Message";
|
||||||
|
|
||||||
function Party({ streams, localStreamId, view, onViewChange }) {
|
import { getRandomMonster } from "../helpers/monsters";
|
||||||
if (view === "social") {
|
|
||||||
return (
|
function Party({ peerId, messages, onMessageSend }) {
|
||||||
<Flex
|
const [messageText, setMessageText] = useState("");
|
||||||
sx={{
|
const [nickname, setNickname] = useState(getRandomMonster());
|
||||||
flexDirection: "column",
|
|
||||||
width: "100%",
|
function handleMessageSubmit(event) {
|
||||||
alignItems: "center",
|
event.preventDefault();
|
||||||
overflowY: "auto"
|
if (!messageText || !peerId) {
|
||||||
}}
|
return;
|
||||||
>
|
}
|
||||||
<Flex
|
const id = uuid();
|
||||||
p={3}
|
const time = Date.now();
|
||||||
bg="background"
|
const message = {
|
||||||
sx={{
|
nickname,
|
||||||
flexDirection: "row",
|
id,
|
||||||
width: "100%",
|
text: messageText,
|
||||||
flexWrap: "wrap",
|
time
|
||||||
justifyContent: "center",
|
};
|
||||||
alignItems: "center"
|
onMessageSend(message);
|
||||||
}}
|
setMessageText("");
|
||||||
>
|
|
||||||
{Object.entries(streams).map(([id, stream]) => (
|
|
||||||
<Box key={id} m={2}>
|
|
||||||
<PartyVideo stream={stream} muted={id === localStreamId} />
|
|
||||||
</Box>
|
|
||||||
))}
|
|
||||||
</Flex>
|
|
||||||
<Box sx={{ flexGrow: 1 }}>
|
|
||||||
<AddPartyMemberButton streamId={localStreamId} />
|
|
||||||
</Box>
|
|
||||||
<Flex my={2}>
|
|
||||||
<GameViewSwitch view={view} onViewChange={onViewChange} />
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
} else if (view === "encounter") {
|
|
||||||
return (
|
|
||||||
<Flex
|
|
||||||
p={3}
|
|
||||||
bg="background"
|
|
||||||
sx={{
|
|
||||||
flexDirection: "column",
|
|
||||||
width: "192px",
|
|
||||||
minWidth: "192px",
|
|
||||||
overflowY: "auto",
|
|
||||||
alignItems: "center"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Object.entries(streams).map(([id, stream]) => (
|
|
||||||
<Box key={id} my={1}>
|
|
||||||
<PartyVideo stream={stream} muted={id === localStreamId} />
|
|
||||||
</Box>
|
|
||||||
))}
|
|
||||||
<Box sx={{ flexGrow: 1 }}>
|
|
||||||
<AddPartyMemberButton streamId={localStreamId} />
|
|
||||||
</Box>
|
|
||||||
<Flex my={1}>
|
|
||||||
<GameViewSwitch view={view} onViewChange={onViewChange} />
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return (
|
||||||
|
<Flex
|
||||||
|
p={3}
|
||||||
|
bg="background"
|
||||||
|
sx={{
|
||||||
|
flexDirection: "column",
|
||||||
|
width: "256px",
|
||||||
|
minWidth: "256px",
|
||||||
|
overflowY: "auto",
|
||||||
|
alignItems: "center"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<AddPartyMemberButton peerId={peerId} />
|
||||||
|
</Box>
|
||||||
|
<Flex
|
||||||
|
p={2}
|
||||||
|
sx={{ width: "100%" }}
|
||||||
|
bg="muted"
|
||||||
|
sx={{
|
||||||
|
flexGrow: 1,
|
||||||
|
width: "100%",
|
||||||
|
borderRadius: "4px",
|
||||||
|
flexDirection: "column",
|
||||||
|
justifyContent: "flex-end"
|
||||||
|
}}
|
||||||
|
my={2}
|
||||||
|
>
|
||||||
|
{Object.values(messages)
|
||||||
|
.sort((a, b) => a.time - b.time)
|
||||||
|
.map(message => (
|
||||||
|
<Message key={message.id} message={message} />
|
||||||
|
))}
|
||||||
|
<Box as="form" onSubmit={handleMessageSubmit} sx={{ width: "100%" }}>
|
||||||
|
<Input
|
||||||
|
value={messageText}
|
||||||
|
onChange={event => setMessageText(event.target.value)}
|
||||||
|
p={1}
|
||||||
|
disabled={!peerId}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Text my={1} variant="caption">
|
||||||
|
{nickname}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Party.defaultProps = { view: "social" };
|
|
||||||
|
|
||||||
export default Party;
|
export default Party;
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
import React, { useRef, useEffect } from "react";
|
|
||||||
|
|
||||||
function PartyVideo({ stream, muted }) {
|
|
||||||
const videoRef = useRef();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (videoRef.current) {
|
|
||||||
videoRef.current.srcObject = stream;
|
|
||||||
}
|
|
||||||
}, [stream]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<video
|
|
||||||
ref={videoRef}
|
|
||||||
autoPlay
|
|
||||||
muted={muted}
|
|
||||||
style={{ width: "100%", borderRadius: "4px", maxWidth: "500px" }}
|
|
||||||
playsInline
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PartyVideo;
|
|
300
src/helpers/monsters.js
Normal file
300
src/helpers/monsters.js
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
const monsters = [
|
||||||
|
"Aboleth",
|
||||||
|
"Acolyte",
|
||||||
|
"Black Dragon",
|
||||||
|
"Blue Dragon",
|
||||||
|
"Brass Dragon",
|
||||||
|
"Bronze Dragon",
|
||||||
|
"Copper Dragon",
|
||||||
|
"Gold Dragon",
|
||||||
|
"Green Dragon",
|
||||||
|
"Red Dragon",
|
||||||
|
"Silver Dragon",
|
||||||
|
"White Dragon",
|
||||||
|
"Air Elemental",
|
||||||
|
"Androsphinx",
|
||||||
|
"Animated Armor",
|
||||||
|
"Ankheg",
|
||||||
|
"Ape",
|
||||||
|
"Archmage",
|
||||||
|
"Assassin",
|
||||||
|
"Awakened Shrub",
|
||||||
|
"Awakened Tree",
|
||||||
|
"Axe Beak",
|
||||||
|
"Azer",
|
||||||
|
"Baboon",
|
||||||
|
"Badger",
|
||||||
|
"Balor",
|
||||||
|
"Bandit",
|
||||||
|
"Bandit Captain",
|
||||||
|
"Barbed Devil",
|
||||||
|
"Basilisk",
|
||||||
|
"Bat",
|
||||||
|
"Bearded Devil",
|
||||||
|
"Behir",
|
||||||
|
"Berserker",
|
||||||
|
"Black Bear",
|
||||||
|
"Black Pudding",
|
||||||
|
"Blink Dog",
|
||||||
|
"Blood Hawk",
|
||||||
|
"Boar",
|
||||||
|
"Bone Devil",
|
||||||
|
"Brown Bear",
|
||||||
|
"Bugbear",
|
||||||
|
"Bulette",
|
||||||
|
"Camel",
|
||||||
|
"Cat",
|
||||||
|
"Centaur",
|
||||||
|
"Chain Devil",
|
||||||
|
"Chimera",
|
||||||
|
"Chuul",
|
||||||
|
"Clay Golem",
|
||||||
|
"Cloaker",
|
||||||
|
"Cloud Giant",
|
||||||
|
"Cockatrice",
|
||||||
|
"Commoner",
|
||||||
|
"Constrictor Snake",
|
||||||
|
"Couatl",
|
||||||
|
"Crab",
|
||||||
|
"Crocodile",
|
||||||
|
"Cult Fanatic",
|
||||||
|
"Cultist",
|
||||||
|
"Darkmantle",
|
||||||
|
"Death Dog",
|
||||||
|
"Deep Gnome",
|
||||||
|
"Deer",
|
||||||
|
"Deva",
|
||||||
|
"Dire Wolf",
|
||||||
|
"Diseased Giant Rat",
|
||||||
|
"Djinni",
|
||||||
|
"Doppelganger",
|
||||||
|
"Draft Horse",
|
||||||
|
"Dragon Turtle",
|
||||||
|
"Dretch",
|
||||||
|
"Drider",
|
||||||
|
"Drow",
|
||||||
|
"Druid",
|
||||||
|
"Dryad",
|
||||||
|
"Duergar",
|
||||||
|
"Dust Mephit",
|
||||||
|
"Eagle",
|
||||||
|
"Earth Elemental",
|
||||||
|
"Efreeti",
|
||||||
|
"Elephant",
|
||||||
|
"Elk",
|
||||||
|
"Erinyes",
|
||||||
|
"Ettercap",
|
||||||
|
"Ettin",
|
||||||
|
"Fire Elemental",
|
||||||
|
"Fire Giant",
|
||||||
|
"Flesh Golem",
|
||||||
|
"Flying Snake",
|
||||||
|
"Flying Sword",
|
||||||
|
"Frog",
|
||||||
|
"Frost Giant",
|
||||||
|
"Gargoyle",
|
||||||
|
"Gelatinous Cube",
|
||||||
|
"Ghast",
|
||||||
|
"Ghost",
|
||||||
|
"Ghoul",
|
||||||
|
"Giant Ape",
|
||||||
|
"Giant Badger",
|
||||||
|
"Giant Bat",
|
||||||
|
"Giant Boar",
|
||||||
|
"Giant Centipede",
|
||||||
|
"Giant Constrictor Snake",
|
||||||
|
"Giant Crab",
|
||||||
|
"Giant Crocodile",
|
||||||
|
"Giant Eagle",
|
||||||
|
"Giant Elk",
|
||||||
|
"Giant Fire Beetle",
|
||||||
|
"Giant Frog",
|
||||||
|
"Giant Goat",
|
||||||
|
"Giant Hyena",
|
||||||
|
"Giant Lizard",
|
||||||
|
"Giant Octopus",
|
||||||
|
"Giant Owl",
|
||||||
|
"Giant Poisonous Snake",
|
||||||
|
"Giant Rat",
|
||||||
|
"Giant Scorpion",
|
||||||
|
"Giant Sea Horse",
|
||||||
|
"Giant Shark",
|
||||||
|
"Giant Spider",
|
||||||
|
"Giant Toad",
|
||||||
|
"Giant Vulture",
|
||||||
|
"Giant Wasp",
|
||||||
|
"Giant Weasel",
|
||||||
|
"Giant Wolf Spider",
|
||||||
|
"Gibbering Mouther",
|
||||||
|
"Glabrezu",
|
||||||
|
"Gladiator",
|
||||||
|
"Gnoll",
|
||||||
|
"Goat",
|
||||||
|
"Goblin",
|
||||||
|
"Gorgon",
|
||||||
|
"Gray Ooze",
|
||||||
|
"Green Hag",
|
||||||
|
"Grick",
|
||||||
|
"Griffon",
|
||||||
|
"Grimlock",
|
||||||
|
"Guard",
|
||||||
|
"Guardian Naga",
|
||||||
|
"Gynosphinx",
|
||||||
|
"Harpy",
|
||||||
|
"Hawk",
|
||||||
|
"Hell Hound",
|
||||||
|
"Hezrou",
|
||||||
|
"Hill Giant",
|
||||||
|
"Hippogriff",
|
||||||
|
"Hobgoblin",
|
||||||
|
"Homunculus",
|
||||||
|
"Horned Devil",
|
||||||
|
"Hunter Shark",
|
||||||
|
"Hydra",
|
||||||
|
"Hyena",
|
||||||
|
"Ice Devil",
|
||||||
|
"Ice Mephit",
|
||||||
|
"Imp",
|
||||||
|
"Incubus",
|
||||||
|
"Invisible Stalker",
|
||||||
|
"Iron Golem",
|
||||||
|
"Jackal",
|
||||||
|
"Killer Whale",
|
||||||
|
"Knight",
|
||||||
|
"Kobold",
|
||||||
|
"Kraken",
|
||||||
|
"Lamia",
|
||||||
|
"Lemure",
|
||||||
|
"Lich",
|
||||||
|
"Lion",
|
||||||
|
"Lizard",
|
||||||
|
"Lizardfolk",
|
||||||
|
"Mage",
|
||||||
|
"Magma Mephit",
|
||||||
|
"Magmin",
|
||||||
|
"Mammoth",
|
||||||
|
"Manticore",
|
||||||
|
"Marilith",
|
||||||
|
"Mastiff",
|
||||||
|
"Medusa",
|
||||||
|
"Merfolk",
|
||||||
|
"Merrow",
|
||||||
|
"Mimic",
|
||||||
|
"Minotaur",
|
||||||
|
"Minotaur Skeleton",
|
||||||
|
"Mule",
|
||||||
|
"Mummy",
|
||||||
|
"Mummy Lord",
|
||||||
|
"Nalfeshnee",
|
||||||
|
"Night Hag",
|
||||||
|
"Nightmare",
|
||||||
|
"Noble",
|
||||||
|
"Ochre Jelly",
|
||||||
|
"Octopus",
|
||||||
|
"Ogre",
|
||||||
|
"Ogre Zombie",
|
||||||
|
"Oni",
|
||||||
|
"Orc",
|
||||||
|
"Otyugh",
|
||||||
|
"Owl",
|
||||||
|
"Owlbear",
|
||||||
|
"Panther",
|
||||||
|
"Pegasus",
|
||||||
|
"Phase Spider",
|
||||||
|
"Pit Fiend",
|
||||||
|
"Planetar",
|
||||||
|
"Plesiosaurus",
|
||||||
|
"Poisonous Snake",
|
||||||
|
"Polar Bear",
|
||||||
|
"Pony",
|
||||||
|
"Priest",
|
||||||
|
"Pseudodragon",
|
||||||
|
"Purple Worm",
|
||||||
|
"Quasit",
|
||||||
|
"Quipper",
|
||||||
|
"Rakshasa",
|
||||||
|
"Rat",
|
||||||
|
"Raven",
|
||||||
|
"Reef Shark",
|
||||||
|
"Remorhaz",
|
||||||
|
"Rhinoceros",
|
||||||
|
"Riding Horse",
|
||||||
|
"Roc",
|
||||||
|
"Roper",
|
||||||
|
"Rug of Smothering",
|
||||||
|
"Rust Monster",
|
||||||
|
"Saber-Toothed Tiger",
|
||||||
|
"Sahuagin",
|
||||||
|
"Salamander",
|
||||||
|
"Satyr",
|
||||||
|
"Scorpion",
|
||||||
|
"Scout",
|
||||||
|
"Sea Hag",
|
||||||
|
"Sea Horse",
|
||||||
|
"Shadow",
|
||||||
|
"Shambling Mound",
|
||||||
|
"Shield Guardian",
|
||||||
|
"Shrieker",
|
||||||
|
"Skeleton",
|
||||||
|
"Solar",
|
||||||
|
"Specter",
|
||||||
|
"Spider",
|
||||||
|
"Spirit Naga",
|
||||||
|
"Sprite",
|
||||||
|
"Spy",
|
||||||
|
"Steam Mephit",
|
||||||
|
"Stirge",
|
||||||
|
"Stone Giant",
|
||||||
|
"Stone Golem",
|
||||||
|
"Storm Giant",
|
||||||
|
"Succubus",
|
||||||
|
"Swarm of Bats",
|
||||||
|
"Swarm of Beetles",
|
||||||
|
"Swarm of Centipedes",
|
||||||
|
"Swarm of Insects",
|
||||||
|
"Swarm of Poisonous Snakes",
|
||||||
|
"Swarm of Quippers",
|
||||||
|
"Swarm of Rats",
|
||||||
|
"Swarm of Ravens",
|
||||||
|
"Swarm of Spiders",
|
||||||
|
"Swarm of Wasps",
|
||||||
|
"Tarrasque",
|
||||||
|
"Thug",
|
||||||
|
"Tiger",
|
||||||
|
"Treant",
|
||||||
|
"Tribal Warrior",
|
||||||
|
"Triceratops",
|
||||||
|
"Troll",
|
||||||
|
"Tyrannosaurus Rex",
|
||||||
|
"Unicorn",
|
||||||
|
"Vampire",
|
||||||
|
"Vampire Spawn",
|
||||||
|
"Veteran",
|
||||||
|
"Violet Fungus",
|
||||||
|
"Vrock",
|
||||||
|
"Vulture",
|
||||||
|
"Warhorse",
|
||||||
|
"Warhorse Skeleton",
|
||||||
|
"Water Elemental",
|
||||||
|
"Weasel",
|
||||||
|
"Werebear",
|
||||||
|
"Wereboar",
|
||||||
|
"Wererat",
|
||||||
|
"Weretiger",
|
||||||
|
"Werewolf",
|
||||||
|
"Wight",
|
||||||
|
"Will-o'-Wisp",
|
||||||
|
"Winter Wolf",
|
||||||
|
"Wolf",
|
||||||
|
"Worg",
|
||||||
|
"Wraith",
|
||||||
|
"Wyvern",
|
||||||
|
"Xorn",
|
||||||
|
"Zombie"
|
||||||
|
];
|
||||||
|
|
||||||
|
export default monsters;
|
||||||
|
|
||||||
|
export function getRandomMonster() {
|
||||||
|
return monsters[Math.floor(Math.random() * monsters.length)];
|
||||||
|
}
|
@ -1,18 +1,10 @@
|
|||||||
import { useEffect, useState, useRef } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import Peer from "peerjs";
|
import Peer from "peerjs";
|
||||||
|
|
||||||
const getUserMedia =
|
|
||||||
navigator.getUserMedia ||
|
|
||||||
navigator.webkitGetUserMedia ||
|
|
||||||
navigator.mozGetUserMedia;
|
|
||||||
|
|
||||||
function useSession(onConnectionOpen, onConnectionSync) {
|
function useSession(onConnectionOpen, onConnectionSync) {
|
||||||
const [peerId, setPeerId] = useState(null);
|
const [peerId, setPeerId] = useState(null);
|
||||||
const [peer, setPeer] = useState(null);
|
const [peer, setPeer] = useState(null);
|
||||||
const [connections, setConnections] = useState({});
|
const [connections, setConnections] = useState({});
|
||||||
const [streams, setStreams] = useState({});
|
|
||||||
// Keep a ref to the streams in order to clean them up on unmount
|
|
||||||
const streamsRef = useRef(streams);
|
|
||||||
|
|
||||||
function addConnection(connection) {
|
function addConnection(connection) {
|
||||||
console.log("Adding connection", connection.peer);
|
console.log("Adding connection", connection.peer);
|
||||||
@ -28,59 +20,15 @@ function useSession(onConnectionOpen, onConnectionSync) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function addStream(stream, id) {
|
|
||||||
console.log("Adding stream", id);
|
|
||||||
setStreams(prevStreams => {
|
|
||||||
console.log("Streams", {
|
|
||||||
...prevStreams,
|
|
||||||
[id]: stream
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
...prevStreams,
|
|
||||||
[id]: stream
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log("Creating peer");
|
console.log("Creating peer");
|
||||||
setPeer(new Peer());
|
setPeer(new Peer());
|
||||||
|
|
||||||
return () => {
|
|
||||||
console.log("Cleaning up streams");
|
|
||||||
for (let stream of Object.values(streamsRef.current)) {
|
|
||||||
for (let track of stream.getTracks()) {
|
|
||||||
track.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Update stream refs
|
|
||||||
useEffect(() => {
|
|
||||||
console.log("Syncing stream ref to stream state", streams);
|
|
||||||
streamsRef.current = streams;
|
|
||||||
}, [streams, streamsRef]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleOpen(id) {
|
function handleOpen(id) {
|
||||||
console.log("Peer open", id);
|
console.log("Peer open", id);
|
||||||
setPeerId(id);
|
setPeerId(id);
|
||||||
|
|
||||||
console.log("Getting user data");
|
|
||||||
getUserMedia(
|
|
||||||
{
|
|
||||||
video: {
|
|
||||||
frameRate: { ideal: 15, max: 20 },
|
|
||||||
width: { max: 256 },
|
|
||||||
height: { max: 144 }
|
|
||||||
},
|
|
||||||
audio: false
|
|
||||||
},
|
|
||||||
stream => {
|
|
||||||
addStream(stream, id);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleConnection(connection) {
|
function handleConnection(connection) {
|
||||||
@ -90,7 +38,7 @@ function useSession(onConnectionOpen, onConnectionSync) {
|
|||||||
if (metadata.sync) {
|
if (metadata.sync) {
|
||||||
connection.send({
|
connection.send({
|
||||||
id: "sync",
|
id: "sync",
|
||||||
data: Object.keys(connections)
|
data: { connections: Object.keys(connections) }
|
||||||
});
|
});
|
||||||
if (onConnectionSync) {
|
if (onConnectionSync) {
|
||||||
onConnectionSync(connection);
|
onConnectionSync(connection);
|
||||||
@ -118,25 +66,6 @@ function useSession(onConnectionOpen, onConnectionSync) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCall(call) {
|
|
||||||
console.log("incoming call", call.peer);
|
|
||||||
call.answer(streams[peerId]);
|
|
||||||
call.on("stream", remoteStream => {
|
|
||||||
addStream(remoteStream, call.peer);
|
|
||||||
});
|
|
||||||
function removeStream() {
|
|
||||||
setStreams(prevStreams => {
|
|
||||||
const { [call.peer]: old, ...rest } = prevStreams;
|
|
||||||
return rest;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
call.on("close", removeStream);
|
|
||||||
call.on("error", error => {
|
|
||||||
console.error("Media Connection error", error);
|
|
||||||
removeStream();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleError(error) {
|
function handleError(error) {
|
||||||
console.error("Peer error", error);
|
console.error("Peer error", error);
|
||||||
}
|
}
|
||||||
@ -147,23 +76,13 @@ function useSession(onConnectionOpen, onConnectionSync) {
|
|||||||
|
|
||||||
peer.on("open", handleOpen);
|
peer.on("open", handleOpen);
|
||||||
peer.on("connection", handleConnection);
|
peer.on("connection", handleConnection);
|
||||||
peer.on("call", handleCall);
|
|
||||||
peer.on("error", handleError);
|
peer.on("error", handleError);
|
||||||
return () => {
|
return () => {
|
||||||
peer.removeListener("open", handleOpen);
|
peer.removeListener("open", handleOpen);
|
||||||
peer.removeListener("connection", handleConnection);
|
peer.removeListener("connection", handleConnection);
|
||||||
peer.removeListener("call", handleCall);
|
|
||||||
peer.removeListener("error", handleError);
|
peer.removeListener("error", handleError);
|
||||||
};
|
};
|
||||||
}, [peer, peerId, connections, onConnectionOpen, onConnectionSync, streams]);
|
}, [peer, peerId, connections, onConnectionOpen, onConnectionSync]);
|
||||||
|
|
||||||
function call(connectionId) {
|
|
||||||
console.log("Calling", connectionId);
|
|
||||||
const call = peer.call(connectionId, streams[peerId]);
|
|
||||||
call.on("stream", stream => {
|
|
||||||
addStream(stream, connectionId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function connectTo(connectionId) {
|
function connectTo(connectionId) {
|
||||||
console.log("Connecting to", connectionId);
|
console.log("Connecting to", connectionId);
|
||||||
@ -177,7 +96,8 @@ function useSession(onConnectionOpen, onConnectionSync) {
|
|||||||
connection.on("open", () => {
|
connection.on("open", () => {
|
||||||
connection.on("data", data => {
|
connection.on("data", data => {
|
||||||
if (data.id === "sync") {
|
if (data.id === "sync") {
|
||||||
for (let syncId of data.data) {
|
const { connections: syncConnections } = data.data;
|
||||||
|
for (let syncId of syncConnections) {
|
||||||
console.log("Syncing to", syncId);
|
console.log("Syncing to", syncId);
|
||||||
if (connectionId === syncId || syncId in connections) {
|
if (connectionId === syncId || syncId in connections) {
|
||||||
continue;
|
continue;
|
||||||
@ -186,7 +106,6 @@ function useSession(onConnectionOpen, onConnectionSync) {
|
|||||||
metadata: { sync: false }
|
metadata: { sync: false }
|
||||||
});
|
});
|
||||||
addConnection(syncConnection);
|
addConnection(syncConnection);
|
||||||
call(syncId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -194,10 +113,9 @@ function useSession(onConnectionOpen, onConnectionSync) {
|
|||||||
onConnectionOpen(connection);
|
onConnectionOpen(connection);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
call(connectionId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return { peer, peerId, streams, connections, connectTo };
|
return { peer, peerId, connections, connectTo };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default useSession;
|
export default useSession;
|
||||||
|
@ -20,21 +20,16 @@ function Game() {
|
|||||||
const { gameId } = useContext(GameContext);
|
const { gameId } = useContext(GameContext);
|
||||||
const handleConnectionOpenCallback = useCallback(handleConnectionOpen);
|
const handleConnectionOpenCallback = useCallback(handleConnectionOpen);
|
||||||
const handleConnectionSyncCallback = useCallback(handleConnectionSync);
|
const handleConnectionSyncCallback = useCallback(handleConnectionSync);
|
||||||
const { peerId, connections, connectTo, streams } = useSession(
|
const { peerId, connections, connectTo } = useSession(
|
||||||
handleConnectionOpenCallback,
|
handleConnectionOpenCallback,
|
||||||
handleConnectionSyncCallback
|
handleConnectionSyncCallback
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (gameId !== null && peerId !== null && !(gameId in connections)) {
|
||||||
gameId !== null &&
|
|
||||||
peerId !== null &&
|
|
||||||
streams[peerId] &&
|
|
||||||
!(gameId in connections)
|
|
||||||
) {
|
|
||||||
connectTo(gameId);
|
connectTo(gameId);
|
||||||
}
|
}
|
||||||
}, [gameId, peerId, connectTo, streams, connections]);
|
}, [gameId, peerId, connectTo, connections]);
|
||||||
|
|
||||||
const [mapSource, setMapSource] = useState(null);
|
const [mapSource, setMapSource] = useState(null);
|
||||||
const mapDataRef = useRef(null);
|
const mapDataRef = useRef(null);
|
||||||
@ -71,6 +66,18 @@ function Game() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [messages, setMessages] = useState({});
|
||||||
|
function handleMessageSend(message) {
|
||||||
|
setMessages(prevMessages => ({
|
||||||
|
...prevMessages,
|
||||||
|
[message.id]: message
|
||||||
|
}));
|
||||||
|
for (let connection of Object.values(connections)) {
|
||||||
|
const data = { [message.id]: message };
|
||||||
|
connection.send({ id: "message", data });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleConnectionOpen(connection) {
|
function handleConnectionOpen(connection) {
|
||||||
connection.on("data", data => {
|
connection.on("data", data => {
|
||||||
if (data.id === "map") {
|
if (data.id === "map") {
|
||||||
@ -89,6 +96,12 @@ function Game() {
|
|||||||
omit(prevMapTokens, Object.keys(data.data))
|
omit(prevMapTokens, Object.keys(data.data))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (data.id === "message") {
|
||||||
|
setMessages(prevMessages => ({
|
||||||
|
...prevMessages,
|
||||||
|
...data.data
|
||||||
|
}));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,43 +110,29 @@ function Game() {
|
|||||||
connection.send({ id: "map", data: mapDataRef.current });
|
connection.send({ id: "map", data: mapDataRef.current });
|
||||||
}
|
}
|
||||||
connection.send({ id: "tokenEdit", data: mapTokens });
|
connection.send({ id: "tokenEdit", data: mapTokens });
|
||||||
|
connection.send({ id: "message", data: messages });
|
||||||
}
|
}
|
||||||
|
|
||||||
const [gameView, setGameView] = useState("social");
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex sx={{ flexDirection: "column", height: "100vh" }}>
|
<Flex sx={{ flexDirection: "column", height: "100vh" }}>
|
||||||
<Flex
|
<Flex
|
||||||
sx={{ justifyContent: "space-between", flexGrow: 1, height: "100%" }}
|
sx={{ justifyContent: "space-between", flexGrow: 1, height: "100%" }}
|
||||||
>
|
>
|
||||||
<Party
|
<Party
|
||||||
streams={streams}
|
peerId={peerId}
|
||||||
localStreamId={peerId}
|
messages={messages}
|
||||||
view={gameView}
|
onMessageSend={handleMessageSend}
|
||||||
onViewChange={setGameView}
|
|
||||||
/>
|
/>
|
||||||
{gameView === "encounter" && (
|
<Map
|
||||||
<Map
|
mapSource={mapSource}
|
||||||
mapSource={mapSource}
|
mapData={mapDataRef.current}
|
||||||
mapData={mapDataRef.current}
|
tokens={mapTokens}
|
||||||
tokens={mapTokens}
|
onMapTokenMove={handleEditMapToken}
|
||||||
onMapTokenMove={handleEditMapToken}
|
onMapTokenRemove={handleRemoveMapToken}
|
||||||
onMapTokenRemove={handleRemoveMapToken}
|
onMapChanged={handleMapChanged}
|
||||||
onMapChanged={handleMapChanged}
|
/>
|
||||||
/>
|
<Tokens onCreateMapToken={handleEditMapToken} />
|
||||||
)}
|
|
||||||
{gameView === "encounter" && (
|
|
||||||
<Tokens onCreateMapToken={handleEditMapToken} />
|
|
||||||
)}
|
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex
|
|
||||||
sx={{
|
|
||||||
position: "absolute",
|
|
||||||
left: "50%",
|
|
||||||
bottom: 0,
|
|
||||||
transform: "translate(-50%, -50%)"
|
|
||||||
}}
|
|
||||||
></Flex>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { useContext } from "react";
|
import React, { useContext } from "react";
|
||||||
import { navigate } from "hookrouter";
|
import { navigate } from "hookrouter";
|
||||||
import { Container, Flex, Button, Image, Heading } from "theme-ui";
|
import { Container, Flex, Button, Image, Text } from "theme-ui";
|
||||||
|
|
||||||
import GameContext from "../contexts/GameContext";
|
import GameContext from "../contexts/GameContext";
|
||||||
|
|
||||||
@ -27,9 +27,9 @@ function Home() {
|
|||||||
justifyContent: "center"
|
justifyContent: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Heading sx={{ textAlign: "center", fontSize: "48px" }}>
|
<Text variant="display" sx={{ textAlign: "center" }}>
|
||||||
Owlbear Rodeo
|
Owlbear Rodeo
|
||||||
</Heading>
|
</Text>
|
||||||
<Image src={owlington} m={2} />
|
<Image src={owlington} m={2} />
|
||||||
<Button m={2} onClick={handleStartGame}>
|
<Button m={2} onClick={handleStartGame}>
|
||||||
Start Game
|
Start Game
|
||||||
|
@ -28,7 +28,8 @@ function Join() {
|
|||||||
<Box as="form" onSubmit={handleSubmit}>
|
<Box as="form" onSubmit={handleSubmit}>
|
||||||
<Label htmlFor="id">Let me see your identification</Label>
|
<Label htmlFor="id">Let me see your identification</Label>
|
||||||
<Input
|
<Input
|
||||||
my={4}
|
mt={1}
|
||||||
|
mb={3}
|
||||||
id="id"
|
id="id"
|
||||||
name="id"
|
name="id"
|
||||||
value={gameId || ""}
|
value={gameId || ""}
|
||||||
|
34
src/theme.js
34
src/theme.js
@ -7,35 +7,51 @@ export default {
|
|||||||
highlight: "hsl(260, 20%, 40%)",
|
highlight: "hsl(260, 20%, 40%)",
|
||||||
purple: "hsl(290, 100%, 80%)",
|
purple: "hsl(290, 100%, 80%)",
|
||||||
muted: "hsla(230, 20%, 0%, 20%)",
|
muted: "hsla(230, 20%, 0%, 20%)",
|
||||||
gray: "hsl(210, 50%, 60%)"
|
gray: "hsl(0, 0%, 70%)"
|
||||||
},
|
},
|
||||||
fonts: {
|
fonts: {
|
||||||
body: "'Bree Serif', serif",
|
body: "'Bree Serif', serif",
|
||||||
heading: "'Pacifico', cursive",
|
message:
|
||||||
monospace: "Menlo, monospace"
|
"system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif",
|
||||||
|
heading: "'Bree Serif', serif",
|
||||||
|
monospace: "Menlo, monospace",
|
||||||
|
display: "'Pacifico', cursive"
|
||||||
},
|
},
|
||||||
fontSizes: [12, 14, 16, 20, 24, 32, 48, 64, 72],
|
fontSizes: [12, 14, 16, 20, 24, 32, 48, 64, 72],
|
||||||
fontWeights: {
|
fontWeights: {
|
||||||
body: 400,
|
caption: 200,
|
||||||
|
body: 300,
|
||||||
heading: 400,
|
heading: 400,
|
||||||
display: 900
|
display: 400
|
||||||
},
|
},
|
||||||
lineHeights: {
|
lineHeights: {
|
||||||
body: 1.5,
|
body: 1.1,
|
||||||
heading: 1.25
|
heading: 1.25
|
||||||
},
|
},
|
||||||
textStyles: {
|
text: {
|
||||||
heading: {
|
heading: {
|
||||||
fontFamily: "heading",
|
fontFamily: "heading",
|
||||||
fontWeight: "heading",
|
fontWeight: "heading",
|
||||||
lineHeight: "heading"
|
lineHeight: "heading",
|
||||||
|
fontSize: 1
|
||||||
},
|
},
|
||||||
display: {
|
display: {
|
||||||
variant: "textStyles.heading",
|
variant: "textStyles.heading",
|
||||||
|
fontFamily: "display",
|
||||||
fontSize: [5, 6],
|
fontSize: [5, 6],
|
||||||
fontWeight: "display",
|
fontWeight: "display",
|
||||||
letterSpacing: "-0.03em",
|
|
||||||
mt: 3
|
mt: 3
|
||||||
|
},
|
||||||
|
caption: {
|
||||||
|
fontFamily: "message",
|
||||||
|
fontWeight: "caption",
|
||||||
|
fontSize: 10,
|
||||||
|
color: "gray"
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
fontFamily: "message",
|
||||||
|
fontSize: 1,
|
||||||
|
fontWeight: "body"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
styles: {
|
styles: {
|
||||||
|
@ -10824,6 +10824,11 @@ uuid@^3.0.1, uuid@^3.3.2:
|
|||||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
|
||||||
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
|
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
|
||||||
|
|
||||||
|
uuid@^7.0.2:
|
||||||
|
version "7.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.2.tgz#7ff5c203467e91f5e0d85cfcbaaf7d2ebbca9be6"
|
||||||
|
integrity sha512-vy9V/+pKG+5ZTYKf+VcphF5Oc6EFiu3W8Nv3P3zIh0EqVI80ZxOzuPfe9EHjkFNvf8+xuTHVeei4Drydlx4zjw==
|
||||||
|
|
||||||
v8-compile-cache@^2.0.3:
|
v8-compile-cache@^2.0.3:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e"
|
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e"
|
||||||
|
Loading…
Reference in New Issue
Block a user