Removed video and added text
This commit is contained in:
parent
30e957e937
commit
54c7100c2c
@ -16,7 +16,8 @@
|
||||
"react-modal": "^3.11.2",
|
||||
"react-scripts": "3.4.0",
|
||||
"shortid": "^2.2.15",
|
||||
"theme-ui": "^0.3.1"
|
||||
"theme-ui": "^0.3.1",
|
||||
"uuid": "^7.0.2"
|
||||
},
|
||||
"scripts": {
|
||||
"predeploy": "yarn build",
|
||||
|
@ -3,7 +3,7 @@ import { IconButton, Flex, Box, Label, Text } from "theme-ui";
|
||||
|
||||
import Modal from "./Modal";
|
||||
|
||||
function AddPartyMemberButton({ streamId }) {
|
||||
function AddPartyMemberButton({ peerId }) {
|
||||
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
|
||||
function openModal() {
|
||||
setIsAddModalOpen(true);
|
||||
@ -32,7 +32,7 @@ function AddPartyMemberButton({ streamId }) {
|
||||
<Box>
|
||||
<Label p={2}>Other people can join using your ID › ʕ•ᴥ•ʔ</Label>
|
||||
<Box p={2} bg="hsla(230, 20%, 0%, 20%)">
|
||||
<Text>{streamId}</Text>
|
||||
<Text>{peerId}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</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 GameViewSwitch from "./GameViewSwitch";
|
||||
import Message from "./Message";
|
||||
|
||||
import { getRandomMonster } from "../helpers/monsters";
|
||||
|
||||
function Party({ peerId, messages, onMessageSend }) {
|
||||
const [messageText, setMessageText] = useState("");
|
||||
const [nickname, setNickname] = useState(getRandomMonster());
|
||||
|
||||
function handleMessageSubmit(event) {
|
||||
event.preventDefault();
|
||||
if (!messageText || !peerId) {
|
||||
return;
|
||||
}
|
||||
const id = uuid();
|
||||
const time = Date.now();
|
||||
const message = {
|
||||
nickname,
|
||||
id,
|
||||
text: messageText,
|
||||
time
|
||||
};
|
||||
onMessageSend(message);
|
||||
setMessageText("");
|
||||
}
|
||||
|
||||
function Party({ streams, localStreamId, view, onViewChange }) {
|
||||
if (view === "social") {
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
alignItems: "center",
|
||||
overflowY: "auto"
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
p={3}
|
||||
bg="background"
|
||||
sx={{
|
||||
flexDirection: "row",
|
||||
width: "100%",
|
||||
flexWrap: "wrap",
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
{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",
|
||||
width: "256px",
|
||||
minWidth: "256px",
|
||||
overflowY: "auto",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
{Object.entries(streams).map(([id, stream]) => (
|
||||
<Box key={id} my={1}>
|
||||
<PartyVideo stream={stream} muted={id === localStreamId} />
|
||||
<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 sx={{ flexGrow: 1 }}>
|
||||
<AddPartyMemberButton streamId={localStreamId} />
|
||||
<Box as="form" onSubmit={handleMessageSubmit} sx={{ width: "100%" }}>
|
||||
<Input
|
||||
value={messageText}
|
||||
onChange={event => setMessageText(event.target.value)}
|
||||
p={1}
|
||||
disabled={!peerId}
|
||||
/>
|
||||
</Box>
|
||||
<Flex my={1}>
|
||||
<GameViewSwitch view={view} onViewChange={onViewChange} />
|
||||
<Text my={1} variant="caption">
|
||||
{nickname}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Party.defaultProps = { view: "social" };
|
||||
|
||||
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";
|
||||
|
||||
const getUserMedia =
|
||||
navigator.getUserMedia ||
|
||||
navigator.webkitGetUserMedia ||
|
||||
navigator.mozGetUserMedia;
|
||||
|
||||
function useSession(onConnectionOpen, onConnectionSync) {
|
||||
const [peerId, setPeerId] = useState(null);
|
||||
const [peer, setPeer] = useState(null);
|
||||
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) {
|
||||
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(() => {
|
||||
console.log("Creating 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(() => {
|
||||
function handleOpen(id) {
|
||||
console.log("Peer open", 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) {
|
||||
@ -90,7 +38,7 @@ function useSession(onConnectionOpen, onConnectionSync) {
|
||||
if (metadata.sync) {
|
||||
connection.send({
|
||||
id: "sync",
|
||||
data: Object.keys(connections)
|
||||
data: { connections: Object.keys(connections) }
|
||||
});
|
||||
if (onConnectionSync) {
|
||||
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) {
|
||||
console.error("Peer error", error);
|
||||
}
|
||||
@ -147,23 +76,13 @@ function useSession(onConnectionOpen, onConnectionSync) {
|
||||
|
||||
peer.on("open", handleOpen);
|
||||
peer.on("connection", handleConnection);
|
||||
peer.on("call", handleCall);
|
||||
peer.on("error", handleError);
|
||||
return () => {
|
||||
peer.removeListener("open", handleOpen);
|
||||
peer.removeListener("connection", handleConnection);
|
||||
peer.removeListener("call", handleCall);
|
||||
peer.removeListener("error", handleError);
|
||||
};
|
||||
}, [peer, peerId, connections, onConnectionOpen, onConnectionSync, streams]);
|
||||
|
||||
function call(connectionId) {
|
||||
console.log("Calling", connectionId);
|
||||
const call = peer.call(connectionId, streams[peerId]);
|
||||
call.on("stream", stream => {
|
||||
addStream(stream, connectionId);
|
||||
});
|
||||
}
|
||||
}, [peer, peerId, connections, onConnectionOpen, onConnectionSync]);
|
||||
|
||||
function connectTo(connectionId) {
|
||||
console.log("Connecting to", connectionId);
|
||||
@ -177,7 +96,8 @@ function useSession(onConnectionOpen, onConnectionSync) {
|
||||
connection.on("open", () => {
|
||||
connection.on("data", data => {
|
||||
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);
|
||||
if (connectionId === syncId || syncId in connections) {
|
||||
continue;
|
||||
@ -186,7 +106,6 @@ function useSession(onConnectionOpen, onConnectionSync) {
|
||||
metadata: { sync: false }
|
||||
});
|
||||
addConnection(syncConnection);
|
||||
call(syncId);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -194,10 +113,9 @@ function useSession(onConnectionOpen, onConnectionSync) {
|
||||
onConnectionOpen(connection);
|
||||
}
|
||||
});
|
||||
call(connectionId);
|
||||
}
|
||||
|
||||
return { peer, peerId, streams, connections, connectTo };
|
||||
return { peer, peerId, connections, connectTo };
|
||||
}
|
||||
|
||||
export default useSession;
|
||||
|
@ -20,21 +20,16 @@ function Game() {
|
||||
const { gameId } = useContext(GameContext);
|
||||
const handleConnectionOpenCallback = useCallback(handleConnectionOpen);
|
||||
const handleConnectionSyncCallback = useCallback(handleConnectionSync);
|
||||
const { peerId, connections, connectTo, streams } = useSession(
|
||||
const { peerId, connections, connectTo } = useSession(
|
||||
handleConnectionOpenCallback,
|
||||
handleConnectionSyncCallback
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
gameId !== null &&
|
||||
peerId !== null &&
|
||||
streams[peerId] &&
|
||||
!(gameId in connections)
|
||||
) {
|
||||
if (gameId !== null && peerId !== null && !(gameId in connections)) {
|
||||
connectTo(gameId);
|
||||
}
|
||||
}, [gameId, peerId, connectTo, streams, connections]);
|
||||
}, [gameId, peerId, connectTo, connections]);
|
||||
|
||||
const [mapSource, setMapSource] = useState(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) {
|
||||
connection.on("data", data => {
|
||||
if (data.id === "map") {
|
||||
@ -89,6 +96,12 @@ function Game() {
|
||||
omit(prevMapTokens, Object.keys(data.data))
|
||||
);
|
||||
}
|
||||
if (data.id === "message") {
|
||||
setMessages(prevMessages => ({
|
||||
...prevMessages,
|
||||
...data.data
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -97,22 +110,19 @@ function Game() {
|
||||
connection.send({ id: "map", data: mapDataRef.current });
|
||||
}
|
||||
connection.send({ id: "tokenEdit", data: mapTokens });
|
||||
connection.send({ id: "message", data: messages });
|
||||
}
|
||||
|
||||
const [gameView, setGameView] = useState("social");
|
||||
|
||||
return (
|
||||
<Flex sx={{ flexDirection: "column", height: "100vh" }}>
|
||||
<Flex
|
||||
sx={{ justifyContent: "space-between", flexGrow: 1, height: "100%" }}
|
||||
>
|
||||
<Party
|
||||
streams={streams}
|
||||
localStreamId={peerId}
|
||||
view={gameView}
|
||||
onViewChange={setGameView}
|
||||
peerId={peerId}
|
||||
messages={messages}
|
||||
onMessageSend={handleMessageSend}
|
||||
/>
|
||||
{gameView === "encounter" && (
|
||||
<Map
|
||||
mapSource={mapSource}
|
||||
mapData={mapDataRef.current}
|
||||
@ -121,19 +131,8 @@ function Game() {
|
||||
onMapTokenRemove={handleRemoveMapToken}
|
||||
onMapChanged={handleMapChanged}
|
||||
/>
|
||||
)}
|
||||
{gameView === "encounter" && (
|
||||
<Tokens onCreateMapToken={handleEditMapToken} />
|
||||
)}
|
||||
</Flex>
|
||||
<Flex
|
||||
sx={{
|
||||
position: "absolute",
|
||||
left: "50%",
|
||||
bottom: 0,
|
||||
transform: "translate(-50%, -50%)"
|
||||
}}
|
||||
></Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { useContext } from "react";
|
||||
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";
|
||||
|
||||
@ -27,9 +27,9 @@ function Home() {
|
||||
justifyContent: "center"
|
||||
}}
|
||||
>
|
||||
<Heading sx={{ textAlign: "center", fontSize: "48px" }}>
|
||||
<Text variant="display" sx={{ textAlign: "center" }}>
|
||||
Owlbear Rodeo
|
||||
</Heading>
|
||||
</Text>
|
||||
<Image src={owlington} m={2} />
|
||||
<Button m={2} onClick={handleStartGame}>
|
||||
Start Game
|
||||
|
@ -28,7 +28,8 @@ function Join() {
|
||||
<Box as="form" onSubmit={handleSubmit}>
|
||||
<Label htmlFor="id">Let me see your identification</Label>
|
||||
<Input
|
||||
my={4}
|
||||
mt={1}
|
||||
mb={3}
|
||||
id="id"
|
||||
name="id"
|
||||
value={gameId || ""}
|
||||
|
34
src/theme.js
34
src/theme.js
@ -7,35 +7,51 @@ export default {
|
||||
highlight: "hsl(260, 20%, 40%)",
|
||||
purple: "hsl(290, 100%, 80%)",
|
||||
muted: "hsla(230, 20%, 0%, 20%)",
|
||||
gray: "hsl(210, 50%, 60%)"
|
||||
gray: "hsl(0, 0%, 70%)"
|
||||
},
|
||||
fonts: {
|
||||
body: "'Bree Serif', serif",
|
||||
heading: "'Pacifico', cursive",
|
||||
monospace: "Menlo, monospace"
|
||||
message:
|
||||
"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],
|
||||
fontWeights: {
|
||||
body: 400,
|
||||
caption: 200,
|
||||
body: 300,
|
||||
heading: 400,
|
||||
display: 900
|
||||
display: 400
|
||||
},
|
||||
lineHeights: {
|
||||
body: 1.5,
|
||||
body: 1.1,
|
||||
heading: 1.25
|
||||
},
|
||||
textStyles: {
|
||||
text: {
|
||||
heading: {
|
||||
fontFamily: "heading",
|
||||
fontWeight: "heading",
|
||||
lineHeight: "heading"
|
||||
lineHeight: "heading",
|
||||
fontSize: 1
|
||||
},
|
||||
display: {
|
||||
variant: "textStyles.heading",
|
||||
fontFamily: "display",
|
||||
fontSize: [5, 6],
|
||||
fontWeight: "display",
|
||||
letterSpacing: "-0.03em",
|
||||
mt: 3
|
||||
},
|
||||
caption: {
|
||||
fontFamily: "message",
|
||||
fontWeight: "caption",
|
||||
fontSize: 10,
|
||||
color: "gray"
|
||||
},
|
||||
message: {
|
||||
fontFamily: "message",
|
||||
fontSize: 1,
|
||||
fontWeight: "body"
|
||||
}
|
||||
},
|
||||
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"
|
||||
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:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e"
|
||||
|
Loading…
Reference in New Issue
Block a user