Added all files successfully converted

This commit is contained in:
Nicola Thouliss 2021-06-03 15:31:18 +10:00
parent c590adf836
commit bfd0529207
58 changed files with 622 additions and 445 deletions

View File

@ -9,7 +9,7 @@
"@msgpack/msgpack": "^2.4.1", "@msgpack/msgpack": "^2.4.1",
"@sentry/react": "^6.2.2", "@sentry/react": "^6.2.2",
"@stripe/stripe-js": "^1.13.1", "@stripe/stripe-js": "^1.13.1",
"@tensorflow/tfjs": "^3.3.0", "@tensorflow/tfjs": "^3.6.0",
"@testing-library/jest-dom": "^5.11.9", "@testing-library/jest-dom": "^5.11.9",
"@testing-library/react": "^11.2.5", "@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.0.2", "@testing-library/user-event": "^13.0.2",
@ -87,7 +87,9 @@
"devDependencies": { "devDependencies": {
"@types/color": "^3.0.1", "@types/color": "^3.0.1",
"@types/deep-diff": "^1.0.0", "@types/deep-diff": "^1.0.0",
"@types/file-saver": "^2.0.2",
"@types/jest": "^26.0.23", "@types/jest": "^26.0.23",
"@types/lodash.clonedeep": "^4.5.6",
"@types/lodash.get": "^4.4.6", "@types/lodash.get": "^4.4.6",
"@types/node": "^15.6.0", "@types/node": "^15.6.0",
"@types/react": "^17.0.6", "@types/react": "^17.0.6",

View File

@ -23,7 +23,7 @@ import useSetting from "../../hooks/useSetting";
function DiceTrayOverlay({ function DiceTrayOverlay({
isOpen, isOpen,
shareDice, shareDice,
onShareDiceChage, onShareDiceChange,
diceRolls, diceRolls,
onDiceRollsChange, onDiceRollsChange,
}) { }) {
@ -345,7 +345,7 @@ function DiceTrayOverlay({
onDiceTraySizeChange={setDiceTraySize} onDiceTraySizeChange={setDiceTraySize}
diceTraySize={diceTraySize} diceTraySize={diceTraySize}
shareDice={shareDice} shareDice={shareDice}
onShareDiceChange={onShareDiceChage} onShareDiceChange={onShareDiceChange}
loading={isLoading} loading={isLoading}
/> />
{isLoading && ( {isLoading && (

View File

@ -29,15 +29,14 @@ import Session from "../../network/Session";
import { Grid } from "../../helpers/grid"; import { Grid } from "../../helpers/grid";
import { ImageFile } from "../../helpers/image"; import { ImageFile } from "../../helpers/image";
type Resolutions = Record<string, ImageFile> export type Resolutions = Record<string, ImageFile>
export type Map = { export type Map = {
id: string, id: string,
name: string, name: string,
owner: string, owner: string,
file: Uint8Array, file?: Uint8Array,
quality: string, quality?: string,
resolutions: Resolutions, resolutions?: Resolutions,
grid: Grid, grid: Grid,
group: string, group: string,
width: number, width: number,
@ -48,7 +47,7 @@ export type Map = {
created: number, created: number,
showGrid: boolean, showGrid: boolean,
snapToGrid: boolean, snapToGrid: boolean,
thumbnail: ImageFile, thumbnail?: ImageFile,
} }
export type Note = { export type Note = {
@ -92,7 +91,7 @@ export type MapState = {
tokens: Record<string, TokenState>, tokens: Record<string, TokenState>,
drawShapes: PathId | ShapeId, drawShapes: PathId | ShapeId,
fogShapes: Fog[], fogShapes: Fog[],
editFlags: string[], editFlags: ["drawing", "tokens", "notes", "fog"],
notes: Note[], notes: Note[],
mapId: string, mapId: string,
} }

View File

@ -1,4 +1,3 @@
import React from "react";
import { Box } from "theme-ui"; import { Box } from "theme-ui";
import { useMapLoading } from "../../contexts/MapLoadingContext"; import { useMapLoading } from "../../contexts/MapLoadingContext";
@ -8,8 +7,11 @@ import LoadingBar from "../LoadingBar";
function MapLoadingOverlay() { function MapLoadingOverlay() {
const { isLoading, loadingProgressRef } = useMapLoading(); const { isLoading, loadingProgressRef } = useMapLoading();
if (!isLoading) {
return null;
}
return ( return (
isLoading && (
<Box <Box
sx={{ sx={{
position: "absolute", position: "absolute",
@ -29,8 +31,7 @@ function MapLoadingOverlay() {
loadingProgressRef={loadingProgressRef} loadingProgressRef={loadingProgressRef}
/> />
</Box> </Box>
) );
);
} }
export default MapLoadingOverlay; export default MapLoadingOverlay;

View File

@ -4,7 +4,7 @@ import { IconButton } from "theme-ui";
import AddPartyMemberModal from "../../modals/AddPartyMemberModal"; import AddPartyMemberModal from "../../modals/AddPartyMemberModal";
import AddPartyMemberIcon from "../../icons/AddPartyMemberIcon"; import AddPartyMemberIcon from "../../icons/AddPartyMemberIcon";
function AddPartyMemberButton({ gameId }) { function AddPartyMemberButton({ gameId }: { gameId: string }) {
const [isAddModalOpen, setIsAddModalOpen] = useState(false); const [isAddModalOpen, setIsAddModalOpen] = useState(false);
function openModal() { function openModal() {
setIsAddModalOpen(true); setIsAddModalOpen(true);

View File

@ -1,10 +1,10 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect, ChangeEvent } from "react";
import { IconButton } from "theme-ui"; import { IconButton } from "theme-ui";
import ChangeNicknameModal from "../../modals/ChangeNicknameModal"; import ChangeNicknameModal from "../../modals/ChangeNicknameModal";
import ChangeNicknameIcon from "../../icons/ChangeNicknameIcon"; import ChangeNicknameIcon from "../../icons/ChangeNicknameIcon";
function ChangeNicknameButton({ nickname, onChange }) { function ChangeNicknameButton({ nickname, onChange }: { nickname: string, onChange: any}) {
const [isChangeModalOpen, setIsChangeModalOpen] = useState(false); const [isChangeModalOpen, setIsChangeModalOpen] = useState(false);
function openModal() { function openModal() {
setIsChangeModalOpen(true); setIsChangeModalOpen(true);
@ -19,14 +19,14 @@ function ChangeNicknameButton({ nickname, onChange }) {
setChangedNickname(nickname); setChangedNickname(nickname);
}, [nickname]); }, [nickname]);
function handleChangeSubmit(event) { function handleChangeSubmit(event: Event) {
event.preventDefault(); event.preventDefault();
onChange(changedNickname); onChange(changedNickname);
closeModal(); closeModal();
} }
function handleChange(event) { function handleChange(event: ChangeEvent<HTMLInputElement>) {
setChangedNickname(event.target.value); setChangedNickname(event.target?.value);
} }
return ( return (
<> <>

View File

@ -1,13 +1,12 @@
import React from "react";
import { Flex, Box, Text } from "theme-ui"; import { Flex, Box, Text } from "theme-ui";
function DiceRoll({ rolls, type, children }) { function DiceRoll({ rolls, type, children }: { rolls: any, type: string, children: any}) {
return ( return (
<Flex sx={{ flexWrap: "wrap" }}> <Flex sx={{ flexWrap: "wrap" }}>
<Box sx={{ transform: "scale(0.8)" }}>{children}</Box> <Box sx={{ transform: "scale(0.8)" }}>{children}</Box>
{rolls {rolls
.filter((d) => d.type === type && d.roll !== "unknown") .filter((d: any) => d.type === type && d.roll !== "unknown")
.map((dice, index) => ( .map((dice: any, index: string | number) => (
<Text as="p" my={1} variant="caption" mx={1} key={index}> <Text as="p" my={1} variant="caption" mx={1} key={index}>
{dice.roll} {dice.roll}
</Text> </Text>

View File

@ -1,4 +1,4 @@
import React, { useState } from "react"; import { useState } from "react";
import { Flex, Text, IconButton } from "theme-ui"; import { Flex, Text, IconButton } from "theme-ui";
import DiceRollsIcon from "../../icons/DiceRollsIcon"; import DiceRollsIcon from "../../icons/DiceRollsIcon";
@ -24,14 +24,14 @@ const diceIcons = [
{ type: "d100", Icon: D100Icon }, { type: "d100", Icon: D100Icon },
]; ];
function DiceRolls({ rolls }) { function DiceRolls({ rolls }: { rolls: any }) {
const total = getDiceRollTotal(rolls); const total = getDiceRollTotal(rolls);
const [expanded, setExpanded] = useState(false); const [expanded, setExpanded] = useState<boolean>(false);
let expandedRolls = []; let expandedRolls = [];
for (let icon of diceIcons) { for (let icon of diceIcons) {
if (rolls.some((roll) => roll.type === icon.type)) { if (rolls.some((roll: any) => roll.type === icon.type)) {
expandedRolls.push( expandedRolls.push(
<DiceRoll rolls={rolls} type={icon.type} key={icon.type}> <DiceRoll rolls={rolls} type={icon.type} key={icon.type}>
<icon.Icon /> <icon.Icon />
@ -40,8 +40,11 @@ function DiceRolls({ rolls }) {
} }
} }
if (total <= 0) {
return null;
}
return ( return (
total > 0 && (
<Flex sx={{ flexDirection: "column" }}> <Flex sx={{ flexDirection: "column" }}>
<Flex sx={{ alignItems: "center" }}> <Flex sx={{ alignItems: "center" }}>
<IconButton <IconButton
@ -65,7 +68,6 @@ function DiceRolls({ rolls }) {
</Flex> </Flex>
)} )}
</Flex> </Flex>
)
); );
} }

View File

@ -13,10 +13,10 @@ const DiceTrayOverlay = React.lazy(() => import("../dice/DiceTrayOverlay"));
function DiceTrayButton({ function DiceTrayButton({
shareDice, shareDice,
onShareDiceChage, onShareDiceChange,
diceRolls, diceRolls,
onDiceRollsChange, onDiceRollsChange,
}) { }: { shareDice: boolean, onShareDiceChange: any, diceRolls: [], onDiceRollsChange: any}) {
const [isExpanded, setIsExpanded] = useState(false); const [isExpanded, setIsExpanded] = useState(false);
const [fullScreen] = useSetting("map.fullScreen"); const [fullScreen] = useSetting("map.fullScreen");
@ -69,7 +69,7 @@ function DiceTrayButton({
<DiceTrayOverlay <DiceTrayOverlay
isOpen={isExpanded} isOpen={isExpanded}
shareDice={shareDice} shareDice={shareDice}
onShareDiceChage={onShareDiceChage} onShareDiceChange={onShareDiceChange}
diceRolls={diceRolls} diceRolls={diceRolls}
onDiceRollsChange={onDiceRollsChange} onDiceRollsChange={onDiceRollsChange}
/> />

View File

@ -1,10 +1,10 @@
import React from "react";
import { Text, Flex } from "theme-ui"; import { Text, Flex } from "theme-ui";
import Stream from "./Stream"; import Stream from "./Stream";
import DiceRolls from "./DiceRolls"; import DiceRolls from "./DiceRolls";
function Nickname({ nickname, stream, diceRolls }) { // TODO: check if stream is a required or optional param
function Nickname({ nickname, stream, diceRolls }: { nickname: string, stream?: any, diceRolls: any}) {
return ( return (
<Flex sx={{ flexDirection: "column" }}> <Flex sx={{ flexDirection: "column" }}>
<Text <Text

View File

@ -10,33 +10,34 @@ import SettingsButton from "../SettingsButton";
import StartTimerButton from "./StartTimerButton"; import StartTimerButton from "./StartTimerButton";
import Timer from "./Timer"; import Timer from "./Timer";
import DiceTrayButton from "./DiceTrayButton"; import DiceTrayButton from "./DiceTrayButton";
import { PartyState, PlayerDice, PlayerInfo, Timer as PartyTimer } from "./PartyState"
import useSetting from "../../hooks/useSetting"; import useSetting from "../../hooks/useSetting";
import { useParty } from "../../contexts/PartyContext"; import { useParty } from "../../contexts/PartyContext";
import { usePlayerState, usePlayerUpdater } from "../../contexts/PlayerContext"; import { usePlayerState, usePlayerUpdater } from "../../contexts/PlayerContext";
function Party({ gameId, stream, partyStreams, onStreamStart, onStreamEnd }) { function Party({ gameId, stream, partyStreams, onStreamStart, onStreamEnd }: { gameId: string, stream: any, partyStreams: any, onStreamStart: any, onStreamEnd: any}) {
const setPlayerState = usePlayerUpdater(); const setPlayerState = usePlayerUpdater();
const playerState = usePlayerState(); const playerState: PlayerInfo = usePlayerState();
const partyState = useParty(); const partyState: PartyState = useParty();
const [fullScreen] = useSetting("map.fullScreen"); const [fullScreen] = useSetting("map.fullScreen");
const [shareDice, setShareDice] = useSetting("dice.shareDice"); const [shareDice, setShareDice] = useSetting("dice.shareDice");
function handleTimerStart(newTimer) { function handleTimerStart(newTimer: PartyTimer) {
setPlayerState((prevState) => ({ ...prevState, timer: newTimer })); setPlayerState((prevState: any) => ({ ...prevState, timer: newTimer }));
} }
function handleTimerStop() { function handleTimerStop() {
setPlayerState((prevState) => ({ ...prevState, timer: null })); setPlayerState((prevState: any) => ({ ...prevState, timer: null }));
} }
useEffect(() => { useEffect(() => {
let prevTime = performance.now(); let prevTime = performance.now();
let request = requestAnimationFrame(update); let request = requestAnimationFrame(update);
let counter = 0; let counter = 0;
function update(time) { function update(time: any) {
request = requestAnimationFrame(update); request = requestAnimationFrame(update);
const deltaTime = time - prevTime; const deltaTime = time - prevTime;
prevTime = time; prevTime = time;
@ -45,14 +46,14 @@ function Party({ gameId, stream, partyStreams, onStreamStart, onStreamEnd }) {
counter += deltaTime; counter += deltaTime;
// Update timer every second // Update timer every second
if (counter > 1000) { if (counter > 1000) {
const newTimer = { const newTimer: PartyTimer = {
...playerState.timer, ...playerState.timer,
current: playerState.timer.current - counter, current: playerState.timer.current - counter,
}; };
if (newTimer.current < 0) { if (newTimer.current < 0) {
setPlayerState((prevState) => ({ ...prevState, timer: null })); setPlayerState((prevState: any) => ({ ...prevState, timer: null }));
} else { } else {
setPlayerState((prevState) => ({ ...prevState, timer: newTimer })); setPlayerState((prevState: any) => ({ ...prevState, timer: newTimer }));
} }
counter = 0; counter = 0;
} }
@ -63,13 +64,13 @@ function Party({ gameId, stream, partyStreams, onStreamStart, onStreamEnd }) {
}; };
}, [playerState.timer, setPlayerState]); }, [playerState.timer, setPlayerState]);
function handleNicknameChange(newNickname) { function handleNicknameChange(newNickname: string) {
setPlayerState((prevState) => ({ ...prevState, nickname: newNickname })); setPlayerState((prevState: any) => ({ ...prevState, nickname: newNickname }));
} }
function handleDiceRollsChange(newDiceRolls) { function handleDiceRollsChange(newDiceRolls: number[]) {
setPlayerState( setPlayerState(
(prevState) => ({ (prevState: PlayerDice) => ({
...prevState, ...prevState,
dice: { share: shareDice, rolls: newDiceRolls }, dice: { share: shareDice, rolls: newDiceRolls },
}), }),
@ -77,9 +78,9 @@ function Party({ gameId, stream, partyStreams, onStreamStart, onStreamEnd }) {
); );
} }
function handleShareDiceChange(newShareDice) { function handleShareDiceChange(newShareDice: boolean) {
setShareDice(newShareDice); setShareDice(newShareDice);
setPlayerState((prevState) => ({ setPlayerState((prevState: PlayerInfo) => ({
...prevState, ...prevState,
dice: { ...prevState.dice, share: newShareDice }, dice: { ...prevState.dice, share: newShareDice },
})); }));
@ -122,6 +123,7 @@ function Party({ gameId, stream, partyStreams, onStreamStart, onStreamEnd }) {
height: "calc(100% - 232px)", height: "calc(100% - 232px)",
}} }}
> >
{/* TODO: check if stream is required here */}
<Nickname <Nickname
nickname={`${playerState.nickname} (you)`} nickname={`${playerState.nickname} (you)`}
diceRolls={shareDice && playerState.dice.rolls} diceRolls={shareDice && playerState.dice.rolls}
@ -167,7 +169,7 @@ function Party({ gameId, stream, partyStreams, onStreamStart, onStreamEnd }) {
</Box> </Box>
<DiceTrayButton <DiceTrayButton
shareDice={shareDice} shareDice={shareDice}
onShareDiceChage={handleShareDiceChange} onShareDiceChange={handleShareDiceChange}
diceRolls={(playerState.dice && playerState.dice.rolls) || []} diceRolls={(playerState.dice && playerState.dice.rolls) || []}
onDiceRollsChange={handleDiceRollsChange} onDiceRollsChange={handleDiceRollsChange}
/> />

View File

@ -0,0 +1,39 @@
/**
* @typedef {object} Timer
* @property {number} current
* @property {number} max
*/
export type Timer = {
current: number,
max: number
}
/**
* @typedef {object} PlayerDice
* @property {boolean} share
* @property {[]} rolls
*/
export type PlayerDice = { share: boolean, rolls: [] }
/**
* @typedef {object} PlayerInfo
* @property {string} nickname
* @property {Timer | null} timer
* @property {PlayerDice} dice
* @property {string} sessionId
* @property {string} userId
*/
export type PlayerInfo = {
nickname: string,
timer: Timer | null,
dice: PlayerDice,
sessionId: string,
userId: string
}
/**
* @typedef {object} PartyState
* @property {string} player
* @property {PlayerInfo} playerInfo
*/
export type PartyState = { [player: string]: PlayerInfo }

View File

@ -6,7 +6,7 @@ import Link from "../Link";
import StartStreamModal from "../../modals/StartStreamModal"; import StartStreamModal from "../../modals/StartStreamModal";
function StartStreamButton({ onStreamStart, onStreamEnd, stream }) { function StartStreamButton({ onStreamStart, onStreamEnd, stream }: { onStreamStart: any, onStreamEnd: any, stream: any}) {
const [isStreamModalOpoen, setIsStreamModalOpen] = useState(false); const [isStreamModalOpoen, setIsStreamModalOpen] = useState(false);
function openModal() { function openModal() {
setIsStreamModalOpen(true); setIsStreamModalOpen(true);
@ -44,7 +44,9 @@ function StartStreamButton({ onStreamStart, onStreamEnd, stream }) {
const [noAudioTrack, setNoAudioTrack] = useState(false); const [noAudioTrack, setNoAudioTrack] = useState(false);
function handleStreamStart() { function handleStreamStart() {
navigator.mediaDevices // Must be defined this way in typescript due to open issue - https://github.com/microsoft/TypeScript/issues/33232
const mediaDevices = navigator.mediaDevices as any;
mediaDevices
.getDisplayMedia({ .getDisplayMedia({
video: true, video: true,
audio: { audio: {
@ -53,10 +55,10 @@ function StartStreamButton({ onStreamStart, onStreamEnd, stream }) {
echoCancellation: false, echoCancellation: false,
}, },
}) })
.then((localStream) => { .then((localStream: { getTracks: () => any; }) => {
const tracks = localStream.getTracks(); const tracks = localStream.getTracks();
const hasAudio = tracks.some((track) => track.kind === "audio"); const hasAudio = tracks.some((track: { kind: string; }) => track.kind === "audio");
setNoAudioTrack(!hasAudio); setNoAudioTrack(!hasAudio);
// Ensure an audio track is present // Ensure an audio track is present

View File

@ -4,7 +4,7 @@ import { IconButton } from "theme-ui";
import StartTimerModal from "../../modals/StartTimerModal"; import StartTimerModal from "../../modals/StartTimerModal";
import StartTimerIcon from "../../icons/StartTimerIcon"; import StartTimerIcon from "../../icons/StartTimerIcon";
function StartTimerButton({ onTimerStart, onTimerStop, timer }) { function StartTimerButton({ onTimerStart, onTimerStop, timer }: { onTimerStart: any, onTimerStop: any, timer: any }) {
const [isTimerModalOpen, setIsTimerModalOpen] = useState(false); const [isTimerModalOpen, setIsTimerModalOpen] = useState(false);
function openModal() { function openModal() {

View File

@ -1,4 +1,4 @@
import React, { useState, useRef, useEffect } from "react"; import React, { useState, useRef, useEffect, ChangeEvent } from "react";
import { Text, IconButton, Box, Flex } from "theme-ui"; import { Text, IconButton, Box, Flex } from "theme-ui";
import StreamMuteIcon from "../../icons/StreamMuteIcon"; import StreamMuteIcon from "../../icons/StreamMuteIcon";
@ -6,13 +6,13 @@ import StreamMuteIcon from "../../icons/StreamMuteIcon";
import Banner from "../banner/Banner"; import Banner from "../banner/Banner";
import Slider from "../Slider"; import Slider from "../Slider";
function Stream({ stream, nickname }) { function Stream({ stream, nickname }: { stream: MediaStream, nickname: string }) {
const [streamVolume, setStreamVolume] = useState(1); const [streamVolume, setStreamVolume] = useState(1);
const [showStreamInteractBanner, setShowStreamInteractBanner] = useState( const [showStreamInteractBanner, setShowStreamInteractBanner] = useState(
false false
); );
const [streamMuted, setStreamMuted] = useState(false); const [streamMuted, setStreamMuted] = useState(false);
const audioRef = useRef(); const audioRef = useRef<any>();
useEffect(() => { useEffect(() => {
if (audioRef.current) { if (audioRef.current) {
@ -44,8 +44,8 @@ function Stream({ stream, nickname }) {
} }
} }
function handleVolumeChange(event) { function handleVolumeChange(event: ChangeEvent<HTMLInputElement>) {
const volume = parseFloat(event.target.value); const volume = parseFloat(event.target?.value);
setStreamVolume(volume); setStreamVolume(volume);
} }
@ -63,7 +63,8 @@ function Stream({ stream, nickname }) {
setTimeout(() => { setTimeout(() => {
setIsVolumeControlAvailable(audio.volume === 0.5); setIsVolumeControlAvailable(audio.volume === 0.5);
audio.volume = prevVolume; audio.volume = prevVolume;
}, [100]); // TODO: check if this supposed to be a number or number[]
}, 100);
} }
audio.addEventListener("playing", checkVolumeControlAvailable); audio.addEventListener("playing", checkVolumeControlAvailable);
@ -74,10 +75,10 @@ function Stream({ stream, nickname }) {
}, []); }, []);
// Use an audio context gain node to control volume to go past 100% // Use an audio context gain node to control volume to go past 100%
const audioGainRef = useRef(); const audioGainRef = useRef<any>();
useEffect(() => { useEffect(() => {
let audioContext; let audioContext: AudioContext;
if (stream && !streamMuted && isVolumeControlAvailable) { if (stream && !streamMuted && isVolumeControlAvailable && audioGainRef) {
audioContext = new AudioContext(); audioContext = new AudioContext();
let source = audioContext.createMediaStreamSource(stream); let source = audioContext.createMediaStreamSource(stream);
let gainNode = audioContext.createGain(); let gainNode = audioContext.createGain();

View File

@ -4,8 +4,8 @@ import { Box, Progress } from "theme-ui";
import usePortal from "../../hooks/usePortal"; import usePortal from "../../hooks/usePortal";
function Timer({ timer, index }) { function Timer({ timer, index }: { timer: any, index: number}) {
const progressBarRef = useRef(); const progressBarRef = useRef<any>();
useEffect(() => { useEffect(() => {
if (progressBarRef.current && timer) { if (progressBarRef.current && timer) {
@ -16,7 +16,7 @@ function Timer({ timer, index }) {
useEffect(() => { useEffect(() => {
let request = requestAnimationFrame(animate); let request = requestAnimationFrame(animate);
let previousTime = performance.now(); let previousTime = performance.now();
function animate(time) { function animate(time: any) {
request = requestAnimationFrame(animate); request = requestAnimationFrame(animate);
const deltaTime = time - previousTime; const deltaTime = time - previousTime;
previousTime = time; previousTime = time;

View File

@ -1,13 +1,16 @@
import React, { useState, useEffect, useContext } from "react"; import React, { useState, useEffect, useContext, SetStateAction } from "react";
import shortid from "shortid"; import shortid from "shortid";
import { useDatabase } from "./DatabaseContext"; import { useDatabase } from "./DatabaseContext";
import FakeStorage from "../helpers/FakeStorage"; import FakeStorage from "../helpers/FakeStorage";
const AuthContext = React.createContext(); type AuthContext = { userId: string; password: string; setPassword: React.Dispatch<any>; }
let storage; // TODO: check what default value we want here
const AuthContext = React.createContext<AuthContext | undefined>(undefined);
let storage: Storage | FakeStorage;
try { try {
sessionStorage.setItem("__test", "__test"); sessionStorage.setItem("__test", "__test");
sessionStorage.removeItem("__test"); sessionStorage.removeItem("__test");
@ -17,28 +20,29 @@ try {
storage = new FakeStorage(); storage = new FakeStorage();
} }
export function AuthProvider({ children }) { export function AuthProvider({ children }: { children: any }) {
const { database, databaseStatus } = useDatabase(); const { database, databaseStatus } = useDatabase();
const [password, setPassword] = useState(storage.getItem("auth") || ""); const [password, setPassword] = useState<string>(storage.getItem("auth") || "");
useEffect(() => { useEffect(() => {
storage.setItem("auth", password); storage.setItem("auth", password);
}, [password]); }, [password]);
const [userId, setUserId] = useState(); // TODO: check pattern here -> undefined or empty default values
const [userId, setUserId]: [ userId: string, setUserId: React.Dispatch<SetStateAction<string>> ] = useState("");
useEffect(() => { useEffect(() => {
if (!database || databaseStatus === "loading") { if (!database || databaseStatus === "loading") {
return; return;
} }
async function loadUserId() { async function loadUserId() {
const storedUserId = await database.table("user").get("userId"); const storedUserId = await database?.table("user").get("userId");
if (storedUserId) { if (storedUserId) {
setUserId(storedUserId.value); setUserId(storedUserId.value);
} else { } else {
const id = shortid.generate(); const id = shortid.generate();
setUserId(id); setUserId(id);
database.table("user").add({ key: "userId", value: id }); database?.table("user").add({ key: "userId", value: id });
} }
} }

View File

@ -1,20 +1,25 @@
import React, { useState, useEffect, useContext } from "react"; import React, { useState, useEffect, useContext, SetStateAction } from "react";
import * as Comlink from "comlink"; import Comlink, { Remote } from "comlink";
import ErrorBanner from "../components/banner/ErrorBanner"; import ErrorBanner from "../components/banner/ErrorBanner";
import { getDatabase } from "../database"; import { getDatabase } from "../database";
//@ts-ignore
import DatabaseWorker from "worker-loader!../workers/DatabaseWorker"; // eslint-disable-line import/no-webpack-loader-syntax import DatabaseWorker from "worker-loader!../workers/DatabaseWorker"; // eslint-disable-line import/no-webpack-loader-syntax
import Dexie from "dexie";
const DatabaseContext = React.createContext(); type DatabaseContext = { database: Dexie | undefined; databaseStatus: any; databaseError: Error | undefined; worker: Remote<any>; }
// TODO: check what default we want here
const DatabaseContext = React.createContext< DatabaseContext | undefined>(undefined);
const worker = Comlink.wrap(new DatabaseWorker()); const worker = Comlink.wrap(new DatabaseWorker());
export function DatabaseProvider({ children }) { export function DatabaseProvider({ children }: { children: any}) {
const [database, setDatabase] = useState(); const [database, setDatabase]: [ database: Dexie | undefined, setDatabase: React.Dispatch<SetStateAction<Dexie | undefined>>] = useState();
const [databaseStatus, setDatabaseStatus] = useState("loading"); const [databaseStatus, setDatabaseStatus]: [ datebaseStatus: any, setDatabaseStatus: React.Dispatch<SetStateAction<string>>] = useState("loading");
const [databaseError, setDatabaseError] = useState(); const [databaseError, setDatabaseError]: [ databaseError: Error | undefined, setDatabaseError: React.Dispatch<SetStateAction<Error | undefined>>] = useState();
useEffect(() => { useEffect(() => {
// Create a test database and open it to see if indexedDB is enabled // Create a test database and open it to see if indexedDB is enabled
@ -43,7 +48,7 @@ export function DatabaseProvider({ children }) {
window.indexedDB.deleteDatabase("__test"); window.indexedDB.deleteDatabase("__test");
}; };
function handleDatabaseError(event) { function handleDatabaseError(event: any) {
event.preventDefault(); event.preventDefault();
if (event.reason?.message.startsWith("QuotaExceededError")) { if (event.reason?.message.startsWith("QuotaExceededError")) {
setDatabaseError({ setDatabaseError({
@ -77,14 +82,14 @@ export function DatabaseProvider({ children }) {
{children} {children}
<ErrorBanner <ErrorBanner
error={databaseError} error={databaseError}
onRequestClose={() => setDatabaseError()} onRequestClose={() => setDatabaseError(undefined)}
/> />
</> </>
</DatabaseContext.Provider> </DatabaseContext.Provider>
); );
} }
export function useDatabase() { export function useDatabase(): DatabaseContext {
const context = useContext(DatabaseContext); const context = useContext(DatabaseContext);
if (context === undefined) { if (context === undefined) {
throw new Error("useDatabase must be used within a DatabaseProvider"); throw new Error("useDatabase must be used within a DatabaseProvider");

View File

@ -1,8 +1,14 @@
import React, { useState, useContext } from "react"; import React, { useState, useContext, ReactChild } from "react";
const DiceLoadingContext = React.createContext(); type DiceLoadingContext = {
assetLoadStart: any,
assetLoadFinish: any,
isLoading: boolean,
}
export function DiceLoadingProvider({ children }) { const DiceLoadingContext = React.createContext<DiceLoadingContext | undefined>(undefined);
export function DiceLoadingProvider({ children }: { children: ReactChild }) {
const [loadingAssetCount, setLoadingAssetCount] = useState(0); const [loadingAssetCount, setLoadingAssetCount] = useState(0);
function assetLoadStart() { function assetLoadStart() {
@ -28,7 +34,7 @@ export function DiceLoadingProvider({ children }) {
); );
} }
export function useDiceLoading() { export function useDiceLoading(): DiceLoadingContext {
const context = useContext(DiceLoadingContext); const context = useContext(DiceLoadingContext);
if (context === undefined) { if (context === undefined) {
throw new Error("useDiceLoading must be used within a DiceLoadingProvider"); throw new Error("useDiceLoading must be used within a DiceLoadingProvider");

View File

@ -15,11 +15,20 @@ import { getGridPixelSize, getCellPixelSize, Grid } from "../helpers/grid";
* @property {number} gridStrokeWidth Stroke width of the grid in pixels * @property {number} gridStrokeWidth Stroke width of the grid in pixels
* @property {Vector2} gridCellPixelOffset Offset of the grid cells to convert the center position of hex cells to the top left * @property {Vector2} gridCellPixelOffset Offset of the grid cells to convert the center position of hex cells to the top left
*/ */
type GridContextValue = {
grid: Grid,
gridPixelSize: Size,
gridCellPixelSize: Size,
gridCellNormalizedSize: Size,
gridOffset: Vector2,
gridStrokeWidth: number,
gridCellPixelOffset: Vector2
}
/** /**
* @type {GridContextValue} * @type {GridContextValue}
*/ */
const defaultValue = { const defaultValue: GridContextValue = {
grid: { grid: {
size: new Vector2(0, 0), size: new Vector2(0, 0),
inset: { topLeft: new Vector2(0, 0), bottomRight: new Vector2(1, 1) }, inset: { topLeft: new Vector2(0, 0), bottomRight: new Vector2(1, 1) },
@ -57,11 +66,11 @@ export const GridCellPixelOffsetContext = React.createContext(
const defaultStrokeWidth = 1 / 10; const defaultStrokeWidth = 1 / 10;
export function GridProvider({ grid: inputGrid, width, height, children }) { export function GridProvider({ grid: inputGrid, width, height, children }: { grid: Required<Grid>, width: number, height: number, children: any }) {
let grid = inputGrid; let grid = inputGrid;
if (!grid?.size.x || !grid?.size.y) { if (!grid.size.x || !grid.size.y) {
grid = defaultValue.grid; grid = defaultValue.grid as Required<Grid>;
} }
const [gridPixelSize, setGridPixelSize] = useState( const [gridPixelSize, setGridPixelSize] = useState(

View File

@ -1,27 +1,28 @@
import React, { useContext, useState, useEffect } from "react"; import React, { useContext, useState, useEffect, ReactChild } from "react";
import { ImageFile } from "../helpers/image";
import { omit } from "../helpers/shared"; import { omit } from "../helpers/shared";
export const ImageSourcesStateContext = React.createContext(); export const ImageSourcesStateContext = React.createContext(undefined) as any;
export const ImageSourcesUpdaterContext = React.createContext(() => {}); export const ImageSourcesUpdaterContext = React.createContext(() => {}) as any;
/** /**
* Helper to manage sharing of custom image sources between uses of useImageSource * Helper to manage sharing of custom image sources between uses of useImageSource
*/ */
export function ImageSourcesProvider({ children }) { export function ImageSourcesProvider({ children }: { children: ReactChild }) {
const [imageSources, setImageSources] = useState({}); const [imageSources, setImageSources] = useState<any>({});
// Revoke url when no more references // Revoke url when no more references
useEffect(() => { useEffect(() => {
let sourcesToCleanup = []; let sourcesToCleanup: any = [];
for (let source of Object.values(imageSources)) { for (let source of Object.values(imageSources) as any) {
if (source.references <= 0) { if (source.references <= 0) {
URL.revokeObjectURL(source.url); URL.revokeObjectURL(source.url);
sourcesToCleanup.push(source.id); sourcesToCleanup.push(source.id);
} }
} }
if (sourcesToCleanup.length > 0) { if (sourcesToCleanup.length > 0) {
setImageSources((prevSources) => omit(prevSources, sourcesToCleanup)); setImageSources((prevSources: any) => omit(prevSources, sourcesToCleanup));
} }
}, [imageSources]); }, [imageSources]);
@ -37,7 +38,7 @@ export function ImageSourcesProvider({ children }) {
/** /**
* Get id from image data * Get id from image data
*/ */
function getImageFileId(data, thumbnail) { function getImageFileId(data: any, thumbnail: ImageFile) {
if (thumbnail) { if (thumbnail) {
return `${data.id}-thumbnail`; return `${data.id}-thumbnail`;
} }
@ -48,7 +49,7 @@ function getImageFileId(data, thumbnail) {
} else if (!data.file) { } else if (!data.file) {
// Fallback to the highest resolution // Fallback to the highest resolution
const resolutionArray = Object.keys(data.resolutions); const resolutionArray = Object.keys(data.resolutions);
const resolution = resolutionArray[resolutionArray.length - 1]; const resolution: any = resolutionArray[resolutionArray.length - 1];
return `${data.id}-${resolution.id}`; return `${data.id}-${resolution.id}`;
} }
} }
@ -58,14 +59,14 @@ function getImageFileId(data, thumbnail) {
/** /**
* Helper function to load either file or default image into a URL * Helper function to load either file or default image into a URL
*/ */
export function useImageSource(data, defaultSources, unknownSource, thumbnail) { export function useImageSource(data: any, defaultSources: string, unknownSource: string, thumbnail: ImageFile) {
const imageSources = useContext(ImageSourcesStateContext); const imageSources: any = useContext(ImageSourcesStateContext);
if (imageSources === undefined) { if (imageSources === undefined) {
throw new Error( throw new Error(
"useImageSource must be used within a ImageSourcesProvider" "useImageSource must be used within a ImageSourcesProvider"
); );
} }
const setImageSources = useContext(ImageSourcesUpdaterContext); const setImageSources: any = useContext(ImageSourcesUpdaterContext);
if (setImageSources === undefined) { if (setImageSources === undefined) {
throw new Error( throw new Error(
"useImageSource must be used within a ImageSourcesProvider" "useImageSource must be used within a ImageSourcesProvider"
@ -78,9 +79,9 @@ export function useImageSource(data, defaultSources, unknownSource, thumbnail) {
} }
const id = getImageFileId(data, thumbnail); const id = getImageFileId(data, thumbnail);
function updateImageSource(file) { function updateImageSource(file: File) {
if (file) { if (file) {
setImageSources((prevSources) => { setImageSources((prevSources: any) => {
if (id in prevSources) { if (id in prevSources) {
// Check if the image source is already added // Check if the image source is already added
return { return {
@ -124,7 +125,7 @@ export function useImageSource(data, defaultSources, unknownSource, thumbnail) {
return () => { return () => {
// Decrease references // Decrease references
setImageSources((prevSources) => { setImageSources((prevSources: any) => {
if (id in prevSources) { if (id in prevSources) {
return { return {
...prevSources, ...prevSources,

View File

@ -4,6 +4,7 @@ import React, {
useContext, useContext,
useCallback, useCallback,
useRef, useRef,
ReactChild,
} from "react"; } from "react";
import * as Comlink from "comlink"; import * as Comlink from "comlink";
import { decode, encode } from "@msgpack/msgpack"; import { decode, encode } from "@msgpack/msgpack";
@ -12,42 +13,66 @@ import { useAuth } from "./AuthContext";
import { useDatabase } from "./DatabaseContext"; import { useDatabase } from "./DatabaseContext";
import { maps as defaultMaps } from "../maps"; import { maps as defaultMaps } from "../maps";
import { Map, MapState, Note, TokenState } from "../components/map/Map";
import { Fog } from "../helpers/drawing";
const MapDataContext = React.createContext();
// TODO: fix differences in types between default maps and imported maps
type MapDataContext = {
maps: Array<Map>,
ownedMaps: Array<Map>
mapStates: MapState[],
addMap: (map: Map) => void,
removeMap: (id: string) => void,
removeMaps: (ids: string[]) => void,
resetMap: (id: string) => void,
updateMap: (id: string, update: Partial<Map>) => void,
updateMaps: (ids: string[], update: Partial<Map>) => void,
updateMapState: (id: string, update: Partial<MapState>) => void,
putMap: (map: Map) => void,
getMap: (id: string) => Map | undefined,
getMapFromDB: (id: string) => Promise<Map>,
mapsLoading: boolean,
getMapStateFromDB: (id: string) => Promise<MapState>,
}
const MapDataContext = React.createContext<MapDataContext | undefined>(undefined);
// Maximum number of maps to keep in the cache // Maximum number of maps to keep in the cache
const cachedMapMax = 15; const cachedMapMax = 15;
const defaultMapState = { const defaultMapState: MapState = {
tokens: {}, mapId: "",
drawShapes: {}, tokens: {} as Record<string, TokenState>,
fogShapes: {}, drawShapes: {} as any,
fogShapes: {} as Fog[],
// Flags to determine what other people can edit // Flags to determine what other people can edit
editFlags: ["drawing", "tokens", "notes"], editFlags: ["drawing", "tokens", "notes", "fog"],
notes: {}, notes: {} as Note[],
}; };
export function MapDataProvider({ children }) { export function MapDataProvider({ children }: { children: ReactChild }) {
const { database, databaseStatus, worker } = useDatabase(); const { database, databaseStatus, worker } = useDatabase();
const { userId } = useAuth(); const { userId } = useAuth();
const [maps, setMaps] = useState([]); const [maps, setMaps] = useState<Array<Map>>([]);
const [mapStates, setMapStates] = useState([]); const [mapStates, setMapStates] = useState<MapState[]>([]);
const [mapsLoading, setMapsLoading] = useState(true); const [mapsLoading, setMapsLoading] = useState<boolean>(true);
// Load maps from the database and ensure state is properly setup // Load maps from the database and ensure state is properly seup
useEffect(() => { useEffect(() => {
if (!userId || !database || databaseStatus === "loading") { if (!userId || !database || databaseStatus === "loading") {
return; return;
} }
async function getDefaultMaps() { async function getDefaultMaps(): Promise<Map[]> {
const defaultMapsWithIds = []; const defaultMapsWithIds: Array<Map> = [];
for (let i = 0; i < defaultMaps.length; i++) { for (let i = 0; i < defaultMaps.length; i++) {
const defaultMap = defaultMaps[i]; const defaultMap = defaultMaps[i];
const id = `__default-${defaultMap.name}`; const mapId = `__default-${defaultMap.name}`;
defaultMapsWithIds.push({ defaultMapsWithIds.push({
...defaultMap, ...defaultMap,
id, lastUsed: Date.now() + i,
id: mapId,
owner: userId, owner: userId,
// Emulate the time increasing to avoid sort errors // Emulate the time increasing to avoid sort errors
created: Date.now() + i, created: Date.now() + i,
@ -57,9 +82,9 @@ export function MapDataProvider({ children }) {
group: "default", group: "default",
}); });
// Add a state for the map if there isn't one already // Add a state for the map if there isn't one already
const state = await database.table("states").get(id); const state = await database?.table("states").get(mapId);
if (!state) { if (!state) {
await database.table("states").add({ ...defaultMapState, mapId: id }); await database?.table("states").add({ ...defaultMapState, mapId: mapId });
} }
} }
return defaultMapsWithIds; return defaultMapsWithIds;
@ -67,24 +92,24 @@ export function MapDataProvider({ children }) {
// Loads maps without the file data to save memory // Loads maps without the file data to save memory
async function loadMaps() { async function loadMaps() {
let storedMaps = []; let storedMaps: Map[] = [];
// Try to load maps with worker, fallback to database if failed // Try to load maps with worker, fallback to database if failed
const packedMaps = await worker.loadData("maps"); const packedMaps = await worker.loadData("maps");
// let packedMaps; // let packedMaps;
if (packedMaps) { if (packedMaps) {
storedMaps = decode(packedMaps); storedMaps = decode(packedMaps) as Map[];
} else { } else {
console.warn("Unable to load maps with worker, loading may be slow"); console.warn("Unable to load maps with worker, loading may be slow");
await database.table("maps").each((map) => { await database?.table("maps").each((map) => {
const { file, resolutions, ...rest } = map; const { file, resolutions, ...rest } = map;
storedMaps.push(rest); storedMaps.push(rest);
}); });
} }
const sortedMaps = storedMaps.sort((a, b) => b.created - a.created); const sortedMaps = storedMaps.sort((a, b) => b.created - a.created);
const defaultMapsWithIds = await getDefaultMaps(); const defaultMapsWithIds = await getDefaultMaps();
const allMaps = [...sortedMaps, ...defaultMapsWithIds]; const allMaps: Array<Map> = [...sortedMaps, ...defaultMapsWithIds];
setMaps(allMaps); setMaps(allMaps);
const storedStates = await database.table("states").toArray(); const storedStates = await database?.table("states").toArray() as MapState[];
setMapStates(storedStates); setMapStates(storedStates);
setMapsLoading(false); setMapsLoading(false);
} }
@ -103,7 +128,7 @@ export function MapDataProvider({ children }) {
const getMapFromDB = useCallback( const getMapFromDB = useCallback(
async (mapId) => { async (mapId) => {
let map = await database.table("maps").get(mapId); let map = await database?.table("maps").get(mapId) as Map;
return map; return map;
}, },
[database] [database]
@ -111,7 +136,7 @@ export function MapDataProvider({ children }) {
const getMapStateFromDB = useCallback( const getMapStateFromDB = useCallback(
async (mapId) => { async (mapId) => {
let mapState = await database.table("states").get(mapId); let mapState = await database?.table("states").get(mapId) as MapState;
return mapState; return mapState;
}, },
[database] [database]
@ -122,30 +147,26 @@ export function MapDataProvider({ children }) {
* Sorted by when they we're last used * Sorted by when they we're last used
*/ */
const updateCache = useCallback(async () => { const updateCache = useCallback(async () => {
const cachedMaps = await database const cachedMaps = await database?.table("maps").where("owner").notEqual(userId).sortBy("lastUsed") as Map[];
.table("maps")
.where("owner")
.notEqual(userId)
.sortBy("lastUsed");
if (cachedMaps.length > cachedMapMax) { if (cachedMaps.length > cachedMapMax) {
const cacheDeleteCount = cachedMaps.length - cachedMapMax; const cacheDeleteCount = cachedMaps.length - cachedMapMax;
const idsToDelete = cachedMaps const idsToDelete = cachedMaps
.slice(0, cacheDeleteCount) .slice(0, cacheDeleteCount)
.map((map) => map.id); .map((map: Map) => map.id);
database.table("maps").where("id").anyOf(idsToDelete).delete(); database?.table("maps").where("id").anyOf(idsToDelete).delete();
} }
}, [database, userId]); }, [database, userId]);
/** /**
* Adds a map to the database, also adds an assosiated state for that map * Adds a map to the database, also adds an assosiated state for that map
* @param {Object} map map to add * @param {Map} map map to add
*/ */
const addMap = useCallback( const addMap = useCallback(
async (map) => { async (map) => {
// Just update map database as react state will be updated with an Observable // Just update map database as react state will be updated with an Observable
const state = { ...defaultMapState, mapId: map.id }; const state = { ...defaultMapState, mapId: map.id };
await database.table("maps").add(map); await database?.table("maps").add(map);
await database.table("states").add(state); await database?.table("states").add(state);
if (map.owner !== userId) { if (map.owner !== userId) {
await updateCache(); await updateCache();
} }
@ -155,16 +176,16 @@ export function MapDataProvider({ children }) {
const removeMap = useCallback( const removeMap = useCallback(
async (id) => { async (id) => {
await database.table("maps").delete(id); await database?.table("maps").delete(id);
await database.table("states").delete(id); await database?.table("states").delete(id);
}, },
[database] [database]
); );
const removeMaps = useCallback( const removeMaps = useCallback(
async (ids) => { async (ids) => {
await database.table("maps").bulkDelete(ids); await database?.table("maps").bulkDelete(ids);
await database.table("states").bulkDelete(ids); await database?.table("states").bulkDelete(ids);
}, },
[database] [database]
); );
@ -172,7 +193,7 @@ export function MapDataProvider({ children }) {
const resetMap = useCallback( const resetMap = useCallback(
async (id) => { async (id) => {
const state = { ...defaultMapState, mapId: id }; const state = { ...defaultMapState, mapId: id };
await database.table("states").put(state); await database?.table("states").put(state);
return state; return state;
}, },
[database] [database]
@ -183,10 +204,10 @@ export function MapDataProvider({ children }) {
// fake-indexeddb throws an error when updating maps in production. // fake-indexeddb throws an error when updating maps in production.
// Catch that error and use put when it fails // Catch that error and use put when it fails
try { try {
await database.table("maps").update(id, update); await database?.table("maps").update(id, update);
} catch (error) { } catch (error) {
const map = (await getMapFromDB(id)) || {}; const map = (await getMapFromDB(id)) || {};
await database.table("maps").put({ ...map, id, ...update }); await database?.table("maps").put({ ...map, id, ...update });
} }
}, },
[database, getMapFromDB] [database, getMapFromDB]
@ -195,7 +216,7 @@ export function MapDataProvider({ children }) {
const updateMaps = useCallback( const updateMaps = useCallback(
async (ids, update) => { async (ids, update) => {
await Promise.all( await Promise.all(
ids.map((id) => database.table("maps").update(id, update)) ids.map((id: string) => database?.table("maps").update(id, update))
); );
}, },
[database] [database]
@ -203,7 +224,7 @@ export function MapDataProvider({ children }) {
const updateMapState = useCallback( const updateMapState = useCallback(
async (id, update) => { async (id, update) => {
await database.table("states").update(id, update); await database?.table("states").update(id, update);
}, },
[database] [database]
); );
@ -223,7 +244,7 @@ export function MapDataProvider({ children }) {
false false
); );
if (!success) { if (!success) {
await database.table("maps").put(map); await database?.table("maps").put(map);
} }
if (map.owner !== userId) { if (map.owner !== userId) {
await updateCache(); await updateCache();
@ -238,13 +259,13 @@ export function MapDataProvider({ children }) {
return; return;
} }
function handleMapChanges(changes) { function handleMapChanges(changes: any) {
for (let change of changes) { for (let change of changes) {
if (change.table === "maps") { if (change.table === "maps") {
if (change.type === 1) { if (change.type === 1) {
// Created // Created
const map = change.obj; const map: Map = change.obj;
const state = { ...defaultMapState, mapId: map.id }; const state: MapState = { ...defaultMapState, mapId: map.id };
setMaps((prevMaps) => [map, ...prevMaps]); setMaps((prevMaps) => [map, ...prevMaps]);
setMapStates((prevStates) => [state, ...prevStates]); setMapStates((prevStates) => [state, ...prevStates]);
} else if (change.type === 2) { } else if (change.type === 2) {

View File

@ -1,16 +1,16 @@
import React, { useContext } from "react"; import React, { ReactChild, useContext } from "react";
import useDebounce from "../hooks/useDebounce"; import useDebounce from "../hooks/useDebounce";
export const StageScaleContext = React.createContext(); export const StageScaleContext = React.createContext(undefined) as any;
export const DebouncedStageScaleContext = React.createContext(); export const DebouncedStageScaleContext = React.createContext(undefined) as any;
export const StageWidthContext = React.createContext(); export const StageWidthContext = React.createContext(undefined) as any;
export const StageHeightContext = React.createContext(); export const StageHeightContext = React.createContext(undefined) as any;
export const SetPreventMapInteractionContext = React.createContext(); export const SetPreventMapInteractionContext = React.createContext(undefined) as any;
export const MapWidthContext = React.createContext(); export const MapWidthContext = React.createContext(undefined) as any;
export const MapHeightContext = React.createContext(); export const MapHeightContext = React.createContext(undefined) as any;
export const InteractionEmitterContext = React.createContext(); export const InteractionEmitterContext = React.createContext(undefined) as any;
export function MapInteractionProvider({ value, children }) { export function MapInteractionProvider({ value, children }: { value: any, children: ReactChild[]}) {
const { const {
stageScale, stageScale,
stageWidth, stageWidth,

View File

@ -1,9 +1,9 @@
import React, { useState, useRef, useContext } from "react"; import React, { useState, useRef, useContext } from "react";
import { omit, isEmpty } from "../helpers/shared"; import { omit, isEmpty } from "../helpers/shared";
const MapLoadingContext = React.createContext(); const MapLoadingContext = React.createContext<any | undefined>(undefined);
export function MapLoadingProvider({ children }) { export function MapLoadingProvider({ children }: { children: any}) {
const [loadingAssetCount, setLoadingAssetCount] = useState(0); const [loadingAssetCount, setLoadingAssetCount] = useState(0);
function assetLoadStart() { function assetLoadStart() {
@ -14,9 +14,9 @@ export function MapLoadingProvider({ children }) {
setLoadingAssetCount((prevLoadingAssets) => prevLoadingAssets - 1); setLoadingAssetCount((prevLoadingAssets) => prevLoadingAssets - 1);
} }
const assetProgressRef = useRef({}); const assetProgressRef = useRef<any>({});
const loadingProgressRef = useRef(null); const loadingProgressRef = useRef<number | null>(null);
function assetProgressUpdate({ id, count, total }) { function assetProgressUpdate({ id, count, total }: { id: string, count: number, total: number }) {
if (count === total) { if (count === total) {
assetProgressRef.current = omit(assetProgressRef.current, [id]); assetProgressRef.current = omit(assetProgressRef.current, [id]);
} else { } else {
@ -28,7 +28,7 @@ export function MapLoadingProvider({ children }) {
if (!isEmpty(assetProgressRef.current)) { if (!isEmpty(assetProgressRef.current)) {
let total = 0; let total = 0;
let count = 0; let count = 0;
for (let progress of Object.values(assetProgressRef.current)) { for (let progress of Object.values(assetProgressRef.current) as any) {
total += progress.total; total += progress.total;
count += progress.count; count += progress.count;
} }

View File

@ -3,7 +3,7 @@ import React, { useContext } from "react";
const MapStageContext = React.createContext({ const MapStageContext = React.createContext({
mapStageRef: { current: null }, mapStageRef: { current: null },
}); });
export const MapStageProvider = MapStageContext.Provider; export const MapStageProvider: any = MapStageContext.Provider;
export function useMapStage() { export function useMapStage() {
const context = useContext(MapStageContext); const context = useContext(MapStageContext);

View File

@ -1,12 +1,14 @@
import React, { useState, useEffect, useContext } from "react"; import React, { useState, useEffect, useContext } from "react";
import { PartyState } from "../components/party/PartyState";
import Session from "../network/Session";
const PartyContext = React.createContext(); const PartyContext = React.createContext<PartyState | undefined>(undefined);
export function PartyProvider({ session, children }) { export function PartyProvider({ session, children }: { session: Session, children: any}) {
const [partyState, setPartyState] = useState({}); const [partyState, setPartyState] = useState({});
useEffect(() => { useEffect(() => {
function handleSocketPartyState(partyState) { function handleSocketPartyState(partyState: PartyState) {
if (partyState) { if (partyState) {
const { [session.id]: _, ...otherMembersState } = partyState; const { [session.id]: _, ...otherMembersState } = partyState;
setPartyState(otherMembersState); setPartyState(otherMembersState);

View File

@ -6,11 +6,13 @@ import { useAuth } from "./AuthContext";
import { getRandomMonster } from "../helpers/monsters"; import { getRandomMonster } from "../helpers/monsters";
import useNetworkedState from "../hooks/useNetworkedState"; import useNetworkedState from "../hooks/useNetworkedState";
import Session from "../network/Session";
import { PlayerInfo } from "../components/party/PartyState";
export const PlayerStateContext = React.createContext(); export const PlayerStateContext = React.createContext<any>(undefined);
export const PlayerUpdaterContext = React.createContext(() => {}); export const PlayerUpdaterContext = React.createContext<any>(() => {});
export function PlayerProvider({ session, children }) { export function PlayerProvider({ session, children }: { session: Session, children: any}) {
const { userId } = useAuth(); const { userId } = useAuth();
const { database, databaseStatus } = useDatabase(); const { database, databaseStatus } = useDatabase();
@ -33,16 +35,16 @@ export function PlayerProvider({ session, children }) {
return; return;
} }
async function loadNickname() { async function loadNickname() {
const storedNickname = await database.table("user").get("nickname"); const storedNickname = await database?.table("user").get("nickname");
if (storedNickname !== undefined) { if (storedNickname !== undefined) {
setPlayerState((prevState) => ({ setPlayerState((prevState: PlayerInfo) => ({
...prevState, ...prevState,
nickname: storedNickname.value, nickname: storedNickname.value,
})); }));
} else { } else {
const name = getRandomMonster(); const name = getRandomMonster();
setPlayerState((prevState) => ({ ...prevState, nickname: name })); setPlayerState((prevState: any) => ({ ...prevState, nickname: name }));
database.table("user").add({ key: "nickname", value: name }); database?.table("user").add({ key: "nickname", value: name });
} }
} }
@ -63,7 +65,7 @@ export function PlayerProvider({ session, children }) {
useEffect(() => { useEffect(() => {
if (userId) { if (userId) {
setPlayerState((prevState) => { setPlayerState((prevState: PlayerInfo) => {
if (prevState) { if (prevState) {
return { return {
...prevState, ...prevState,
@ -77,7 +79,8 @@ export function PlayerProvider({ session, children }) {
useEffect(() => { useEffect(() => {
function updateSessionId() { function updateSessionId() {
setPlayerState((prevState) => { setPlayerState((prevState: PlayerInfo) => {
// TODO: check useNetworkState requirements here
if (prevState) { if (prevState) {
return { return {
...prevState, ...prevState,
@ -92,7 +95,7 @@ export function PlayerProvider({ session, children }) {
updateSessionId(); updateSessionId();
} }
function handleSocketStatus(status) { function handleSocketStatus(status: string) {
if (status === "joined") { if (status === "joined") {
updateSessionId(); updateSessionId();
} }

View File

@ -9,14 +9,14 @@ const SettingsContext = React.createContext({
const settingsProvider = getSettings(); const settingsProvider = getSettings();
export function SettingsProvider({ children }) { export function SettingsProvider({ children }: { children: any }) {
const [settings, setSettings] = useState(settingsProvider.getAll()); const [settings, setSettings] = useState(settingsProvider.getAll());
useEffect(() => { useEffect(() => {
settingsProvider.setAll(settings); settingsProvider.setAll(settings);
}, [settings]); }, [settings]);
const value = { const value: { settings: any, setSettings: any} = {
settings, settings,
setSettings, setSettings,
}; };

View File

@ -1,7 +1,7 @@
import { Vector3 } from "@babylonjs/core/Maths/math"; import { Vector3 } from "@babylonjs/core/Maths/math";
import { SceneLoader } from "@babylonjs/core/Loading/sceneLoader"; import { SceneLoader } from "@babylonjs/core/Loading/sceneLoader";
import { PBRMaterial } from "@babylonjs/core/Materials/PBR/pbrMaterial"; import { PBRMaterial } from "@babylonjs/core/Materials/PBR/pbrMaterial";
import { PhysicsImpostor } from "@babylonjs/core/Physics/physicsImpostor"; import { PhysicsImpostor, PhysicsImpostorParameters } from "@babylonjs/core/Physics/physicsImpostor";
import d4Source from "./shared/d4.glb"; import d4Source from "./shared/d4.glb";
import d6Source from "./shared/d6.glb"; import d6Source from "./shared/d6.glb";
@ -13,6 +13,7 @@ import d100Source from "./shared/d100.glb";
import { lerp } from "../helpers/shared"; import { lerp } from "../helpers/shared";
import { importTextureAsync } from "../helpers/babylon"; import { importTextureAsync } from "../helpers/babylon";
import { BaseTexture, InstancedMesh, Material, Mesh, Scene, Texture } from "@babylonjs/core";
const minDiceRollSpeed = 600; const minDiceRollSpeed = 600;
const maxDiceRollSpeed = 800; const maxDiceRollSpeed = 800;
@ -20,10 +21,10 @@ const maxDiceRollSpeed = 800;
class Dice { class Dice {
static instanceCount = 0; static instanceCount = 0;
static async loadMeshes(material, scene, sourceOverrides) { static async loadMeshes(material: Material, scene: Scene, sourceOverrides?: any): Promise<Record<string, Mesh>> {
let meshes = {}; let meshes: any = {};
const addToMeshes = async (type, defaultSource) => { const addToMeshes = async (type: string | number, defaultSource: any) => {
let source = sourceOverrides ? sourceOverrides[type] : defaultSource; let source: string = sourceOverrides ? sourceOverrides[type] : defaultSource;
const mesh = await this.loadMesh(source, material, scene); const mesh = await this.loadMesh(source, material, scene);
meshes[type] = mesh; meshes[type] = mesh;
}; };
@ -39,7 +40,7 @@ class Dice {
return meshes; return meshes;
} }
static async loadMesh(source, material, scene) { static async loadMesh(source: string, material: Material, scene: Scene) {
let mesh = (await SceneLoader.ImportMeshAsync("", source, "", scene)) let mesh = (await SceneLoader.ImportMeshAsync("", source, "", scene))
.meshes[1]; .meshes[1];
mesh.setParent(null); mesh.setParent(null);
@ -51,15 +52,16 @@ class Dice {
return mesh; return mesh;
} }
static async loadMaterial(materialName, textures, scene) { static async loadMaterial(materialName: string, textures: any, scene: Scene) {
let pbr = new PBRMaterial(materialName, scene); let pbr = new PBRMaterial(materialName, scene);
let [albedo, normal, metalRoughness] = await Promise.all([ let [albedo, normal, metalRoughness]: [albedo: BaseTexture, normal: Texture, metalRoughness: Texture] = await Promise.all([
importTextureAsync(textures.albedo), importTextureAsync(textures.albedo),
importTextureAsync(textures.normal), importTextureAsync(textures.normal),
importTextureAsync(textures.metalRoughness), importTextureAsync(textures.metalRoughness),
]); ]);
pbr.albedoTexture = albedo; pbr.albedoTexture = albedo;
pbr.normalTexture = normal; // pbr.normalTexture = normal;
pbr.bumpTexture = normal;
pbr.metallicTexture = metalRoughness; pbr.metallicTexture = metalRoughness;
pbr.useRoughnessFromMetallicTextureAlpha = false; pbr.useRoughnessFromMetallicTextureAlpha = false;
pbr.useRoughnessFromMetallicTextureGreen = true; pbr.useRoughnessFromMetallicTextureGreen = true;
@ -67,11 +69,16 @@ class Dice {
return pbr; return pbr;
} }
static createInstanceFromMesh(mesh, name, physicalProperties, scene) { static createInstanceFromMesh(mesh: Mesh, name: string, physicalProperties: PhysicsImpostorParameters, scene: Scene) {
let instance = mesh.createInstance(name); let instance = mesh.createInstance(name);
instance.position = mesh.position; instance.position = mesh.position;
for (let child of mesh.getChildTransformNodes()) { for (let child of mesh.getChildTransformNodes()) {
const locator = child.clone(); // TODO: type correctly another time -> should not be any
const locator: any = child.clone(child.name, instance);
// TODO: handle possible null value
if (!locator) {
throw Error
}
locator.setAbsolutePosition(child.getAbsolutePosition()); locator.setAbsolutePosition(child.getAbsolutePosition());
locator.name = child.name; locator.name = child.name;
instance.addChild(locator); instance.addChild(locator);
@ -87,7 +94,7 @@ class Dice {
return instance; return instance;
} }
static getDicePhysicalProperties(diceType) { static getDicePhysicalProperties(diceType: string) {
switch (diceType) { switch (diceType) {
case "d4": case "d4":
return { mass: 4, friction: 4 }; return { mass: 4, friction: 4 };
@ -107,17 +114,18 @@ class Dice {
} }
} }
static roll(instance) { static roll(instance: Mesh) {
instance.physicsImpostor.setLinearVelocity(Vector3.Zero()); instance.physicsImpostor?.setLinearVelocity(Vector3.Zero());
instance.physicsImpostor.setAngularVelocity(Vector3.Zero()); instance.physicsImpostor?.setAngularVelocity(Vector3.Zero());
const scene = instance.getScene(); const scene = instance.getScene();
const diceTraySingle = scene.getNodeByID("dice_tray_single"); // TODO: remove any typing in this function -> this is just to get it working
const diceTraySingle: any = scene.getNodeByID("dice_tray_single");
const diceTrayDouble = scene.getNodeByID("dice_tray_double"); const diceTrayDouble = scene.getNodeByID("dice_tray_double");
const visibleDiceTray = diceTraySingle.isVisible const visibleDiceTray: any = diceTraySingle?.isVisible
? diceTraySingle ? diceTraySingle
: diceTrayDouble; : diceTrayDouble;
const trayBounds = visibleDiceTray.getBoundingInfo().boundingBox; const trayBounds = visibleDiceTray?.getBoundingInfo().boundingBox;
const position = new Vector3( const position = new Vector3(
trayBounds.center.x + (Math.random() * 2 - 1), trayBounds.center.x + (Math.random() * 2 - 1),
@ -142,13 +150,13 @@ class Dice {
.normalizeToNew() .normalizeToNew()
.scale(lerp(minDiceRollSpeed, maxDiceRollSpeed, Math.random())); .scale(lerp(minDiceRollSpeed, maxDiceRollSpeed, Math.random()));
instance.physicsImpostor.applyImpulse( instance.physicsImpostor?.applyImpulse(
impulse, impulse,
instance.physicsImpostor.getObjectCenter() instance.physicsImpostor.getObjectCenter()
); );
} }
static createInstance(mesh, physicalProperties, scene) { static createInstanceMesh(mesh: Mesh, physicalProperties: PhysicsImpostorParameters, scene: Scene): InstancedMesh {
this.instanceCount++; this.instanceCount++;
return this.createInstanceFromMesh( return this.createInstanceFromMesh(

View File

@ -3,7 +3,9 @@ import { PBRMaterial } from "@babylonjs/core/Materials/PBR/pbrMaterial";
import { PhysicsImpostor } from "@babylonjs/core/Physics/physicsImpostor"; import { PhysicsImpostor } from "@babylonjs/core/Physics/physicsImpostor";
import { Mesh } from "@babylonjs/core/Meshes/mesh"; import { Mesh } from "@babylonjs/core/Meshes/mesh";
//@ts-ignore
import singleMeshSource from "./single.glb"; import singleMeshSource from "./single.glb";
//@ts-ignore
import doubleMeshSource from "./double.glb"; import doubleMeshSource from "./double.glb";
import singleAlbedo from "./singleAlbedo.jpg"; import singleAlbedo from "./singleAlbedo.jpg";
@ -15,12 +17,15 @@ import doubleMetalRoughness from "./doubleMetalRoughness.jpg";
import doubleNormal from "./doubleNormal.jpg"; import doubleNormal from "./doubleNormal.jpg";
import { importTextureAsync } from "../../helpers/babylon"; import { importTextureAsync } from "../../helpers/babylon";
import { Scene, ShadowGenerator, Texture } from "@babylonjs/core";
class DiceTray { class DiceTray {
_size; _size;
get size() { get size() {
return this._size; return this._size;
} }
set size(newSize) { set size(newSize) {
this._size = newSize; this._size = newSize;
const wallOffsetWidth = this.collisionSize / 2 + this.width / 2 - 0.5; const wallOffsetWidth = this.collisionSize / 2 + this.width / 2 - 0.5;
@ -32,21 +37,24 @@ class DiceTray {
this.singleMesh.isVisible = newSize === "single"; this.singleMesh.isVisible = newSize === "single";
this.doubleMesh.isVisible = newSize === "double"; this.doubleMesh.isVisible = newSize === "double";
} }
scene; scene;
shadowGenerator; shadowGenerator;
get width() { get width() {
return this.size === "single" ? 10 : 20; return this.size === "single" ? 10 : 20;
} }
height = 20; height = 20;
collisionSize = 50; collisionSize = 50;
wallTop; wallTop: any;
wallRight; wallRight: any;
wallBottom; wallBottom: any;
wallLeft; wallLeft: any;
singleMesh; singleMesh: any;
doubleMesh; doubleMesh: any;
constructor(initialSize, scene, shadowGenerator) { constructor(initialSize: string, scene: Scene, shadowGenerator: ShadowGenerator) {
this._size = initialSize; this._size = initialSize;
this.scene = scene; this.scene = scene;
this.shadowGenerator = shadowGenerator; this.shadowGenerator = shadowGenerator;
@ -57,7 +65,7 @@ class DiceTray {
await this.loadMeshes(); await this.loadMeshes();
} }
createCollision(name, x, y, z, friction) { createCollision(name: string, x: number, y: number, z: number, friction: number) {
let collision = Mesh.CreateBox( let collision = Mesh.CreateBox(
name, name,
this.collisionSize, this.collisionSize,
@ -126,6 +134,15 @@ class DiceTray {
doubleAlbedoTexture, doubleAlbedoTexture,
doubleNormalTexture, doubleNormalTexture,
doubleMetalRoughnessTexture, doubleMetalRoughnessTexture,
]: [
singleMeshes: any,
doubleMeshes: any,
singleAlbedoTexture: Texture,
singleNormalTexture: Texture,
singleMetalRoughnessTexture: Texture,
doubleAlbedoTexture: Texture,
doubleNormalTexture: Texture,
doubleMetalRoughnessTexture: Texture
] = await Promise.all([ ] = await Promise.all([
SceneLoader.ImportMeshAsync("", singleMeshSource, "", this.scene), SceneLoader.ImportMeshAsync("", singleMeshSource, "", this.scene),
SceneLoader.ImportMeshAsync("", doubleMeshSource, "", this.scene), SceneLoader.ImportMeshAsync("", doubleMeshSource, "", this.scene),
@ -142,7 +159,9 @@ class DiceTray {
this.singleMesh.name = "dice_tray"; this.singleMesh.name = "dice_tray";
let singleMaterial = new PBRMaterial("dice_tray_mat_single", this.scene); let singleMaterial = new PBRMaterial("dice_tray_mat_single", this.scene);
singleMaterial.albedoTexture = singleAlbedoTexture; singleMaterial.albedoTexture = singleAlbedoTexture;
singleMaterial.normalTexture = singleNormalTexture; // TODO: ask Mitch about texture
// singleMaterial.normalTexture = singleNormalTexture;
singleMaterial.bumpTexture = singleNormalTexture;
singleMaterial.metallicTexture = singleMetalRoughnessTexture; singleMaterial.metallicTexture = singleMetalRoughnessTexture;
singleMaterial.useRoughnessFromMetallicTextureAlpha = false; singleMaterial.useRoughnessFromMetallicTextureAlpha = false;
singleMaterial.useRoughnessFromMetallicTextureGreen = true; singleMaterial.useRoughnessFromMetallicTextureGreen = true;
@ -158,7 +177,9 @@ class DiceTray {
this.doubleMesh.name = "dice_tray"; this.doubleMesh.name = "dice_tray";
let doubleMaterial = new PBRMaterial("dice_tray_mat_double", this.scene); let doubleMaterial = new PBRMaterial("dice_tray_mat_double", this.scene);
doubleMaterial.albedoTexture = doubleAlbedoTexture; doubleMaterial.albedoTexture = doubleAlbedoTexture;
doubleMaterial.normalTexture = doubleNormalTexture; // TODO: ask Mitch about texture
//doubleMaterial.normalTexture = doubleNormalTexture;
doubleMaterial.bumpTexture = doubleNormalTexture;
doubleMaterial.metallicTexture = doubleMetalRoughnessTexture; doubleMaterial.metallicTexture = doubleMetalRoughnessTexture;
doubleMaterial.useRoughnessFromMetallicTextureAlpha = false; doubleMaterial.useRoughnessFromMetallicTextureAlpha = false;
doubleMaterial.useRoughnessFromMetallicTextureGreen = true; doubleMaterial.useRoughnessFromMetallicTextureGreen = true;

View File

@ -1,3 +1,4 @@
import { InstancedMesh, Material, Mesh, Scene } from "@babylonjs/core";
import Dice from "../Dice"; import Dice from "../Dice";
import albedo from "./albedo.jpg"; import albedo from "./albedo.jpg";
@ -5,10 +6,10 @@ import metalRoughness from "./metalRoughness.jpg";
import normal from "./normal.jpg"; import normal from "./normal.jpg";
class GalaxyDice extends Dice { class GalaxyDice extends Dice {
static meshes; static meshes: Record<string, Mesh>;
static material; static material: Material;
static async load(scene) { static async load(scene: Scene) {
if (!this.material) { if (!this.material) {
this.material = await this.loadMaterial( this.material = await this.loadMaterial(
"galaxy_pbr", "galaxy_pbr",
@ -21,12 +22,13 @@ class GalaxyDice extends Dice {
} }
} }
static createInstance(diceType, scene) { // TODO: check static -> rename function?
static createInstance(diceType: string, scene: Scene): InstancedMesh {
if (!this.material || !this.meshes) { if (!this.material || !this.meshes) {
throw Error("Dice not loaded, call load before creating an instance"); throw Error("Dice not loaded, call load before creating an instance");
} }
return Dice.createInstance( return super.createInstanceMesh(
this.meshes[diceType], this.meshes[diceType],
this.getDicePhysicalProperties(diceType), this.getDicePhysicalProperties(diceType),
scene scene

View File

@ -8,17 +8,18 @@ import metalRoughness from "./metalRoughness.jpg";
import normal from "./normal.jpg"; import normal from "./normal.jpg";
import { importTextureAsync } from "../../helpers/babylon"; import { importTextureAsync } from "../../helpers/babylon";
import { Material, Mesh, Scene } from "@babylonjs/core";
class GemstoneDice extends Dice { class GemstoneDice extends Dice {
static meshes; static meshes: Record<string, Mesh>;
static material; static material: Material;
static getDicePhysicalProperties(diceType) { static getDicePhysicalProperties(diceType: string) {
let properties = super.getDicePhysicalProperties(diceType); let properties = super.getDicePhysicalProperties(diceType);
return { mass: properties.mass * 1.5, friction: properties.friction }; return { mass: properties.mass * 1.5, friction: properties.friction };
} }
static async loadMaterial(materialName, textures, scene) { static async loadMaterial(materialName: string, textures: any, scene: Scene) {
let pbr = new PBRMaterial(materialName, scene); let pbr = new PBRMaterial(materialName, scene);
let [albedo, normal, metalRoughness] = await Promise.all([ let [albedo, normal, metalRoughness] = await Promise.all([
importTextureAsync(textures.albedo), importTextureAsync(textures.albedo),
@ -26,7 +27,8 @@ class GemstoneDice extends Dice {
importTextureAsync(textures.metalRoughness), importTextureAsync(textures.metalRoughness),
]); ]);
pbr.albedoTexture = albedo; pbr.albedoTexture = albedo;
pbr.normalTexture = normal; // TODO: ask Mitch about texture
pbr.bumpTexture = normal;
pbr.metallicTexture = metalRoughness; pbr.metallicTexture = metalRoughness;
pbr.useRoughnessFromMetallicTextureAlpha = false; pbr.useRoughnessFromMetallicTextureAlpha = false;
pbr.useRoughnessFromMetallicTextureGreen = true; pbr.useRoughnessFromMetallicTextureGreen = true;
@ -41,7 +43,7 @@ class GemstoneDice extends Dice {
return pbr; return pbr;
} }
static async load(scene) { static async load(scene: Scene) {
if (!this.material) { if (!this.material) {
this.material = await this.loadMaterial( this.material = await this.loadMaterial(
"gemstone_pbr", "gemstone_pbr",
@ -54,12 +56,12 @@ class GemstoneDice extends Dice {
} }
} }
static createInstance(diceType, scene) { static createInstance(diceType: string, scene: Scene) {
if (!this.material || !this.meshes) { if (!this.material || !this.meshes) {
throw Error("Dice not loaded, call load before creating an instance"); throw Error("Dice not loaded, call load before creating an instance");
} }
return Dice.createInstance( return Dice.createInstanceMesh(
this.meshes[diceType], this.meshes[diceType],
this.getDicePhysicalProperties(diceType), this.getDicePhysicalProperties(diceType),
scene scene

View File

@ -8,17 +8,18 @@ import mask from "./mask.png";
import normal from "./normal.jpg"; import normal from "./normal.jpg";
import { importTextureAsync } from "../../helpers/babylon"; import { importTextureAsync } from "../../helpers/babylon";
import { Material, Mesh, Scene } from "@babylonjs/core";
class GlassDice extends Dice { class GlassDice extends Dice {
static meshes; static meshes: Record<string, Mesh>;
static material; static material: Material;
static getDicePhysicalProperties(diceType) { static getDicePhysicalProperties(diceType: string) {
let properties = super.getDicePhysicalProperties(diceType); let properties = super.getDicePhysicalProperties(diceType);
return { mass: properties.mass * 1.5, friction: properties.friction }; return { mass: properties.mass * 1.5, friction: properties.friction };
} }
static async loadMaterial(materialName, textures, scene) { static async loadMaterial(materialName: string, textures: any, scene: Scene) {
let pbr = new PBRMaterial(materialName, scene); let pbr = new PBRMaterial(materialName, scene);
let [albedo, normal, mask] = await Promise.all([ let [albedo, normal, mask] = await Promise.all([
importTextureAsync(textures.albedo), importTextureAsync(textures.albedo),
@ -26,7 +27,8 @@ class GlassDice extends Dice {
importTextureAsync(textures.mask), importTextureAsync(textures.mask),
]); ]);
pbr.albedoTexture = albedo; pbr.albedoTexture = albedo;
pbr.normalTexture = normal; // pbr.normalTexture = normal;
pbr.bumpTexture = normal;
pbr.roughness = 0.25; pbr.roughness = 0.25;
pbr.metallic = 0; pbr.metallic = 0;
pbr.subSurface.isRefractionEnabled = true; pbr.subSurface.isRefractionEnabled = true;
@ -43,7 +45,7 @@ class GlassDice extends Dice {
return pbr; return pbr;
} }
static async load(scene) { static async load(scene: Scene) {
if (!this.material) { if (!this.material) {
this.material = await this.loadMaterial( this.material = await this.loadMaterial(
"glass_pbr", "glass_pbr",
@ -56,12 +58,12 @@ class GlassDice extends Dice {
} }
} }
static createInstance(diceType, scene) { static createInstance(diceType: string, scene: Scene) {
if (!this.material || !this.meshes) { if (!this.material || !this.meshes) {
throw Error("Dice not loaded, call load before creating an instance"); throw Error("Dice not loaded, call load before creating an instance");
} }
return Dice.createInstance( return Dice.createInstanceMesh(
this.meshes[diceType], this.meshes[diceType],
this.getDicePhysicalProperties(diceType), this.getDicePhysicalProperties(diceType),
scene scene

View File

@ -17,8 +17,11 @@ import SunsetPreview from "./sunset/preview.png";
import WalnutPreview from "./walnut/preview.png"; import WalnutPreview from "./walnut/preview.png";
import GlassPreview from "./glass/preview.png"; import GlassPreview from "./glass/preview.png";
import GemstonePreview from "./gemstone/preview.png"; import GemstonePreview from "./gemstone/preview.png";
import Dice from "./Dice";
export const diceClasses = { type DiceClasses = Record<string, Dice>;
export const diceClasses: DiceClasses = {
galaxy: GalaxyDice, galaxy: GalaxyDice,
nebula: NebulaDice, nebula: NebulaDice,
sunrise: SunriseDice, sunrise: SunriseDice,
@ -29,7 +32,9 @@ export const diceClasses = {
gemstone: GemstoneDice, gemstone: GemstoneDice,
}; };
export const dicePreviews = { type DicePreview = Record<string, string>;
export const dicePreviews: DicePreview = {
galaxy: GalaxyPreview, galaxy: GalaxyPreview,
nebula: NebulaPreview, nebula: NebulaPreview,
sunrise: SunrisePreview, sunrise: SunrisePreview,

View File

@ -1,3 +1,4 @@
import { Material, Mesh, Scene } from "@babylonjs/core";
import Dice from "../Dice"; import Dice from "../Dice";
import albedo from "./albedo.jpg"; import albedo from "./albedo.jpg";
@ -5,15 +6,15 @@ import metalRoughness from "./metalRoughness.jpg";
import normal from "./normal.jpg"; import normal from "./normal.jpg";
class IronDice extends Dice { class IronDice extends Dice {
static meshes; static meshes: Record<string, Mesh>;
static material; static material: Material;
static getDicePhysicalProperties(diceType) { static getDicePhysicalProperties(diceType: string) {
let properties = super.getDicePhysicalProperties(diceType); let properties = super.getDicePhysicalProperties(diceType);
return { mass: properties.mass * 2, friction: properties.friction }; return { mass: properties.mass * 2, friction: properties.friction };
} }
static async load(scene) { static async load(scene: Scene) {
if (!this.material) { if (!this.material) {
this.material = await this.loadMaterial( this.material = await this.loadMaterial(
"iron_pbr", "iron_pbr",
@ -26,12 +27,12 @@ class IronDice extends Dice {
} }
} }
static createInstance(diceType, scene) { static createInstance(diceType: string, scene: Scene) {
if (!this.material || !this.meshes) { if (!this.material || !this.meshes) {
throw Error("Dice not loaded, call load before creating an instance"); throw Error("Dice not loaded, call load before creating an instance");
} }
return Dice.createInstance( return Dice.createInstanceMesh(
this.meshes[diceType], this.meshes[diceType],
this.getDicePhysicalProperties(diceType), this.getDicePhysicalProperties(diceType),
scene scene

View File

@ -1,3 +1,4 @@
import { Material, Mesh, Scene } from "@babylonjs/core";
import Dice from "../Dice"; import Dice from "../Dice";
import albedo from "./albedo.jpg"; import albedo from "./albedo.jpg";
@ -5,10 +6,10 @@ import metalRoughness from "./metalRoughness.jpg";
import normal from "./normal.jpg"; import normal from "./normal.jpg";
class NebulaDice extends Dice { class NebulaDice extends Dice {
static meshes; static meshes: Record<string, Mesh>;
static material; static material: Material;
static async load(scene) { static async load(scene: Scene) {
if (!this.material) { if (!this.material) {
this.material = await this.loadMaterial( this.material = await this.loadMaterial(
"neubula_pbr", "neubula_pbr",
@ -21,12 +22,12 @@ class NebulaDice extends Dice {
} }
} }
static createInstance(diceType, scene) { static createInstance(diceType: string, scene: Scene) {
if (!this.material || !this.meshes) { if (!this.material || !this.meshes) {
throw Error("Dice not loaded, call load before creating an instance"); throw Error("Dice not loaded, call load before creating an instance");
} }
return Dice.createInstance( return Dice.createInstanceMesh(
this.meshes[diceType], this.meshes[diceType],
this.getDicePhysicalProperties(diceType), this.getDicePhysicalProperties(diceType),
scene scene

View File

@ -1,3 +1,4 @@
import { Material, Mesh, Scene } from "@babylonjs/core";
import Dice from "../Dice"; import Dice from "../Dice";
import albedo from "./albedo.jpg"; import albedo from "./albedo.jpg";
@ -5,10 +6,10 @@ import metalRoughness from "./metalRoughness.jpg";
import normal from "./normal.jpg"; import normal from "./normal.jpg";
class SunriseDice extends Dice { class SunriseDice extends Dice {
static meshes; static meshes: Record<string, Mesh>;
static material; static material: Material;
static async load(scene) { static async load(scene: Scene) {
if (!this.material) { if (!this.material) {
this.material = await this.loadMaterial( this.material = await this.loadMaterial(
"sunrise_pbr", "sunrise_pbr",
@ -21,12 +22,12 @@ class SunriseDice extends Dice {
} }
} }
static createInstance(diceType, scene) { static createInstance(diceType: string, scene: Scene) {
if (!this.material || !this.meshes) { if (!this.material || !this.meshes) {
throw Error("Dice not loaded, call load before creating an instance"); throw Error("Dice not loaded, call load before creating an instance");
} }
return Dice.createInstance( return super.createInstanceMesh(
this.meshes[diceType], this.meshes[diceType],
this.getDicePhysicalProperties(diceType), this.getDicePhysicalProperties(diceType),
scene scene

View File

@ -1,3 +1,4 @@
import { Material, Mesh, Scene } from "@babylonjs/core";
import Dice from "../Dice"; import Dice from "../Dice";
import albedo from "./albedo.jpg"; import albedo from "./albedo.jpg";
@ -5,10 +6,10 @@ import metalRoughness from "./metalRoughness.jpg";
import normal from "./normal.jpg"; import normal from "./normal.jpg";
class SunsetDice extends Dice { class SunsetDice extends Dice {
static meshes; static meshes: Record<string, Mesh>;
static material; static material: Material;
static async load(scene) { static async load(scene: Scene) {
if (!this.material) { if (!this.material) {
this.material = await this.loadMaterial( this.material = await this.loadMaterial(
"sunset_pbr", "sunset_pbr",
@ -21,12 +22,12 @@ class SunsetDice extends Dice {
} }
} }
static createInstance(diceType, scene) { static createInstance(diceType: string, scene: Scene) {
if (!this.material || !this.meshes) { if (!this.material || !this.meshes) {
throw Error("Dice not loaded, call load before creating an instance"); throw Error("Dice not loaded, call load before creating an instance");
} }
return Dice.createInstance( return super.createInstanceMesh(
this.meshes[diceType], this.meshes[diceType],
this.getDicePhysicalProperties(diceType), this.getDicePhysicalProperties(diceType),
scene scene

View File

@ -11,6 +11,7 @@ import d10Source from "./d10.glb";
import d12Source from "./d12.glb"; import d12Source from "./d12.glb";
import d20Source from "./d20.glb"; import d20Source from "./d20.glb";
import d100Source from "./d100.glb"; import d100Source from "./d100.glb";
import { Material, Mesh, Scene } from "@babylonjs/core";
const sourceOverrides = { const sourceOverrides = {
d4: d4Source, d4: d4Source,
@ -23,15 +24,15 @@ const sourceOverrides = {
}; };
class WalnutDice extends Dice { class WalnutDice extends Dice {
static meshes; static meshes: Record<string, Mesh>;
static material; static material: Material;
static getDicePhysicalProperties(diceType) { static getDicePhysicalProperties(diceType: string) {
let properties = super.getDicePhysicalProperties(diceType); let properties = super.getDicePhysicalProperties(diceType);
return { mass: properties.mass * 1.4, friction: properties.friction }; return { mass: properties.mass * 1.4, friction: properties.friction };
} }
static async load(scene) { static async load(scene: Scene) {
if (!this.material) { if (!this.material) {
this.material = await this.loadMaterial( this.material = await this.loadMaterial(
"walnut_pbr", "walnut_pbr",
@ -48,12 +49,12 @@ class WalnutDice extends Dice {
} }
} }
static createInstance(diceType, scene) { static createInstance(diceType: string, scene: Scene) {
if (!this.material || !this.meshes) { if (!this.material || !this.meshes) {
throw Error("Dice not loaded, call load before creating an instance"); throw Error("Dice not loaded, call load before creating an instance");
} }
return Dice.createInstance( return super.createInstanceMesh(
this.meshes[diceType], this.meshes[diceType],
this.getDicePhysicalProperties(diceType), this.getDicePhysicalProperties(diceType),
scene scene

View File

@ -1,7 +1,7 @@
import { Texture } from "@babylonjs/core/Materials/Textures/texture"; import { Texture } from "@babylonjs/core/Materials/Textures/texture";
// Turn texture load into an async function so it can be awaited // Turn texture load into an async function so it can be awaited
export async function importTextureAsync(url: string) { export async function importTextureAsync(url: string): Promise<Texture> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let texture = new Texture( let texture = new Texture(
url, url,

View File

@ -1,6 +1,6 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
function useDebounce(value, delay) { function useDebounce(value: any, delay: number): any {
const [debouncedValue, setDebouncedValue] = useState(); const [debouncedValue, setDebouncedValue] = useState();
useEffect(() => { useEffect(() => {

View File

@ -2,6 +2,7 @@ import { useEffect, useState, useRef, useCallback } from "react";
import useDebounce from "./useDebounce"; import useDebounce from "./useDebounce";
import { diff, applyChanges } from "../helpers/diff"; import { diff, applyChanges } from "../helpers/diff";
import Session from "../network/Session";
/** /**
* @callback setNetworkedState * @callback setNetworkedState
@ -9,6 +10,8 @@ import { diff, applyChanges } from "../helpers/diff";
* @param {boolean} sync Whether to sync the update with the session * @param {boolean} sync Whether to sync the update with the session
* @param {boolean} force Whether to force a full update, usefull when partialUpdates is enabled * @param {boolean} force Whether to force a full update, usefull when partialUpdates is enabled
*/ */
// TODO: check parameter requirements here
type setNetworkedState = (update: any, sync?: boolean, force?: boolean) => void
/** /**
* Helper to sync a react state to a `Session` * Helper to sync a react state to a `Session`
@ -23,13 +26,13 @@ import { diff, applyChanges } from "../helpers/diff";
* @returns {[any, setNetworkedState]} * @returns {[any, setNetworkedState]}
*/ */
function useNetworkedState( function useNetworkedState(
initialState, initialState: any,
session, session: Session,
eventName, eventName: string,
debounceRate = 500, debounceRate: number = 500,
partialUpdates = true, partialUpdates: boolean = true,
partialUpdatesKey = "id" partialUpdatesKey: string = "id"
) { ): [any, setNetworkedState] {
const [state, _setState] = useState(initialState); const [state, _setState] = useState(initialState);
// Used to control whether the state needs to be sent to the socket // Used to control whether the state needs to be sent to the socket
const dirtyRef = useRef(false); const dirtyRef = useRef(false);
@ -62,6 +65,9 @@ function useNetworkedState(
) { ) {
const changes = diff(lastSyncedStateRef.current, debouncedState); const changes = diff(lastSyncedStateRef.current, debouncedState);
if (changes) { if (changes) {
if (!debouncedState) {
return;
}
const update = { id: debouncedState[partialUpdatesKey], changes }; const update = { id: debouncedState[partialUpdatesKey], changes };
session.socket.emit(`${eventName}_update`, update); session.socket.emit(`${eventName}_update`, update);
} }
@ -81,13 +87,13 @@ function useNetworkedState(
]); ]);
useEffect(() => { useEffect(() => {
function handleSocketEvent(data) { function handleSocketEvent(data: any) {
_setState(data); _setState(data);
lastSyncedStateRef.current = data; lastSyncedStateRef.current = data;
} }
function handleSocketUpdateEvent(update) { function handleSocketUpdateEvent(update: any) {
_setState((prevState) => { _setState((prevState: any) => {
if (prevState && prevState[partialUpdatesKey] === update.id) { if (prevState && prevState[partialUpdatesKey] === update.id) {
let newState = { ...prevState }; let newState = { ...prevState };
applyChanges(newState, update.changes); applyChanges(newState, update.changes);

View File

@ -1,4 +1,5 @@
import Case from "case"; import Case from "case";
import { Map } from "../components/map/Map";
import blankImage from "./Blank Grid 22x22.jpg"; import blankImage from "./Blank Grid 22x22.jpg";
import grassImage from "./Grass Grid 22x22.jpg"; import grassImage from "./Grass Grid 22x22.jpg";
@ -18,7 +19,7 @@ export const mapSources = {
wood: woodImage, wood: woodImage,
}; };
export const maps = Object.keys(mapSources).map((key) => ({ export const maps: Array<Omit<Map, "lastModified" | "created" | "showGrid" | "snapToGrid" | "id" | "owner" | "group" |"file" | "quality" | "resolutions" | "lastUsed" |"thumbnail">> = Object.keys(mapSources).map((key) => ({
key, key,
name: Case.capital(key), name: Case.capital(key),
grid: { grid: {

View File

@ -43,7 +43,7 @@ function EditMapModal({ isOpen, onDone, mapId }: EditMapProps) {
} }
const mapState = await getMapStateFromDB(mapId); const mapState = await getMapStateFromDB(mapId);
setMap(loadingMap); setMap(loadingMap);
setMapState(mapState); setMapState(mapState as MapState);
setIsLoading(false); setIsLoading(false);
} }

View File

@ -30,7 +30,7 @@ import { useAuth } from "../contexts/AuthContext";
import { useKeyboard, useBlur } from "../contexts/KeyboardContext"; import { useKeyboard, useBlur } from "../contexts/KeyboardContext";
import shortcuts from "../shortcuts"; import shortcuts from "../shortcuts";
import { MapState } from "../components/map/Map"; import { Map, MapState } from "../components/map/Map";
type SelectMapProps = { type SelectMapProps = {
isOpen: boolean, isOpen: boolean,
@ -175,7 +175,7 @@ function SelectMapModal({
clearFileInput(); clearFileInput();
} }
async function handleImageUpload(file: any) { async function handleImageUpload(file: File) {
if (!file) { if (!file) {
return Promise.reject(); return Promise.reject();
} }
@ -313,7 +313,7 @@ function SelectMapModal({
// The map selected in the modal // The map selected in the modal
const [selectedMapIds, setSelectedMapIds] = useState<string[]>([]); const [selectedMapIds, setSelectedMapIds] = useState<string[]>([]);
const selectedMaps = ownedMaps.filter((map: any) => const selectedMaps: Map[] = ownedMaps.filter((map: Map) =>
selectedMapIds.includes(map.id) selectedMapIds.includes(map.id)
); );
const selectedMapStates = mapStates.filter((state: MapState) => const selectedMapStates = mapStates.filter((state: MapState) =>
@ -499,11 +499,15 @@ function SelectMapModal({
</Button> </Button>
</Flex> </Flex>
</ImageDrop> </ImageDrop>
<>
{(isLoading || mapsLoading) && <LoadingOverlay bg="overlay" />} {(isLoading || mapsLoading) && <LoadingOverlay bg="overlay" />}
</>
<EditMapModal <EditMapModal
isOpen={isEditModalOpen} isOpen={isEditModalOpen}
onDone={() => setIsEditModalOpen(false)} onDone={() => setIsEditModalOpen(false)}
mapId={selectedMaps.length === 1 && selectedMaps[0].id} // TODO: check with Mitch what to do here if length > 1
//selectedMaps.length === 1 &&
mapId={selectedMaps[0].id}
/> />
<EditGroupModal <EditGroupModal
isOpen={isGroupModalOpen} isOpen={isGroupModalOpen}

View File

@ -6,10 +6,10 @@ type StartStreamProps = {
isOpen: boolean, isOpen: boolean,
onRequestClose: () => void, onRequestClose: () => void,
isSupported: boolean, isSupported: boolean,
unavailableMessage: string, unavailableMessage: JSX.Element,
stream: MediaStream, stream: MediaStream,
noAudioTrack: boolean, noAudioTrack: boolean,
noAudioMessage: string, noAudioMessage: JSX.Element,
onStreamStart: any, onStreamStart: any,
onStreamEnd: any, onStreamEnd: any,
} }

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef } from "react"; import { useState, useEffect, useRef } from "react";
import { useToasts } from "react-toast-notifications"; import { useToasts } from "react-toast-notifications";
import { useTokenData } from "../contexts/TokenDataContext"; import { useTokenData } from "../contexts/TokenDataContext";
@ -14,11 +14,13 @@ import useDebounce from "../hooks/useDebounce";
import useNetworkedState from "../hooks/useNetworkedState"; import useNetworkedState from "../hooks/useNetworkedState";
// Load session for auto complete // Load session for auto complete
// eslint-disable-next-line no-unused-vars
import Session from "./Session"; import Session from "./Session";
import Map from "../components/map/Map"; import Map, { MapState, Resolutions, TokenState } from "../components/map/Map";
import Tokens from "../components/token/Tokens"; import Tokens from "../components/token/Tokens";
import { PartyState } from "../components/party/PartyState";
import Action from "../actions/Action";
import { Token } from "../tokens";
const defaultMapActions = { const defaultMapActions = {
mapDrawActions: [], mapDrawActions: [],
@ -35,10 +37,10 @@ const defaultMapActions = {
/** /**
* @param {NetworkedMapProps} props * @param {NetworkedMapProps} props
*/ */
function NetworkedMapAndTokens({ session }) { function NetworkedMapAndTokens({ session }: { session: Session }) {
const { addToast } = useToasts(); const { addToast } = useToasts();
const { userId } = useAuth(); const { userId } = useAuth();
const partyState = useParty(); const partyState: PartyState = useParty();
const { const {
assetLoadStart, assetLoadStart,
assetLoadFinish, assetLoadFinish,
@ -49,8 +51,8 @@ function NetworkedMapAndTokens({ session }) {
const { putToken, getTokenFromDB } = useTokenData(); const { putToken, getTokenFromDB } = useTokenData();
const { putMap, updateMap, getMapFromDB, updateMapState } = useMapData(); const { putMap, updateMap, getMapFromDB, updateMapState } = useMapData();
const [currentMap, setCurrentMap] = useState(null); const [currentMap, setCurrentMap] = useState<any>(null);
const [currentMapState, setCurrentMapState] = useNetworkedState( const [currentMapState, setCurrentMapState]: [ currentMapState: MapState, setCurrentMapState: any] = useNetworkedState(
null, null,
session, session,
"map_state", "map_state",
@ -67,8 +69,8 @@ function NetworkedMapAndTokens({ session }) {
"mapId" "mapId"
); );
async function loadAssetManifestFromMap(map, mapState) { async function loadAssetManifestFromMap(map: any, mapState: MapState) {
const assets = {}; const assets: any = {};
if (map.type === "file") { if (map.type === "file") {
const { id, lastModified, owner } = map; const { id, lastModified, owner } = map;
assets[`map-${id}`] = { type: "map", id, lastModified, owner }; assets[`map-${id}`] = { type: "map", id, lastModified, owner };
@ -90,20 +92,20 @@ function NetworkedMapAndTokens({ session }) {
setAssetManifest({ mapId: map.id, assets }, true, true); setAssetManifest({ mapId: map.id, assets }, true, true);
} }
function compareAssets(a, b) { function compareAssets(a: any, b: any) {
return a.type === b.type && a.id === b.id; return a.type === b.type && a.id === b.id;
} }
// Return true if an asset is out of date // Return true if an asset is out of date
function assetNeedsUpdate(oldAsset, newAsset) { function assetNeedsUpdate(oldAsset: any, newAsset: any) {
return ( return (
compareAssets(oldAsset, newAsset) && compareAssets(oldAsset, newAsset) &&
oldAsset.lastModified < newAsset.lastModified oldAsset.lastModified < newAsset.lastModified
); );
} }
function addAssetIfNeeded(asset) { function addAssetIfNeeded(asset: any) {
setAssetManifest((prevManifest) => { setAssetManifest((prevManifest: any) => {
if (prevManifest?.assets) { if (prevManifest?.assets) {
const id = const id =
asset.type === "map" ? `map-${asset.id}` : `token-${asset.id}`; asset.type === "map" ? `map-${asset.id}` : `token-${asset.id}`;
@ -133,7 +135,7 @@ function NetworkedMapAndTokens({ session }) {
} }
async function requestAssetsIfNeeded() { async function requestAssetsIfNeeded() {
for (let asset of Object.values(assetManifest.assets)) { for (let asset of Object.values(assetManifest.assets) as any) {
if ( if (
asset.owner === userId || asset.owner === userId ||
requestingAssetsRef.current.has(asset.id) requestingAssetsRef.current.has(asset.id)
@ -200,14 +202,14 @@ function NetworkedMapAndTokens({ session }) {
debouncedMapState && debouncedMapState &&
debouncedMapState.mapId && debouncedMapState.mapId &&
currentMap && currentMap &&
currentMap.owner === userId && currentMap?.owner === userId &&
database database
) { ) {
updateMapState(debouncedMapState.mapId, debouncedMapState); updateMapState(debouncedMapState.mapId, debouncedMapState);
} }
}, [currentMap, debouncedMapState, userId, database, updateMapState]); }, [currentMap, debouncedMapState, userId, database, updateMapState]);
async function handleMapChange(newMap, newMapState) { async function handleMapChange(newMap: any, newMapState: any) {
// Clear map before sending new one // Clear map before sending new one
setCurrentMap(null); setCurrentMap(null);
session.socket?.emit("map", null); session.socket?.emit("map", null);
@ -229,15 +231,15 @@ function NetworkedMapAndTokens({ session }) {
await loadAssetManifestFromMap(newMap, newMapState); await loadAssetManifestFromMap(newMap, newMapState);
} }
function handleMapReset(newMapState) { function handleMapReset(newMapState: any) {
setCurrentMapState(newMapState, true, true); setCurrentMapState(newMapState, true, true);
setMapActions(defaultMapActions); setMapActions(defaultMapActions);
} }
const [mapActions, setMapActions] = useState(defaultMapActions); const [mapActions, setMapActions] = useState<any>(defaultMapActions);
function addMapActions(actions, indexKey, actionsKey, shapesKey) { function addMapActions(actions: Action[], indexKey: string, actionsKey: any, shapesKey: any) {
setMapActions((prevMapActions) => { setMapActions((prevMapActions: any) => {
const newActions = [ const newActions = [
...prevMapActions[actionsKey].slice(0, prevMapActions[indexKey] + 1), ...prevMapActions[actionsKey].slice(0, prevMapActions[indexKey] + 1),
...actions, ...actions,
@ -250,7 +252,7 @@ function NetworkedMapAndTokens({ session }) {
}; };
}); });
// Update map state by performing the actions on it // Update map state by performing the actions on it
setCurrentMapState((prevMapState) => { setCurrentMapState((prevMapState: any) => {
if (prevMapState) { if (prevMapState) {
let shapes = prevMapState[shapesKey]; let shapes = prevMapState[shapesKey];
for (let action of actions) { for (let action of actions) {
@ -264,20 +266,20 @@ function NetworkedMapAndTokens({ session }) {
}); });
} }
function updateActionIndex(change, indexKey, actionsKey, shapesKey) { function updateActionIndex(change: any, indexKey: any, actionsKey: any, shapesKey: any) {
const prevIndex = mapActions[indexKey]; const prevIndex: any = mapActions[indexKey];
const newIndex = Math.min( const newIndex = Math.min(
Math.max(mapActions[indexKey] + change, -1), Math.max(mapActions[indexKey] + change, -1),
mapActions[actionsKey].length - 1 mapActions[actionsKey].length - 1
); );
setMapActions((prevMapActions) => ({ setMapActions((prevMapActions: Action[]) => ({
...prevMapActions, ...prevMapActions,
[indexKey]: newIndex, [indexKey]: newIndex,
})); }));
// Update map state by either performing the actions or undoing them // Update map state by either performing the actions or undoing them
setCurrentMapState((prevMapState) => { setCurrentMapState((prevMapState: any) => {
if (prevMapState) { if (prevMapState) {
let shapes = prevMapState[shapesKey]; let shapes = prevMapState[shapesKey];
if (prevIndex < newIndex) { if (prevIndex < newIndex) {
@ -303,7 +305,7 @@ function NetworkedMapAndTokens({ session }) {
return newIndex; return newIndex;
} }
function handleMapDraw(action) { function handleMapDraw(action: Action) {
addMapActions( addMapActions(
[action], [action],
"mapDrawActionIndex", "mapDrawActionIndex",
@ -320,7 +322,7 @@ function NetworkedMapAndTokens({ session }) {
updateActionIndex(1, "mapDrawActionIndex", "mapDrawActions", "drawShapes"); updateActionIndex(1, "mapDrawActionIndex", "mapDrawActions", "drawShapes");
} }
function handleFogDraw(action) { function handleFogDraw(action: Action) {
addMapActions( addMapActions(
[action], [action],
"fogDrawActionIndex", "fogDrawActionIndex",
@ -338,16 +340,16 @@ function NetworkedMapAndTokens({ session }) {
} }
// If map changes clear map actions // If map changes clear map actions
const previousMapIdRef = useRef(); const previousMapIdRef = useRef<any>();
useEffect(() => { useEffect(() => {
if (currentMap && currentMap.id !== previousMapIdRef.current) { if (currentMap && currentMap?.id !== previousMapIdRef.current) {
setMapActions(defaultMapActions); setMapActions(defaultMapActions);
previousMapIdRef.current = currentMap.id; previousMapIdRef.current = currentMap?.id;
} }
}, [currentMap]); }, [currentMap]);
function handleNoteChange(note) { function handleNoteChange(note: any) {
setCurrentMapState((prevMapState) => ({ setCurrentMapState((prevMapState: any) => ({
...prevMapState, ...prevMapState,
notes: { notes: {
...prevMapState.notes, ...prevMapState.notes,
@ -356,8 +358,8 @@ function NetworkedMapAndTokens({ session }) {
})); }));
} }
function handleNoteRemove(noteId) { function handleNoteRemove(noteId: string) {
setCurrentMapState((prevMapState) => ({ setCurrentMapState((prevMapState: any) => ({
...prevMapState, ...prevMapState,
notes: omit(prevMapState.notes, [noteId]), notes: omit(prevMapState.notes, [noteId]),
})); }));
@ -367,17 +369,17 @@ function NetworkedMapAndTokens({ session }) {
* Token state * Token state
*/ */
async function handleMapTokenStateCreate(tokenState) { async function handleMapTokenStateCreate(tokenState: TokenState) {
if (!currentMap || !currentMapState) { if (!currentMap || !currentMapState) {
return; return;
} }
// If file type token send the token to the other peers // If file type token send the token to the other peers
const token = await getTokenFromDB(tokenState.tokenId); const token: Token = await getTokenFromDB(tokenState.tokenId);
if (token && token.type === "file") { if (token && token.type === "file") {
const { id, lastModified, owner } = token; const { id, lastModified, owner } = token;
addAssetIfNeeded({ type: "token", id, lastModified, owner }); addAssetIfNeeded({ type: "token", id, lastModified, owner });
} }
setCurrentMapState((prevMapState) => ({ setCurrentMapState((prevMapState: any) => ({
...prevMapState, ...prevMapState,
tokens: { tokens: {
...prevMapState.tokens, ...prevMapState.tokens,
@ -386,11 +388,11 @@ function NetworkedMapAndTokens({ session }) {
})); }));
} }
function handleMapTokenStateChange(change) { function handleMapTokenStateChange(change: any) {
if (!currentMapState) { if (!currentMapState) {
return; return;
} }
setCurrentMapState((prevMapState) => { setCurrentMapState((prevMapState: any) => {
let tokens = { ...prevMapState.tokens }; let tokens = { ...prevMapState.tokens };
for (let id in change) { for (let id in change) {
if (id in tokens) { if (id in tokens) {
@ -405,22 +407,21 @@ function NetworkedMapAndTokens({ session }) {
}); });
} }
function handleMapTokenStateRemove(tokenState) { function handleMapTokenStateRemove(tokenState: any) {
setCurrentMapState((prevMapState) => { setCurrentMapState((prevMapState: any) => {
const { [tokenState.id]: old, ...rest } = prevMapState.tokens; const { [tokenState.id]: old, ...rest } = prevMapState.tokens;
return { ...prevMapState, tokens: rest }; return { ...prevMapState, tokens: rest };
}); });
} }
useEffect(() => { useEffect(() => {
async function handlePeerData({ id, data, reply }) { // TODO: edit Map type with appropriate resolutions
async function handlePeerData({ id, data, reply }: { id: string, data: any, reply: any}) {
if (id === "mapRequest") { if (id === "mapRequest") {
const map = await getMapFromDB(data); const map = await getMapFromDB(data);
function replyWithMap(preview, resolution) { function replyWithMap(preview?: string | undefined, resolution?: any) {
let response = { let response = {
...map, ...map,
resolutions: undefined,
file: undefined,
thumbnail: undefined, thumbnail: undefined,
// Remove last modified so if there is an error // Remove last modified so if there is an error
// during the map request the cache is invalid // during the map request the cache is invalid
@ -429,13 +430,13 @@ function NetworkedMapAndTokens({ session }) {
lastUsed: Date.now(), lastUsed: Date.now(),
}; };
// Send preview if available // Send preview if available
if (map.resolutions[preview]) { if (preview !== undefined && map.resolutions && map.resolutions[preview]) {
response.resolutions = { [preview]: map.resolutions[preview] }; response.resolutions = { [preview]: map.resolutions[preview] } as Resolutions;
reply("mapResponse", response, "map"); reply("mapResponse", response, "map");
} }
// Send full map at the desired resolution if available // Send full map at the desired resolution if available
if (map.resolutions[resolution]) { if (map.resolutions && map.resolutions[resolution]) {
response.file = map.resolutions[resolution].file; response.file = map.resolutions[resolution].file as Uint8Array;
} else if (map.file) { } else if (map.file) {
// The resolution might not exist for other users so send the file instead // The resolution might not exist for other users so send the file instead
response.file = map.file; response.file = map.file;
@ -506,7 +507,7 @@ function NetworkedMapAndTokens({ session }) {
} }
} }
function handlePeerDataProgress({ id, total, count }) { function handlePeerDataProgress({ id, total, count }: { id: string, total: number, count: number}) {
if (count === 1) { if (count === 1) {
// Corresponding asset load finished called in token and map response // Corresponding asset load finished called in token and map response
assetLoadStart(); assetLoadStart();
@ -514,7 +515,7 @@ function NetworkedMapAndTokens({ session }) {
assetProgressUpdate({ id, total, count }); assetProgressUpdate({ id, total, count });
} }
async function handleSocketMap(map) { async function handleSocketMap(map: any) {
if (map) { if (map) {
if (map.type === "file") { if (map.type === "file") {
const fullMap = await getMapFromDB(map.id); const fullMap = await getMapFromDB(map.id);
@ -540,31 +541,31 @@ function NetworkedMapAndTokens({ session }) {
const canChangeMap = !isLoading; const canChangeMap = !isLoading;
const canEditMapDrawing = const canEditMapDrawing: any =
currentMap && currentMap &&
currentMapState && currentMapState &&
(currentMapState.editFlags.includes("drawing") || (currentMapState.editFlags.includes("drawing") ||
currentMap.owner === userId); currentMap?.owner === userId);
const canEditFogDrawing = const canEditFogDrawing =
currentMap && currentMap &&
currentMapState && currentMapState &&
(currentMapState.editFlags.includes("fog") || currentMap.owner === userId); (currentMapState.editFlags.includes("fog") || currentMap?.owner === userId);
const canEditNotes = const canEditNotes =
currentMap && currentMap &&
currentMapState && currentMapState &&
(currentMapState.editFlags.includes("notes") || (currentMapState.editFlags.includes("notes") ||
currentMap.owner === userId); currentMap?.owner === userId);
const disabledMapTokens = {}; const disabledMapTokens: { [key: string]: any } = {};
// If we have a map and state and have the token permission disabled // If we have a map and state and have the token permission disabled
// and are not the map owner // and are not the map owner
if ( if (
currentMapState && currentMapState &&
currentMap && currentMap &&
!currentMapState.editFlags.includes("tokens") && !currentMapState.editFlags.includes("tokens") &&
currentMap.owner !== userId currentMap?.owner !== userId
) { ) {
for (let token of Object.values(currentMapState.tokens)) { for (let token of Object.values(currentMapState.tokens)) {
if (token.owner !== userId) { if (token.owner !== userId) {

View File

@ -8,11 +8,12 @@ import { isEmpty } from "../helpers/shared";
import Vector2 from "../helpers/Vector2"; import Vector2 from "../helpers/Vector2";
import useSetting from "../hooks/useSetting"; import useSetting from "../hooks/useSetting";
import Session from "./Session";
// Send pointer updates every 50ms (20fps) // Send pointer updates every 50ms (20fps)
const sendTickRate = 50; const sendTickRate = 50;
function NetworkedMapPointer({ session, active }) { function NetworkedMapPointer({ session, active }: { session: Session, active: boolean }) {
const { userId } = useAuth(); const { userId } = useAuth();
const [localPointerState, setLocalPointerState] = useState({}); const [localPointerState, setLocalPointerState] = useState({});
const [pointerColor] = useSetting("pointer.color"); const [pointerColor] = useSetting("pointer.color");
@ -38,12 +39,12 @@ function NetworkedMapPointer({ session, active }) {
// Send pointer updates every sendTickRate to peers to save on bandwidth // Send pointer updates every sendTickRate to peers to save on bandwidth
// We use requestAnimationFrame as setInterval was being blocked during // We use requestAnimationFrame as setInterval was being blocked during
// re-renders on Chrome with Windows // re-renders on Chrome with Windows
const ownPointerUpdateRef = useRef(); const ownPointerUpdateRef: React.MutableRefObject<{ position: any; visible: boolean; id: any; color: any; } | undefined | null > = useRef();
useEffect(() => { useEffect(() => {
let prevTime = performance.now(); let prevTime = performance.now();
let request = requestAnimationFrame(update); let request = requestAnimationFrame(update);
let counter = 0; let counter = 0;
function update(time) { function update(time: any) {
request = requestAnimationFrame(update); request = requestAnimationFrame(update);
const deltaTime = time - prevTime; const deltaTime = time - prevTime;
counter += deltaTime; counter += deltaTime;
@ -70,7 +71,7 @@ function NetworkedMapPointer({ session, active }) {
}; };
}, []); }, []);
function updateOwnPointerState(position, visible) { function updateOwnPointerState(position: any, visible: boolean) {
setLocalPointerState((prev) => ({ setLocalPointerState((prev) => ({
...prev, ...prev,
[userId]: { position, visible, id: userId, color: pointerColor }, [userId]: { position, visible, id: userId, color: pointerColor },
@ -83,24 +84,24 @@ function NetworkedMapPointer({ session, active }) {
}; };
} }
function handleOwnPointerDown(position) { function handleOwnPointerDown(position: any) {
updateOwnPointerState(position, true); updateOwnPointerState(position, true);
} }
function handleOwnPointerMove(position) { function handleOwnPointerMove(position: any) {
updateOwnPointerState(position, true); updateOwnPointerState(position, true);
} }
function handleOwnPointerUp(position) { function handleOwnPointerUp(position: any) {
updateOwnPointerState(position, false); updateOwnPointerState(position, false);
} }
// Handle pointer data receive // Handle pointer data receive
const interpolationsRef = useRef({}); const interpolationsRef: React.MutableRefObject<any> = useRef({});
useEffect(() => { useEffect(() => {
// TODO: Handle player disconnect while pointer visible // TODO: Handle player disconnect while pointer visible
function handleSocketPlayerPointer(pointer) { function handleSocketPlayerPointer(pointer: any) {
const interpolations = interpolationsRef.current; const interpolations: any = interpolationsRef.current;
const id = pointer.id; const id = pointer.id;
if (!(id in interpolations)) { if (!(id in interpolations)) {
interpolations[id] = { interpolations[id] = {
@ -145,8 +146,8 @@ function NetworkedMapPointer({ session, active }) {
function animate() { function animate() {
request = requestAnimationFrame(animate); request = requestAnimationFrame(animate);
const time = performance.now(); const time = performance.now();
let interpolatedPointerState = {}; let interpolatedPointerState: any = {};
for (let interp of Object.values(interpolationsRef.current)) { for (let interp of Object.values(interpolationsRef.current) as any) {
if (!interp.from || !interp.to) { if (!interp.from || !interp.to) {
continue; continue;
} }
@ -191,7 +192,7 @@ function NetworkedMapPointer({ session, active }) {
return ( return (
<Group> <Group>
{Object.values(localPointerState).map((pointer) => ( {Object.values(localPointerState).map((pointer: any) => (
<MapPointer <MapPointer
key={pointer.id} key={pointer.id}
active={pointer.id === userId ? active : false} active={pointer.id === userId ? active : false}

View File

@ -1,14 +1,14 @@
import React, { useState, useEffect, useCallback, useRef } from "react"; import { useState, useEffect, useCallback, useRef } from "react";
import { useToasts } from "react-toast-notifications"; import { useToasts } from "react-toast-notifications";
// Load session for auto complete // Load session for auto complete
// eslint-disable-next-line no-unused-vars import Session, { SessionPeer } from "./Session";
import Session from "./Session";
import { isStreamStopped, omit } from "../helpers/shared"; import { isStreamStopped, omit } from "../helpers/shared";
import { useParty } from "../contexts/PartyContext"; import { useParty } from "../contexts/PartyContext";
import Party from "../components/party/Party"; import Party from "../components/party/Party";
import { PartyState } from "../components/party/PartyState";
/** /**
* @typedef {object} NetworkedPartyProps * @typedef {object} NetworkedPartyProps
@ -16,24 +16,26 @@ import Party from "../components/party/Party";
* @property {Session} session * @property {Session} session
*/ */
type NetworkedPartyProps = { gameId: string, session: Session }
/** /**
* @param {NetworkedPartyProps} props * @param {NetworkedPartyProps} props
*/ */
function NetworkedParty({ gameId, session }) { function NetworkedParty(props: NetworkedPartyProps) {
const partyState = useParty(); const partyState: PartyState = useParty();
const [stream, setStream] = useState(null); const [stream, setStream] = useState<MediaStream | null>(null);
const [partyStreams, setPartyStreams] = useState({}); const [partyStreams, setPartyStreams] = useState({});
const { addToast } = useToasts(); const { addToast } = useToasts();
function handleStreamStart(localStream) { function handleStreamStart(localStream: MediaStream) {
setStream(localStream); setStream(localStream);
const tracks = localStream.getTracks(); const tracks = localStream.getTracks();
for (let track of tracks) { for (let track of tracks) {
// Only add the audio track of the stream to the remote peer // Only add the audio track of the stream to the remote peer
if (track.kind === "audio") { if (track.kind === "audio") {
for (let player of Object.values(partyState)) { for (let player of Object.values(partyState)) {
session.startStreamTo(player.sessionId, track, localStream); props.session.startStreamTo(player.sessionId, track, localStream);
} }
} }
} }
@ -48,16 +50,16 @@ function NetworkedParty({ gameId, session }) {
// Only sending audio so only remove the audio track // Only sending audio so only remove the audio track
if (track.kind === "audio") { if (track.kind === "audio") {
for (let player of Object.values(partyState)) { for (let player of Object.values(partyState)) {
session.endStreamTo(player.sessionId, track, localStream); props.session.endStreamTo(player.sessionId, track, localStream);
} }
} }
} }
}, },
[session, partyState] [props.session, partyState]
); );
// Keep a reference to players who have just joined to show the joined notification // Keep a reference to players who have just joined to show the joined notification
const joinedPlayersRef = useRef([]); const joinedPlayersRef = useRef<string[]>([]);
useEffect(() => { useEffect(() => {
if (joinedPlayersRef.current.length > 0) { if (joinedPlayersRef.current.length > 0) {
for (let id of joinedPlayersRef.current) { for (let id of joinedPlayersRef.current) {
@ -70,12 +72,12 @@ function NetworkedParty({ gameId, session }) {
}, [partyState, addToast]); }, [partyState, addToast]);
useEffect(() => { useEffect(() => {
function handlePlayerJoined(sessionId) { function handlePlayerJoined(sessionId: string) {
if (stream) { if (stream) {
const tracks = stream.getTracks(); const tracks = stream.getTracks();
for (let track of tracks) { for (let track of tracks) {
if (track.kind === "audio") { if (track.kind === "audio") {
session.startStreamTo(sessionId, track, stream); props.session.startStreamTo(sessionId, track, stream);
} }
} }
} }
@ -84,20 +86,20 @@ function NetworkedParty({ gameId, session }) {
joinedPlayersRef.current.push(sessionId); joinedPlayersRef.current.push(sessionId);
} }
function handlePlayerLeft(sessionId) { function handlePlayerLeft(sessionId: string) {
if (partyState[sessionId]) { if (partyState[sessionId]) {
addToast(`${partyState[sessionId].nickname} left the party`); addToast(`${partyState[sessionId].nickname} left the party`);
} }
} }
function handlePeerTrackAdded({ peer, stream: remoteStream }) { function handlePeerTrackAdded({ peer, stream: remoteStream }: { peer: SessionPeer, stream: MediaStream}) {
setPartyStreams((prevStreams) => ({ setPartyStreams((prevStreams) => ({
...prevStreams, ...prevStreams,
[peer.id]: remoteStream, [peer.id]: remoteStream,
})); }));
} }
function handlePeerTrackRemoved({ peer, stream: remoteStream }) { function handlePeerTrackRemoved({ peer, stream: remoteStream }: { peer: SessionPeer, stream: MediaStream }) {
if (isStreamStopped(remoteStream)) { if (isStreamStopped(remoteStream)) {
setPartyStreams((prevStreams) => omit(prevStreams, [peer.id])); setPartyStreams((prevStreams) => omit(prevStreams, [peer.id]));
} else { } else {
@ -108,16 +110,16 @@ function NetworkedParty({ gameId, session }) {
} }
} }
session.on("playerJoined", handlePlayerJoined); props.session.on("playerJoined", handlePlayerJoined);
session.on("playerLeft", handlePlayerLeft); props.session.on("playerLeft", handlePlayerLeft);
session.on("peerTrackAdded", handlePeerTrackAdded); props.session.on("peerTrackAdded", handlePeerTrackAdded);
session.on("peerTrackRemoved", handlePeerTrackRemoved); props.session.on("peerTrackRemoved", handlePeerTrackRemoved);
return () => { return () => {
session.off("playerJoined", handlePlayerJoined); props.session.off("playerJoined", handlePlayerJoined);
session.off("playerLeft", handlePlayerLeft); props.session.off("playerLeft", handlePlayerLeft);
session.off("peerTrackAdded", handlePeerTrackAdded); props.session.off("peerTrackAdded", handlePeerTrackAdded);
session.off("peerTrackRemoved", handlePeerTrackRemoved); props.session.off("peerTrackRemoved", handlePeerTrackRemoved);
}; };
}); });
@ -140,7 +142,7 @@ function NetworkedParty({ gameId, session }) {
return ( return (
<> <>
<Party <Party
gameId={gameId} gameId={props.gameId}
onStreamStart={handleStreamStart} onStreamStart={handleStreamStart}
onStreamEnd={handleStreamEnd} onStreamEnd={handleStreamEnd}
stream={stream} stream={stream}

View File

@ -15,7 +15,7 @@ import { SimplePeerData } from "simple-peer";
* @property {boolean} initiator - Is this peer the initiator of the connection * @property {boolean} initiator - Is this peer the initiator of the connection
* @property {boolean} ready - Ready for data to be sent * @property {boolean} ready - Ready for data to be sent
*/ */
type SessionPeer = { export type SessionPeer = {
id: string; id: string;
connection: Connection; connection: Connection;
initiator: boolean; initiator: boolean;
@ -137,7 +137,7 @@ class Session extends EventEmitter {
* @param {object} data * @param {object} data
* @param {string} channel * @param {string} channel
*/ */
sendTo(sessionId: string, eventId: string, data: SimplePeerData, channel: string) { sendTo(sessionId: string, eventId: string, data: SimplePeerData, channel?: string) {
if (!(sessionId in this.peers)) { if (!(sessionId in this.peers)) {
if (!this._addPeer(sessionId, true)) { if (!this._addPeer(sessionId, true)) {
return; return;

View File

@ -1,4 +1,3 @@
import React from "react";
import { Flex, Text, Link as ExternalLink } from "theme-ui"; import { Flex, Text, Link as ExternalLink } from "theme-ui";
import Footer from "../components/Footer"; import Footer from "../components/Footer";

View File

@ -16,37 +16,46 @@ import ErrorBanner from "../components/banner/ErrorBanner";
import LoadingOverlay from "../components/LoadingOverlay"; import LoadingOverlay from "../components/LoadingOverlay";
import { logError } from "../helpers/logging"; import { logError } from "../helpers/logging";
import { Stripe } from "@stripe/stripe-js";
const prices = [ type Price = { price?: string, name: string, value: number }
const prices: Price[] = [
{ price: "$5.00", name: "Small", value: 5 }, { price: "$5.00", name: "Small", value: 5 },
{ price: "$15.00", name: "Medium", value: 15 }, { price: "$15.00", name: "Medium", value: 15 },
{ price: "$30.00", name: "Large", value: 30 }, { price: "$30.00", name: "Large", value: 30 },
]; ];
function Donate() { function Donate() {
const location = useLocation(); const location = useLocation();
const query = new URLSearchParams(location.search); const query = new URLSearchParams(location.search);
const hasDonated = query.has("success"); const hasDonated = query.has("success");
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState(null); // TODO: check with Mitch about changes here from useState(null)
// TODO: typing with error a little messy
const [error, setError]= useState<any>();
const [stripe, setStripe] = useState(); const [stripe, setStripe]: [ stripe: Stripe | undefined, setStripe: React.Dispatch<Stripe | undefined >] = useState();
useEffect(() => { useEffect(() => {
import("@stripe/stripe-js").then(({ loadStripe }) => { import("@stripe/stripe-js").then(({ loadStripe }) => {
loadStripe(process.env.REACT_APP_STRIPE_API_KEY) loadStripe(process.env.REACT_APP_STRIPE_API_KEY as string)
.then((stripe) => { .then((stripe) => {
setStripe(stripe); if (stripe) {
setLoading(false); setStripe(stripe);
setLoading(false);
}
}) })
.catch((error) => { .catch((error) => {
logError(error); logError(error);
// TODO: check setError -> cannot work with value as a string
setError(error.message); setError(error.message);
setLoading(false); setLoading(false);
}); });
}); });
}, []); }, []);
async function handleSubmit(event) { async function handleSubmit(event: any) {
event.preventDefault(); event.preventDefault();
if (loading) { if (loading) {
return; return;
@ -64,9 +73,9 @@ function Donate() {
} }
); );
const session = await response.json(); const session = await response.json();
const result = await stripe.redirectToCheckout({ sessionId: session.id }); const result = await stripe?.redirectToCheckout({ sessionId: session.id });
if (result.error) { if (result?.error) {
setError(result.error.message); setError(result.error.message);
} }
} }
@ -74,10 +83,11 @@ function Donate() {
const [selectedPrice, setSelectedPrice] = useState("Medium"); const [selectedPrice, setSelectedPrice] = useState("Medium");
const [value, setValue] = useState(15); const [value, setValue] = useState(15);
function handlePriceChange(price) { function handlePriceChange(price: Price) {
setValue(price.value); setValue(price.value);
setSelectedPrice(price.name); setSelectedPrice(price.name);
} }
return ( return (
<Flex <Flex
sx={{ sx={{
@ -149,7 +159,7 @@ function Donate() {
name="donation" name="donation"
min={1} min={1}
value={value} value={value}
onChange={(e) => setValue(e.target.value)} onChange={(e: any) => setValue(e.target.value)}
/> />
</Box> </Box>
)} )}
@ -159,7 +169,7 @@ function Donate() {
</Flex> </Flex>
<Footer /> <Footer />
{loading && <LoadingOverlay />} {loading && <LoadingOverlay />}
<ErrorBanner error={error} onRequestClose={() => setError()} /> <ErrorBanner error={error as Error} onRequestClose={() => setError(undefined)} />
</Flex> </Flex>
); );
} }

View File

@ -1,4 +1,3 @@
import React from "react";
import { Flex, Text, Box } from "theme-ui"; import { Flex, Text, Box } from "theme-ui";
import raw from "raw.macro"; import raw from "raw.macro";

View File

@ -25,7 +25,7 @@ import NetworkedParty from "../network/NetworkedParty";
import Session from "../network/Session"; import Session from "../network/Session";
function Game() { function Game() {
const { id: gameId } = useParams(); const { id: gameId }: { id: string } = useParams();
const { password } = useAuth(); const { password } = useAuth();
const { databaseStatus } = useDatabase(); const { databaseStatus } = useDatabase();
@ -44,9 +44,9 @@ function Game() {
}, [session]); }, [session]);
// Handle session errors // Handle session errors
const [peerError, setPeerError] = useState(null); const [peerError, setPeerError]: [ peerError: any, setPeerError: React.Dispatch<any>] = useState(null);
useEffect(() => { useEffect(() => {
function handlePeerError({ error }) { function handlePeerError({ error }: { error: any }) {
if (error.code === "ERR_WEBRTC_SUPPORT") { if (error.code === "ERR_WEBRTC_SUPPORT") {
setPeerError("WebRTC not supported."); setPeerError("WebRTC not supported.");
} else if (error.code === "ERR_CREATE_OFFER") { } else if (error.code === "ERR_CREATE_OFFER") {
@ -60,7 +60,7 @@ function Game() {
}, [session]); }, [session]);
useEffect(() => { useEffect(() => {
function handleStatus(status) { function handleStatus(status: any) {
setSessionStatus(status); setSessionStatus(status);
} }
@ -92,7 +92,7 @@ function Game() {
} }
}, [gameId, password, databaseStatus, session, sessionStatus]); }, [gameId, password, databaseStatus, session, sessionStatus]);
function handleAuthSubmit(newPassword) { function handleAuthSubmit(newPassword: string) {
if (databaseStatus !== "loading") { if (databaseStatus !== "loading") {
session.joinGame(gameId, newPassword); session.joinGame(gameId, newPassword);
} }
@ -100,7 +100,7 @@ function Game() {
// A ref to the Konva stage // A ref to the Konva stage
// the ref will be assigned in the MapInteraction component // the ref will be assigned in the MapInteraction component
const mapStageRef = useRef(); const mapStageRef: React.MutableRefObject<any> = useRef();
return ( return (
<PlayerProvider session={session}> <PlayerProvider session={session}>

View File

@ -1,4 +1,3 @@
import React from "react";
import { Flex, Text } from "theme-ui"; import { Flex, Text } from "theme-ui";
import raw from "raw.macro"; import raw from "raw.macro";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";

View File

@ -1,4 +1,3 @@
import React from "react";
import { Flex, Text } from "theme-ui"; import { Flex, Text } from "theme-ui";
import raw from "raw.macro"; import raw from "raw.macro";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";

View File

@ -1,5 +1,6 @@
{ {
"compilerOptions": { "compilerOptions": {
"outDir": "./dist",
"target": "es6", "target": "es6",
"lib": [ "lib": [
"dom", "dom",

View File

@ -2603,35 +2603,35 @@
"@svgr/plugin-svgo" "^5.5.0" "@svgr/plugin-svgo" "^5.5.0"
loader-utils "^2.0.0" loader-utils "^2.0.0"
"@tensorflow/tfjs-backend-cpu@3.3.0": "@tensorflow/tfjs-backend-cpu@3.6.0":
version "3.3.0" version "3.6.0"
resolved "https://registry.yarnpkg.com/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-3.3.0.tgz#aa0a3ed2c6237a6e0c169678c5bd4b5a88766b1c" resolved "https://registry.yarnpkg.com/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-3.6.0.tgz#4e64a7cf1c33b203f71f8f77cd7b0ac1ef25a871"
integrity sha512-DLctv+PUZni26kQW1hq8jwQQ8u+GGc/p764WQIC4/IDagGtfGAUW1mHzWcTxtni2l4re1VrwE41ogWLhv4sGHg== integrity sha512-ZpAs17hPdKXadbtNjAsymYUILe8V7+pY4fYo8j25nfDTW/HfBpyAwsHPbMcA/n5zyJ7ZJtGKFcCUv1sl24KL1Q==
dependencies: dependencies:
"@types/seedrandom" "2.4.27" "@types/seedrandom" "2.4.27"
seedrandom "2.4.3" seedrandom "2.4.3"
"@tensorflow/tfjs-backend-webgl@3.3.0": "@tensorflow/tfjs-backend-webgl@3.6.0":
version "3.3.0" version "3.6.0"
resolved "https://registry.yarnpkg.com/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-3.3.0.tgz#29dd665f6a856c9defcb9108164f845e1fdcd02e" resolved "https://registry.yarnpkg.com/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-3.6.0.tgz#1ea1a73abea8d6324fd81aedf7f187ab6eb73692"
integrity sha512-GWCtXbrjPTyye3ooId9GlcNDwnIMskZarUpNIQ5g/zeISLfwEQoutA/UqJF+HzuEHgGMsWFkmaO3xKVT7UMpdg== integrity sha512-zp7l4TmD1khgeSux/Ujaaj8M/v+e8JVIKjOci6HCGaeMNrn74lTSH9oqGPWKUCmpZME17/V0LfRHK34ddmrPSA==
dependencies: dependencies:
"@tensorflow/tfjs-backend-cpu" "3.3.0" "@tensorflow/tfjs-backend-cpu" "3.6.0"
"@types/offscreencanvas" "~2019.3.0" "@types/offscreencanvas" "~2019.3.0"
"@types/seedrandom" "2.4.27" "@types/seedrandom" "2.4.27"
"@types/webgl-ext" "0.0.30" "@types/webgl-ext" "0.0.30"
"@types/webgl2" "0.0.5" "@types/webgl2" "0.0.5"
seedrandom "2.4.3" seedrandom "2.4.3"
"@tensorflow/tfjs-converter@3.3.0": "@tensorflow/tfjs-converter@3.6.0":
version "3.3.0" version "3.6.0"
resolved "https://registry.yarnpkg.com/@tensorflow/tfjs-converter/-/tfjs-converter-3.3.0.tgz#d9f2ffd0fbdbb47c07d5fd7c3e5dc180cff317aa" resolved "https://registry.yarnpkg.com/@tensorflow/tfjs-converter/-/tfjs-converter-3.6.0.tgz#32b3ff31b47e29630a82e30fbe01708facad7fd6"
integrity sha512-k57wN4yelePhmO9orcT/wzGMIuyedrMpVtg0FhxpV6BQu0+TZ/ti3W4Kb97GWJsoHKXMoing9SnioKfVnBW6hw== integrity sha512-9MtatbTSvo3gpEulYI6+byTA3OeXSMT2lzyGAegXO9nMxsvjR01zBvlZ5SmsNyecNh6fMSzdL2+cCdQfQtsIBg==
"@tensorflow/tfjs-core@3.3.0": "@tensorflow/tfjs-core@3.6.0":
version "3.3.0" version "3.6.0"
resolved "https://registry.yarnpkg.com/@tensorflow/tfjs-core/-/tfjs-core-3.3.0.tgz#3d26bd03cb58e0ecf46c96d118c39c4a90b7f5ed" resolved "https://registry.yarnpkg.com/@tensorflow/tfjs-core/-/tfjs-core-3.6.0.tgz#6b4d8175790bdff78868eabe6adc6442eb4dc276"
integrity sha512-6G+LcCiQBl4Kza5mDbWbf8QSWBTW3l7SDjGhQzMO1ITtQatHzxkuHGHcJ4CTUJvNA0JmKf4QJWOvlFqEmxwyLQ== integrity sha512-bb2c3zwK4SgXZRvkTiC7EhCpWbCGp0GMd+1/3Vo2/Z54jiLB/h3sXIgHQrTNiWwhKPtst/xxA+MsslFlvD0A5w==
dependencies: dependencies:
"@types/offscreencanvas" "~2019.3.0" "@types/offscreencanvas" "~2019.3.0"
"@types/seedrandom" "2.4.27" "@types/seedrandom" "2.4.27"
@ -2639,30 +2639,30 @@
node-fetch "~2.6.1" node-fetch "~2.6.1"
seedrandom "2.4.3" seedrandom "2.4.3"
"@tensorflow/tfjs-data@3.3.0": "@tensorflow/tfjs-data@3.6.0":
version "3.3.0" version "3.6.0"
resolved "https://registry.yarnpkg.com/@tensorflow/tfjs-data/-/tfjs-data-3.3.0.tgz#ba943bd6a486fa4cb3ca312c12646ea4dcf6cce4" resolved "https://registry.yarnpkg.com/@tensorflow/tfjs-data/-/tfjs-data-3.6.0.tgz#af2f03cffb75ad8e4c2f46e192e392d9b7f977ed"
integrity sha512-0x28tRe6RJu5GmYq3IYN2GNnOgXU0nY+o6zZrlijkK+W3vjSTJlZzaBSifoeD6J8gzVpjs8W8qd/JKHQ1MQp8w== integrity sha512-5KU7fnU7cj/opb4aCNDoW4qma64ggDwI0PCs5KEO41T3waVHDLk6bjlFlBVRdjfZqvM0K6EfWEyoiXzdvz/Ieg==
dependencies: dependencies:
"@types/node-fetch" "^2.1.2" "@types/node-fetch" "^2.1.2"
node-fetch "~2.6.1" node-fetch "~2.6.1"
"@tensorflow/tfjs-layers@3.3.0": "@tensorflow/tfjs-layers@3.6.0":
version "3.3.0" version "3.6.0"
resolved "https://registry.yarnpkg.com/@tensorflow/tfjs-layers/-/tfjs-layers-3.3.0.tgz#d2097c5b22ec12e5fdbe470a88ca0a34a95ca11f" resolved "https://registry.yarnpkg.com/@tensorflow/tfjs-layers/-/tfjs-layers-3.6.0.tgz#5358af559fc8baed304b3e567319fe93f1aa46a6"
integrity sha512-qO+TL2I29vWUiuFcQJXNyayWFYagwR+SIfbex8p5jjYaCGHGwE5GQcrH+ngoCgKZxm5tdMvYJsJPnih2M3fYzQ== integrity sha512-B7EHwAT6KFqhKzdf0e2Sr6haj9qpqpyEATV8OCPHdk+g8z2AGXOLlFfbgW6vCMjy1wb5jzYqCyZDoY3EWdgJAw==
"@tensorflow/tfjs@^3.3.0": "@tensorflow/tfjs@^3.6.0":
version "3.3.0" version "3.6.0"
resolved "https://registry.yarnpkg.com/@tensorflow/tfjs/-/tfjs-3.3.0.tgz#db92099dd48c0eb1c1673f705125d2b57496a1a3" resolved "https://registry.yarnpkg.com/@tensorflow/tfjs/-/tfjs-3.6.0.tgz#e65956cd40c96523e3f5ec7a58a4bef9ef5e349c"
integrity sha512-xo22GCUCGcPtNGIdDpLPrp9ms3atXmzX8AF4y3aIBEwK5KlvGe+ZhcoQ2xEOCPQGBr7NB7AO6rwT8gRoziAHVg== integrity sha512-uLDMDzyRkJa3fYBeR6etQTFD/t+nkQIH/DznL9hxmYoIYG8PigY2gcrc482TAvsdhiuvxCZ9rl5SyDtP93MvxQ==
dependencies: dependencies:
"@tensorflow/tfjs-backend-cpu" "3.3.0" "@tensorflow/tfjs-backend-cpu" "3.6.0"
"@tensorflow/tfjs-backend-webgl" "3.3.0" "@tensorflow/tfjs-backend-webgl" "3.6.0"
"@tensorflow/tfjs-converter" "3.3.0" "@tensorflow/tfjs-converter" "3.6.0"
"@tensorflow/tfjs-core" "3.3.0" "@tensorflow/tfjs-core" "3.6.0"
"@tensorflow/tfjs-data" "3.3.0" "@tensorflow/tfjs-data" "3.6.0"
"@tensorflow/tfjs-layers" "3.3.0" "@tensorflow/tfjs-layers" "3.6.0"
argparse "^1.0.10" argparse "^1.0.10"
chalk "^4.1.0" chalk "^4.1.0"
core-js "3" core-js "3"
@ -2873,6 +2873,11 @@
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
"@types/file-saver@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@types/file-saver/-/file-saver-2.0.2.tgz#bd593ccfaee42ff94a5c1c83bf69ae9be83493b9"
integrity sha512-xbqnZmGrCEqi/KUzOkeUSe77p7APvLuyellGaAoeww3CHJ1AbjQWjPSCFtKIzZn8L7LpEax4NXnC+gfa6nM7IA==
"@types/glob@^7.1.1": "@types/glob@^7.1.1":
version "7.1.3" version "7.1.3"
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183"
@ -2943,6 +2948,13 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
"@types/lodash.clonedeep@^4.5.6":
version "4.5.6"
resolved "https://registry.yarnpkg.com/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.6.tgz#3b6c40a0affe0799a2ce823b440a6cf33571d32b"
integrity sha512-cE1jYr2dEg1wBImvXlNtp0xDoS79rfEdGozQVgliDZj1uERH4k+rmEMTudP9b4VQ8O6nRb5gPqft0QzEQGMQgA==
dependencies:
"@types/lodash" "*"
"@types/lodash.get@^4.4.6": "@types/lodash.get@^4.4.6":
version "4.4.6" version "4.4.6"
resolved "https://registry.yarnpkg.com/@types/lodash.get/-/lodash.get-4.4.6.tgz#0c7ac56243dae0f9f09ab6f75b29471e2e777240" resolved "https://registry.yarnpkg.com/@types/lodash.get/-/lodash.get-4.4.6.tgz#0c7ac56243dae0f9f09ab6f75b29471e2e777240"