Removed video and added text

This commit is contained in:
Mitchell McCaffrey 2020-03-26 12:24:52 +11:00
parent 30e957e937
commit 54c7100c2c
13 changed files with 469 additions and 338 deletions

View File

@ -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",

View File

@ -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>

View File

@ -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
View 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;

View File

@ -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;

View File

@ -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
View 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)];
}

View File

@ -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;

View File

@ -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>
);
}

View File

@ -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

View File

@ -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 || ""}

View File

@ -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: {

View File

@ -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"