From bfd05292073ad28e01bab39b24f78bc834e76dc7 Mon Sep 17 00:00:00 2001 From: Nicola Thouliss Date: Thu, 3 Jun 2021 15:31:18 +1000 Subject: [PATCH] Added all files successfully converted --- package.json | 4 +- src/components/dice/DiceTrayOverlay.js | 4 +- src/components/map/Map.tsx | 13 +- ...oadingOverlay.js => MapLoadingOverlay.tsx} | 9 +- ...mberButton.js => AddPartyMemberButton.tsx} | 2 +- ...nameButton.js => ChangeNicknameButton.tsx} | 10 +- .../party/{DiceRoll.js => DiceRoll.tsx} | 7 +- .../party/{DiceRolls.js => DiceRolls.tsx} | 14 ++- .../{DiceTrayButton.js => DiceTrayButton.tsx} | 6 +- .../party/{Nickname.js => Nickname.tsx} | 4 +- src/components/party/{Party.js => Party.tsx} | 36 +++--- src/components/party/PartyState.ts | 39 ++++++ ...tStreamButton.js => StartStreamButton.tsx} | 10 +- ...artTimerButton.js => StartTimerButton.tsx} | 2 +- .../party/{Stream.js => Stream.tsx} | 19 +-- src/components/party/{Timer.js => Timer.tsx} | 6 +- .../{AuthContext.js => AuthContext.tsx} | 20 +-- ...DatabaseContext.js => DatabaseContext.tsx} | 25 ++-- ...adingContext.js => DiceLoadingContext.tsx} | 14 ++- .../{GridContext.js => GridContext.tsx} | 17 ++- ...ourceContext.js => ImageSourceContext.tsx} | 33 ++--- .../{MapDataContext.js => MapDataContext.tsx} | 117 +++++++++++------- ...onContext.js => MapInteractionContext.tsx} | 20 +-- ...oadingContext.js => MapLoadingContext.tsx} | 12 +- ...MapStageContext.js => MapStageContext.tsx} | 2 +- .../{PartyContext.js => PartyContext.tsx} | 8 +- .../{PlayerContext.js => PlayerContext.tsx} | 23 ++-- ...SettingsContext.js => SettingsContext.tsx} | 4 +- src/dice/{Dice.js => Dice.ts} | 48 ++++--- .../diceTray/{DiceTray.js => DiceTray.ts} | 41 ++++-- .../galaxy/{GalaxyDice.js => GalaxyDice.ts} | 12 +- .../{GemstoneDice.js => GemstoneDice.ts} | 18 +-- src/dice/glass/{GlassDice.js => GlassDice.ts} | 18 +-- src/dice/{index.js => index.ts} | 9 +- src/dice/iron/{IronDice.js => IronDice.ts} | 13 +- .../nebula/{NebulaDice.js => NebulaDice.ts} | 11 +- .../{SunriseDice.js => SunriseDice.ts} | 11 +- .../sunset/{SunsetDice.js => SunsetDice.ts} | 11 +- .../walnut/{WalnutDice.js => WalnutDice.ts} | 13 +- src/helpers/babylon.ts | 2 +- src/hooks/{useDebounce.js => useDebounce.tsx} | 2 +- ...etworkedState.js => useNetworkedState.tsx} | 26 ++-- src/maps/index.ts | 3 +- src/modals/EditMapModal.tsx | 2 +- src/modals/SelectMapModal.tsx | 12 +- src/modals/StartStreamModal.tsx | 4 +- ...AndTokens.js => NetworkedMapAndTokens.tsx} | 115 ++++++++--------- ...dMapPointer.js => NetworkedMapPointer.tsx} | 27 ++-- .../{NetworkedParty.js => NetworkedParty.tsx} | 52 ++++---- src/network/Session.ts | 4 +- src/routes/{About.js => About.tsx} | 1 - src/routes/{Donate.js => Donate.tsx} | 34 +++-- src/routes/{FAQ.js => FAQ.tsx} | 1 - src/routes/{Game.js => Game.tsx} | 12 +- src/routes/{HowTo.js => HowTo.tsx} | 1 - .../{ReleaseNotes.js => ReleaseNotes.tsx} | 1 - tsconfig.json | 1 + yarn.lock | 82 ++++++------ 58 files changed, 622 insertions(+), 445 deletions(-) rename src/components/map/{MapLoadingOverlay.js => MapLoadingOverlay.tsx} (92%) rename src/components/party/{AddPartyMemberButton.js => AddPartyMemberButton.tsx} (92%) rename src/components/party/{ChangeNicknameButton.js => ChangeNicknameButton.tsx} (77%) rename src/components/party/{DiceRoll.js => DiceRoll.tsx} (60%) rename src/components/party/{DiceRolls.js => DiceRolls.tsx} (87%) rename src/components/party/{DiceTrayButton.js => DiceTrayButton.tsx} (92%) rename src/components/party/{Nickname.js => Nickname.tsx} (75%) rename src/components/party/{Party.js => Party.tsx} (79%) create mode 100644 src/components/party/PartyState.ts rename src/components/party/{StartStreamButton.js => StartStreamButton.tsx} (88%) rename src/components/party/{StartTimerButton.js => StartTimerButton.tsx} (87%) rename src/components/party/{Stream.js => Stream.tsx} (90%) rename src/components/party/{Timer.js => Timer.tsx} (91%) rename src/contexts/{AuthContext.js => AuthContext.tsx} (60%) rename src/contexts/{DatabaseContext.js => DatabaseContext.tsx} (69%) rename src/contexts/{DiceLoadingContext.js => DiceLoadingContext.tsx} (65%) rename src/contexts/{GridContext.js => GridContext.tsx} (93%) rename src/contexts/{ImageSourceContext.js => ImageSourceContext.tsx} (77%) rename src/contexts/{MapDataContext.js => MapDataContext.tsx} (69%) rename src/contexts/{MapInteractionContext.js => MapInteractionContext.tsx} (82%) rename src/contexts/{MapLoadingContext.js => MapLoadingContext.tsx} (80%) rename src/contexts/{MapStageContext.js => MapStageContext.tsx} (85%) rename src/contexts/{PartyContext.js => PartyContext.tsx} (71%) rename src/contexts/{PlayerContext.js => PlayerContext.tsx} (78%) rename src/contexts/{SettingsContext.js => SettingsContext.tsx} (86%) rename src/dice/{Dice.js => Dice.ts} (67%) rename src/dice/diceTray/{DiceTray.js => DiceTray.ts} (84%) rename src/dice/galaxy/{GalaxyDice.js => GalaxyDice.ts} (68%) rename src/dice/gemstone/{GemstoneDice.js => GemstoneDice.ts} (79%) rename src/dice/glass/{GlassDice.js => GlassDice.ts} (80%) rename src/dice/{index.js => index.ts} (87%) rename src/dice/iron/{IronDice.js => IronDice.ts} (73%) rename src/dice/nebula/{NebulaDice.js => NebulaDice.ts} (73%) rename src/dice/sunrise/{SunriseDice.js => SunriseDice.ts} (73%) rename src/dice/sunset/{SunsetDice.js => SunsetDice.ts} (73%) rename src/dice/walnut/{WalnutDice.js => WalnutDice.ts} (80%) rename src/hooks/{useDebounce.js => useDebounce.tsx} (86%) rename src/hooks/{useNetworkedState.js => useNetworkedState.tsx} (85%) rename src/network/{NetworkedMapAndTokens.js => NetworkedMapAndTokens.tsx} (81%) rename src/network/{NetworkedMapPointer.js => NetworkedMapPointer.tsx} (86%) rename src/network/{NetworkedParty.js => NetworkedParty.tsx} (69%) rename src/routes/{About.js => About.tsx} (98%) rename src/routes/{Donate.js => Donate.tsx} (80%) rename src/routes/{FAQ.js => FAQ.tsx} (98%) rename src/routes/{Game.js => Game.tsx} (92%) rename src/routes/{HowTo.js => HowTo.tsx} (99%) rename src/routes/{ReleaseNotes.js => ReleaseNotes.tsx} (99%) diff --git a/package.json b/package.json index b83f00b..6f426c6 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "@msgpack/msgpack": "^2.4.1", "@sentry/react": "^6.2.2", "@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/react": "^11.2.5", "@testing-library/user-event": "^13.0.2", @@ -87,7 +87,9 @@ "devDependencies": { "@types/color": "^3.0.1", "@types/deep-diff": "^1.0.0", + "@types/file-saver": "^2.0.2", "@types/jest": "^26.0.23", + "@types/lodash.clonedeep": "^4.5.6", "@types/lodash.get": "^4.4.6", "@types/node": "^15.6.0", "@types/react": "^17.0.6", diff --git a/src/components/dice/DiceTrayOverlay.js b/src/components/dice/DiceTrayOverlay.js index 62028a3..2c19088 100644 --- a/src/components/dice/DiceTrayOverlay.js +++ b/src/components/dice/DiceTrayOverlay.js @@ -23,7 +23,7 @@ import useSetting from "../../hooks/useSetting"; function DiceTrayOverlay({ isOpen, shareDice, - onShareDiceChage, + onShareDiceChange, diceRolls, onDiceRollsChange, }) { @@ -345,7 +345,7 @@ function DiceTrayOverlay({ onDiceTraySizeChange={setDiceTraySize} diceTraySize={diceTraySize} shareDice={shareDice} - onShareDiceChange={onShareDiceChage} + onShareDiceChange={onShareDiceChange} loading={isLoading} /> {isLoading && ( diff --git a/src/components/map/Map.tsx b/src/components/map/Map.tsx index 8f8f539..90448d5 100644 --- a/src/components/map/Map.tsx +++ b/src/components/map/Map.tsx @@ -29,15 +29,14 @@ import Session from "../../network/Session"; import { Grid } from "../../helpers/grid"; import { ImageFile } from "../../helpers/image"; -type Resolutions = Record - +export type Resolutions = Record export type Map = { id: string, name: string, owner: string, - file: Uint8Array, - quality: string, - resolutions: Resolutions, + file?: Uint8Array, + quality?: string, + resolutions?: Resolutions, grid: Grid, group: string, width: number, @@ -48,7 +47,7 @@ export type Map = { created: number, showGrid: boolean, snapToGrid: boolean, - thumbnail: ImageFile, + thumbnail?: ImageFile, } export type Note = { @@ -92,7 +91,7 @@ export type MapState = { tokens: Record, drawShapes: PathId | ShapeId, fogShapes: Fog[], - editFlags: string[], + editFlags: ["drawing", "tokens", "notes", "fog"], notes: Note[], mapId: string, } diff --git a/src/components/map/MapLoadingOverlay.js b/src/components/map/MapLoadingOverlay.tsx similarity index 92% rename from src/components/map/MapLoadingOverlay.js rename to src/components/map/MapLoadingOverlay.tsx index e3b77b6..9c191b3 100644 --- a/src/components/map/MapLoadingOverlay.js +++ b/src/components/map/MapLoadingOverlay.tsx @@ -1,4 +1,3 @@ -import React from "react"; import { Box } from "theme-ui"; import { useMapLoading } from "../../contexts/MapLoadingContext"; @@ -8,8 +7,11 @@ import LoadingBar from "../LoadingBar"; function MapLoadingOverlay() { const { isLoading, loadingProgressRef } = useMapLoading(); + if (!isLoading) { + return null; + } + return ( - isLoading && ( - ) - ); + ); } export default MapLoadingOverlay; diff --git a/src/components/party/AddPartyMemberButton.js b/src/components/party/AddPartyMemberButton.tsx similarity index 92% rename from src/components/party/AddPartyMemberButton.js rename to src/components/party/AddPartyMemberButton.tsx index b2932b1..4e80e1d 100644 --- a/src/components/party/AddPartyMemberButton.js +++ b/src/components/party/AddPartyMemberButton.tsx @@ -4,7 +4,7 @@ import { IconButton } from "theme-ui"; import AddPartyMemberModal from "../../modals/AddPartyMemberModal"; import AddPartyMemberIcon from "../../icons/AddPartyMemberIcon"; -function AddPartyMemberButton({ gameId }) { +function AddPartyMemberButton({ gameId }: { gameId: string }) { const [isAddModalOpen, setIsAddModalOpen] = useState(false); function openModal() { setIsAddModalOpen(true); diff --git a/src/components/party/ChangeNicknameButton.js b/src/components/party/ChangeNicknameButton.tsx similarity index 77% rename from src/components/party/ChangeNicknameButton.js rename to src/components/party/ChangeNicknameButton.tsx index 20231af..be599e3 100644 --- a/src/components/party/ChangeNicknameButton.js +++ b/src/components/party/ChangeNicknameButton.tsx @@ -1,10 +1,10 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, ChangeEvent } from "react"; import { IconButton } from "theme-ui"; import ChangeNicknameModal from "../../modals/ChangeNicknameModal"; import ChangeNicknameIcon from "../../icons/ChangeNicknameIcon"; -function ChangeNicknameButton({ nickname, onChange }) { +function ChangeNicknameButton({ nickname, onChange }: { nickname: string, onChange: any}) { const [isChangeModalOpen, setIsChangeModalOpen] = useState(false); function openModal() { setIsChangeModalOpen(true); @@ -19,14 +19,14 @@ function ChangeNicknameButton({ nickname, onChange }) { setChangedNickname(nickname); }, [nickname]); - function handleChangeSubmit(event) { + function handleChangeSubmit(event: Event) { event.preventDefault(); onChange(changedNickname); closeModal(); } - function handleChange(event) { - setChangedNickname(event.target.value); + function handleChange(event: ChangeEvent) { + setChangedNickname(event.target?.value); } return ( <> diff --git a/src/components/party/DiceRoll.js b/src/components/party/DiceRoll.tsx similarity index 60% rename from src/components/party/DiceRoll.js rename to src/components/party/DiceRoll.tsx index f04d11b..e770205 100644 --- a/src/components/party/DiceRoll.js +++ b/src/components/party/DiceRoll.tsx @@ -1,13 +1,12 @@ -import React from "react"; import { Flex, Box, Text } from "theme-ui"; -function DiceRoll({ rolls, type, children }) { +function DiceRoll({ rolls, type, children }: { rolls: any, type: string, children: any}) { return ( {children} {rolls - .filter((d) => d.type === type && d.roll !== "unknown") - .map((dice, index) => ( + .filter((d: any) => d.type === type && d.roll !== "unknown") + .map((dice: any, index: string | number) => ( {dice.roll} diff --git a/src/components/party/DiceRolls.js b/src/components/party/DiceRolls.tsx similarity index 87% rename from src/components/party/DiceRolls.js rename to src/components/party/DiceRolls.tsx index b20b3bd..3798595 100644 --- a/src/components/party/DiceRolls.js +++ b/src/components/party/DiceRolls.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import { useState } from "react"; import { Flex, Text, IconButton } from "theme-ui"; import DiceRollsIcon from "../../icons/DiceRollsIcon"; @@ -24,14 +24,14 @@ const diceIcons = [ { type: "d100", Icon: D100Icon }, ]; -function DiceRolls({ rolls }) { +function DiceRolls({ rolls }: { rolls: any }) { const total = getDiceRollTotal(rolls); - const [expanded, setExpanded] = useState(false); + const [expanded, setExpanded] = useState(false); let expandedRolls = []; for (let icon of diceIcons) { - if (rolls.some((roll) => roll.type === icon.type)) { + if (rolls.some((roll: any) => roll.type === icon.type)) { expandedRolls.push( @@ -40,8 +40,11 @@ function DiceRolls({ rolls }) { } } + if (total <= 0) { + return null; + } + return ( - total > 0 && ( )} - ) ); } diff --git a/src/components/party/DiceTrayButton.js b/src/components/party/DiceTrayButton.tsx similarity index 92% rename from src/components/party/DiceTrayButton.js rename to src/components/party/DiceTrayButton.tsx index d4591a9..1a00263 100644 --- a/src/components/party/DiceTrayButton.js +++ b/src/components/party/DiceTrayButton.tsx @@ -13,10 +13,10 @@ const DiceTrayOverlay = React.lazy(() => import("../dice/DiceTrayOverlay")); function DiceTrayButton({ shareDice, - onShareDiceChage, + onShareDiceChange, diceRolls, onDiceRollsChange, -}) { +}: { shareDice: boolean, onShareDiceChange: any, diceRolls: [], onDiceRollsChange: any}) { const [isExpanded, setIsExpanded] = useState(false); const [fullScreen] = useSetting("map.fullScreen"); @@ -69,7 +69,7 @@ function DiceTrayButton({ diff --git a/src/components/party/Nickname.js b/src/components/party/Nickname.tsx similarity index 75% rename from src/components/party/Nickname.js rename to src/components/party/Nickname.tsx index 12cdd33..00b316a 100644 --- a/src/components/party/Nickname.js +++ b/src/components/party/Nickname.tsx @@ -1,10 +1,10 @@ -import React from "react"; import { Text, Flex } from "theme-ui"; import Stream from "./Stream"; 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 ( ({ ...prevState, timer: newTimer })); + function handleTimerStart(newTimer: PartyTimer) { + setPlayerState((prevState: any) => ({ ...prevState, timer: newTimer })); } function handleTimerStop() { - setPlayerState((prevState) => ({ ...prevState, timer: null })); + setPlayerState((prevState: any) => ({ ...prevState, timer: null })); } useEffect(() => { let prevTime = performance.now(); let request = requestAnimationFrame(update); let counter = 0; - function update(time) { + function update(time: any) { request = requestAnimationFrame(update); const deltaTime = time - prevTime; prevTime = time; @@ -45,14 +46,14 @@ function Party({ gameId, stream, partyStreams, onStreamStart, onStreamEnd }) { counter += deltaTime; // Update timer every second if (counter > 1000) { - const newTimer = { + const newTimer: PartyTimer = { ...playerState.timer, current: playerState.timer.current - counter, }; if (newTimer.current < 0) { - setPlayerState((prevState) => ({ ...prevState, timer: null })); + setPlayerState((prevState: any) => ({ ...prevState, timer: null })); } else { - setPlayerState((prevState) => ({ ...prevState, timer: newTimer })); + setPlayerState((prevState: any) => ({ ...prevState, timer: newTimer })); } counter = 0; } @@ -63,13 +64,13 @@ function Party({ gameId, stream, partyStreams, onStreamStart, onStreamEnd }) { }; }, [playerState.timer, setPlayerState]); - function handleNicknameChange(newNickname) { - setPlayerState((prevState) => ({ ...prevState, nickname: newNickname })); + function handleNicknameChange(newNickname: string) { + setPlayerState((prevState: any) => ({ ...prevState, nickname: newNickname })); } - function handleDiceRollsChange(newDiceRolls) { + function handleDiceRollsChange(newDiceRolls: number[]) { setPlayerState( - (prevState) => ({ + (prevState: PlayerDice) => ({ ...prevState, 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); - setPlayerState((prevState) => ({ + setPlayerState((prevState: PlayerInfo) => ({ ...prevState, dice: { ...prevState.dice, share: newShareDice }, })); @@ -122,6 +123,7 @@ function Party({ gameId, stream, partyStreams, onStreamStart, onStreamEnd }) { height: "calc(100% - 232px)", }} > + {/* TODO: check if stream is required here */} diff --git a/src/components/party/PartyState.ts b/src/components/party/PartyState.ts new file mode 100644 index 0000000..194de30 --- /dev/null +++ b/src/components/party/PartyState.ts @@ -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 } \ No newline at end of file diff --git a/src/components/party/StartStreamButton.js b/src/components/party/StartStreamButton.tsx similarity index 88% rename from src/components/party/StartStreamButton.js rename to src/components/party/StartStreamButton.tsx index df41e9d..23e8de0 100644 --- a/src/components/party/StartStreamButton.js +++ b/src/components/party/StartStreamButton.tsx @@ -6,7 +6,7 @@ import Link from "../Link"; 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); function openModal() { setIsStreamModalOpen(true); @@ -44,7 +44,9 @@ function StartStreamButton({ onStreamStart, onStreamEnd, stream }) { const [noAudioTrack, setNoAudioTrack] = useState(false); 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({ video: true, audio: { @@ -53,10 +55,10 @@ function StartStreamButton({ onStreamStart, onStreamEnd, stream }) { echoCancellation: false, }, }) - .then((localStream) => { + .then((localStream: { getTracks: () => any; }) => { const tracks = localStream.getTracks(); - const hasAudio = tracks.some((track) => track.kind === "audio"); + const hasAudio = tracks.some((track: { kind: string; }) => track.kind === "audio"); setNoAudioTrack(!hasAudio); // Ensure an audio track is present diff --git a/src/components/party/StartTimerButton.js b/src/components/party/StartTimerButton.tsx similarity index 87% rename from src/components/party/StartTimerButton.js rename to src/components/party/StartTimerButton.tsx index 266ba6f..3c413fc 100644 --- a/src/components/party/StartTimerButton.js +++ b/src/components/party/StartTimerButton.tsx @@ -4,7 +4,7 @@ import { IconButton } from "theme-ui"; import StartTimerModal from "../../modals/StartTimerModal"; 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); function openModal() { diff --git a/src/components/party/Stream.js b/src/components/party/Stream.tsx similarity index 90% rename from src/components/party/Stream.js rename to src/components/party/Stream.tsx index 631f4e1..6dc5eec 100644 --- a/src/components/party/Stream.js +++ b/src/components/party/Stream.tsx @@ -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 StreamMuteIcon from "../../icons/StreamMuteIcon"; @@ -6,13 +6,13 @@ import StreamMuteIcon from "../../icons/StreamMuteIcon"; import Banner from "../banner/Banner"; import Slider from "../Slider"; -function Stream({ stream, nickname }) { +function Stream({ stream, nickname }: { stream: MediaStream, nickname: string }) { const [streamVolume, setStreamVolume] = useState(1); const [showStreamInteractBanner, setShowStreamInteractBanner] = useState( false ); const [streamMuted, setStreamMuted] = useState(false); - const audioRef = useRef(); + const audioRef = useRef(); useEffect(() => { if (audioRef.current) { @@ -44,8 +44,8 @@ function Stream({ stream, nickname }) { } } - function handleVolumeChange(event) { - const volume = parseFloat(event.target.value); + function handleVolumeChange(event: ChangeEvent) { + const volume = parseFloat(event.target?.value); setStreamVolume(volume); } @@ -63,7 +63,8 @@ function Stream({ stream, nickname }) { setTimeout(() => { setIsVolumeControlAvailable(audio.volume === 0.5); audio.volume = prevVolume; - }, [100]); + // TODO: check if this supposed to be a number or number[] + }, 100); } 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% - const audioGainRef = useRef(); + const audioGainRef = useRef(); useEffect(() => { - let audioContext; - if (stream && !streamMuted && isVolumeControlAvailable) { + let audioContext: AudioContext; + if (stream && !streamMuted && isVolumeControlAvailable && audioGainRef) { audioContext = new AudioContext(); let source = audioContext.createMediaStreamSource(stream); let gainNode = audioContext.createGain(); diff --git a/src/components/party/Timer.js b/src/components/party/Timer.tsx similarity index 91% rename from src/components/party/Timer.js rename to src/components/party/Timer.tsx index 51b576a..3f208ce 100644 --- a/src/components/party/Timer.js +++ b/src/components/party/Timer.tsx @@ -4,8 +4,8 @@ import { Box, Progress } from "theme-ui"; import usePortal from "../../hooks/usePortal"; -function Timer({ timer, index }) { - const progressBarRef = useRef(); +function Timer({ timer, index }: { timer: any, index: number}) { + const progressBarRef = useRef(); useEffect(() => { if (progressBarRef.current && timer) { @@ -16,7 +16,7 @@ function Timer({ timer, index }) { useEffect(() => { let request = requestAnimationFrame(animate); let previousTime = performance.now(); - function animate(time) { + function animate(time: any) { request = requestAnimationFrame(animate); const deltaTime = time - previousTime; previousTime = time; diff --git a/src/contexts/AuthContext.js b/src/contexts/AuthContext.tsx similarity index 60% rename from src/contexts/AuthContext.js rename to src/contexts/AuthContext.tsx index 868fe7e..70f44ea 100644 --- a/src/contexts/AuthContext.js +++ b/src/contexts/AuthContext.tsx @@ -1,13 +1,16 @@ -import React, { useState, useEffect, useContext } from "react"; +import React, { useState, useEffect, useContext, SetStateAction } from "react"; import shortid from "shortid"; import { useDatabase } from "./DatabaseContext"; import FakeStorage from "../helpers/FakeStorage"; -const AuthContext = React.createContext(); +type AuthContext = { userId: string; password: string; setPassword: React.Dispatch; } -let storage; +// TODO: check what default value we want here +const AuthContext = React.createContext(undefined); + +let storage: Storage | FakeStorage; try { sessionStorage.setItem("__test", "__test"); sessionStorage.removeItem("__test"); @@ -17,28 +20,29 @@ try { storage = new FakeStorage(); } -export function AuthProvider({ children }) { +export function AuthProvider({ children }: { children: any }) { const { database, databaseStatus } = useDatabase(); - const [password, setPassword] = useState(storage.getItem("auth") || ""); + const [password, setPassword] = useState(storage.getItem("auth") || ""); useEffect(() => { storage.setItem("auth", password); }, [password]); - const [userId, setUserId] = useState(); + // TODO: check pattern here -> undefined or empty default values + const [userId, setUserId]: [ userId: string, setUserId: React.Dispatch> ] = useState(""); useEffect(() => { if (!database || databaseStatus === "loading") { return; } async function loadUserId() { - const storedUserId = await database.table("user").get("userId"); + const storedUserId = await database?.table("user").get("userId"); if (storedUserId) { setUserId(storedUserId.value); } else { const id = shortid.generate(); setUserId(id); - database.table("user").add({ key: "userId", value: id }); + database?.table("user").add({ key: "userId", value: id }); } } diff --git a/src/contexts/DatabaseContext.js b/src/contexts/DatabaseContext.tsx similarity index 69% rename from src/contexts/DatabaseContext.js rename to src/contexts/DatabaseContext.tsx index 5e1e3b5..8a9ee08 100644 --- a/src/contexts/DatabaseContext.js +++ b/src/contexts/DatabaseContext.tsx @@ -1,20 +1,25 @@ -import React, { useState, useEffect, useContext } from "react"; -import * as Comlink from "comlink"; +import React, { useState, useEffect, useContext, SetStateAction } from "react"; +import Comlink, { Remote } from "comlink"; import ErrorBanner from "../components/banner/ErrorBanner"; import { getDatabase } from "../database"; +//@ts-ignore 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; } + +// TODO: check what default we want here +const DatabaseContext = React.createContext< DatabaseContext | undefined>(undefined); const worker = Comlink.wrap(new DatabaseWorker()); -export function DatabaseProvider({ children }) { - const [database, setDatabase] = useState(); - const [databaseStatus, setDatabaseStatus] = useState("loading"); - const [databaseError, setDatabaseError] = useState(); +export function DatabaseProvider({ children }: { children: any}) { + const [database, setDatabase]: [ database: Dexie | undefined, setDatabase: React.Dispatch>] = useState(); + const [databaseStatus, setDatabaseStatus]: [ datebaseStatus: any, setDatabaseStatus: React.Dispatch>] = useState("loading"); + const [databaseError, setDatabaseError]: [ databaseError: Error | undefined, setDatabaseError: React.Dispatch>] = useState(); useEffect(() => { // 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"); }; - function handleDatabaseError(event) { + function handleDatabaseError(event: any) { event.preventDefault(); if (event.reason?.message.startsWith("QuotaExceededError")) { setDatabaseError({ @@ -77,14 +82,14 @@ export function DatabaseProvider({ children }) { {children} setDatabaseError()} + onRequestClose={() => setDatabaseError(undefined)} /> ); } -export function useDatabase() { +export function useDatabase(): DatabaseContext { const context = useContext(DatabaseContext); if (context === undefined) { throw new Error("useDatabase must be used within a DatabaseProvider"); diff --git a/src/contexts/DiceLoadingContext.js b/src/contexts/DiceLoadingContext.tsx similarity index 65% rename from src/contexts/DiceLoadingContext.js rename to src/contexts/DiceLoadingContext.tsx index 8bc9ed3..5f6fc43 100644 --- a/src/contexts/DiceLoadingContext.js +++ b/src/contexts/DiceLoadingContext.tsx @@ -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(undefined); + +export function DiceLoadingProvider({ children }: { children: ReactChild }) { const [loadingAssetCount, setLoadingAssetCount] = useState(0); function assetLoadStart() { @@ -28,7 +34,7 @@ export function DiceLoadingProvider({ children }) { ); } -export function useDiceLoading() { +export function useDiceLoading(): DiceLoadingContext { const context = useContext(DiceLoadingContext); if (context === undefined) { throw new Error("useDiceLoading must be used within a DiceLoadingProvider"); diff --git a/src/contexts/GridContext.js b/src/contexts/GridContext.tsx similarity index 93% rename from src/contexts/GridContext.js rename to src/contexts/GridContext.tsx index a4b173f..f7390d1 100644 --- a/src/contexts/GridContext.js +++ b/src/contexts/GridContext.tsx @@ -15,11 +15,20 @@ import { getGridPixelSize, getCellPixelSize, Grid } from "../helpers/grid"; * @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 */ +type GridContextValue = { + grid: Grid, + gridPixelSize: Size, + gridCellPixelSize: Size, + gridCellNormalizedSize: Size, + gridOffset: Vector2, + gridStrokeWidth: number, + gridCellPixelOffset: Vector2 +} /** * @type {GridContextValue} */ -const defaultValue = { +const defaultValue: GridContextValue = { grid: { size: new Vector2(0, 0), inset: { topLeft: new Vector2(0, 0), bottomRight: new Vector2(1, 1) }, @@ -57,11 +66,11 @@ export const GridCellPixelOffsetContext = React.createContext( const defaultStrokeWidth = 1 / 10; -export function GridProvider({ grid: inputGrid, width, height, children }) { +export function GridProvider({ grid: inputGrid, width, height, children }: { grid: Required, width: number, height: number, children: any }) { let grid = inputGrid; - if (!grid?.size.x || !grid?.size.y) { - grid = defaultValue.grid; + if (!grid.size.x || !grid.size.y) { + grid = defaultValue.grid as Required; } const [gridPixelSize, setGridPixelSize] = useState( diff --git a/src/contexts/ImageSourceContext.js b/src/contexts/ImageSourceContext.tsx similarity index 77% rename from src/contexts/ImageSourceContext.js rename to src/contexts/ImageSourceContext.tsx index b558383..c566300 100644 --- a/src/contexts/ImageSourceContext.js +++ b/src/contexts/ImageSourceContext.tsx @@ -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"; -export const ImageSourcesStateContext = React.createContext(); -export const ImageSourcesUpdaterContext = React.createContext(() => {}); +export const ImageSourcesStateContext = React.createContext(undefined) as any; +export const ImageSourcesUpdaterContext = React.createContext(() => {}) as any; /** * Helper to manage sharing of custom image sources between uses of useImageSource */ -export function ImageSourcesProvider({ children }) { - const [imageSources, setImageSources] = useState({}); +export function ImageSourcesProvider({ children }: { children: ReactChild }) { + const [imageSources, setImageSources] = useState({}); // Revoke url when no more references useEffect(() => { - let sourcesToCleanup = []; - for (let source of Object.values(imageSources)) { + let sourcesToCleanup: any = []; + for (let source of Object.values(imageSources) as any) { if (source.references <= 0) { URL.revokeObjectURL(source.url); sourcesToCleanup.push(source.id); } } if (sourcesToCleanup.length > 0) { - setImageSources((prevSources) => omit(prevSources, sourcesToCleanup)); + setImageSources((prevSources: any) => omit(prevSources, sourcesToCleanup)); } }, [imageSources]); @@ -37,7 +38,7 @@ export function ImageSourcesProvider({ children }) { /** * Get id from image data */ -function getImageFileId(data, thumbnail) { +function getImageFileId(data: any, thumbnail: ImageFile) { if (thumbnail) { return `${data.id}-thumbnail`; } @@ -48,7 +49,7 @@ function getImageFileId(data, thumbnail) { } else if (!data.file) { // Fallback to the highest resolution const resolutionArray = Object.keys(data.resolutions); - const resolution = resolutionArray[resolutionArray.length - 1]; + const resolution: any = resolutionArray[resolutionArray.length - 1]; 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 */ -export function useImageSource(data, defaultSources, unknownSource, thumbnail) { - const imageSources = useContext(ImageSourcesStateContext); +export function useImageSource(data: any, defaultSources: string, unknownSource: string, thumbnail: ImageFile) { + const imageSources: any = useContext(ImageSourcesStateContext); if (imageSources === undefined) { throw new Error( "useImageSource must be used within a ImageSourcesProvider" ); } - const setImageSources = useContext(ImageSourcesUpdaterContext); + const setImageSources: any = useContext(ImageSourcesUpdaterContext); if (setImageSources === undefined) { throw new Error( "useImageSource must be used within a ImageSourcesProvider" @@ -78,9 +79,9 @@ export function useImageSource(data, defaultSources, unknownSource, thumbnail) { } const id = getImageFileId(data, thumbnail); - function updateImageSource(file) { + function updateImageSource(file: File) { if (file) { - setImageSources((prevSources) => { + setImageSources((prevSources: any) => { if (id in prevSources) { // Check if the image source is already added return { @@ -124,7 +125,7 @@ export function useImageSource(data, defaultSources, unknownSource, thumbnail) { return () => { // Decrease references - setImageSources((prevSources) => { + setImageSources((prevSources: any) => { if (id in prevSources) { return { ...prevSources, diff --git a/src/contexts/MapDataContext.js b/src/contexts/MapDataContext.tsx similarity index 69% rename from src/contexts/MapDataContext.js rename to src/contexts/MapDataContext.tsx index ddd8dce..1f6f83e 100644 --- a/src/contexts/MapDataContext.js +++ b/src/contexts/MapDataContext.tsx @@ -4,6 +4,7 @@ import React, { useContext, useCallback, useRef, + ReactChild, } from "react"; import * as Comlink from "comlink"; import { decode, encode } from "@msgpack/msgpack"; @@ -12,42 +13,66 @@ import { useAuth } from "./AuthContext"; import { useDatabase } from "./DatabaseContext"; 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, + ownedMaps: Array + mapStates: MapState[], + addMap: (map: Map) => void, + removeMap: (id: string) => void, + removeMaps: (ids: string[]) => void, + resetMap: (id: string) => void, + updateMap: (id: string, update: Partial) => void, + updateMaps: (ids: string[], update: Partial) => void, + updateMapState: (id: string, update: Partial) => void, + putMap: (map: Map) => void, + getMap: (id: string) => Map | undefined, + getMapFromDB: (id: string) => Promise, + mapsLoading: boolean, + getMapStateFromDB: (id: string) => Promise, +} + +const MapDataContext = React.createContext(undefined); // Maximum number of maps to keep in the cache const cachedMapMax = 15; -const defaultMapState = { - tokens: {}, - drawShapes: {}, - fogShapes: {}, +const defaultMapState: MapState = { + mapId: "", + tokens: {} as Record, + drawShapes: {} as any, + fogShapes: {} as Fog[], // Flags to determine what other people can edit - editFlags: ["drawing", "tokens", "notes"], - notes: {}, + editFlags: ["drawing", "tokens", "notes", "fog"], + notes: {} as Note[], }; -export function MapDataProvider({ children }) { +export function MapDataProvider({ children }: { children: ReactChild }) { const { database, databaseStatus, worker } = useDatabase(); const { userId } = useAuth(); - const [maps, setMaps] = useState([]); - const [mapStates, setMapStates] = useState([]); - const [mapsLoading, setMapsLoading] = useState(true); + const [maps, setMaps] = useState>([]); + const [mapStates, setMapStates] = useState([]); + const [mapsLoading, setMapsLoading] = useState(true); - // Load maps from the database and ensure state is properly setup + // Load maps from the database and ensure state is properly seup useEffect(() => { if (!userId || !database || databaseStatus === "loading") { return; } - async function getDefaultMaps() { - const defaultMapsWithIds = []; + async function getDefaultMaps(): Promise { + const defaultMapsWithIds: Array = []; for (let i = 0; i < defaultMaps.length; i++) { const defaultMap = defaultMaps[i]; - const id = `__default-${defaultMap.name}`; + const mapId = `__default-${defaultMap.name}`; defaultMapsWithIds.push({ ...defaultMap, - id, + lastUsed: Date.now() + i, + id: mapId, owner: userId, // Emulate the time increasing to avoid sort errors created: Date.now() + i, @@ -57,9 +82,9 @@ export function MapDataProvider({ children }) { group: "default", }); // 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) { - await database.table("states").add({ ...defaultMapState, mapId: id }); + await database?.table("states").add({ ...defaultMapState, mapId: mapId }); } } return defaultMapsWithIds; @@ -67,24 +92,24 @@ export function MapDataProvider({ children }) { // Loads maps without the file data to save memory async function loadMaps() { - let storedMaps = []; + let storedMaps: Map[] = []; // Try to load maps with worker, fallback to database if failed const packedMaps = await worker.loadData("maps"); // let packedMaps; if (packedMaps) { - storedMaps = decode(packedMaps); + storedMaps = decode(packedMaps) as Map[]; } else { 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; storedMaps.push(rest); }); } const sortedMaps = storedMaps.sort((a, b) => b.created - a.created); const defaultMapsWithIds = await getDefaultMaps(); - const allMaps = [...sortedMaps, ...defaultMapsWithIds]; + const allMaps: Array = [...sortedMaps, ...defaultMapsWithIds]; setMaps(allMaps); - const storedStates = await database.table("states").toArray(); + const storedStates = await database?.table("states").toArray() as MapState[]; setMapStates(storedStates); setMapsLoading(false); } @@ -103,7 +128,7 @@ export function MapDataProvider({ children }) { const getMapFromDB = useCallback( async (mapId) => { - let map = await database.table("maps").get(mapId); + let map = await database?.table("maps").get(mapId) as Map; return map; }, [database] @@ -111,7 +136,7 @@ export function MapDataProvider({ children }) { const getMapStateFromDB = useCallback( async (mapId) => { - let mapState = await database.table("states").get(mapId); + let mapState = await database?.table("states").get(mapId) as MapState; return mapState; }, [database] @@ -122,30 +147,26 @@ export function MapDataProvider({ children }) { * Sorted by when they we're last used */ const updateCache = useCallback(async () => { - const cachedMaps = await database - .table("maps") - .where("owner") - .notEqual(userId) - .sortBy("lastUsed"); + const cachedMaps = await database?.table("maps").where("owner").notEqual(userId).sortBy("lastUsed") as Map[]; if (cachedMaps.length > cachedMapMax) { const cacheDeleteCount = cachedMaps.length - cachedMapMax; const idsToDelete = cachedMaps .slice(0, cacheDeleteCount) - .map((map) => map.id); - database.table("maps").where("id").anyOf(idsToDelete).delete(); + .map((map: Map) => map.id); + database?.table("maps").where("id").anyOf(idsToDelete).delete(); } }, [database, userId]); /** * 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( async (map) => { // Just update map database as react state will be updated with an Observable const state = { ...defaultMapState, mapId: map.id }; - await database.table("maps").add(map); - await database.table("states").add(state); + await database?.table("maps").add(map); + await database?.table("states").add(state); if (map.owner !== userId) { await updateCache(); } @@ -155,16 +176,16 @@ export function MapDataProvider({ children }) { const removeMap = useCallback( async (id) => { - await database.table("maps").delete(id); - await database.table("states").delete(id); + await database?.table("maps").delete(id); + await database?.table("states").delete(id); }, [database] ); const removeMaps = useCallback( async (ids) => { - await database.table("maps").bulkDelete(ids); - await database.table("states").bulkDelete(ids); + await database?.table("maps").bulkDelete(ids); + await database?.table("states").bulkDelete(ids); }, [database] ); @@ -172,7 +193,7 @@ export function MapDataProvider({ children }) { const resetMap = useCallback( async (id) => { const state = { ...defaultMapState, mapId: id }; - await database.table("states").put(state); + await database?.table("states").put(state); return state; }, [database] @@ -183,10 +204,10 @@ export function MapDataProvider({ children }) { // fake-indexeddb throws an error when updating maps in production. // Catch that error and use put when it fails try { - await database.table("maps").update(id, update); + await database?.table("maps").update(id, update); } catch (error) { const map = (await getMapFromDB(id)) || {}; - await database.table("maps").put({ ...map, id, ...update }); + await database?.table("maps").put({ ...map, id, ...update }); } }, [database, getMapFromDB] @@ -195,7 +216,7 @@ export function MapDataProvider({ children }) { const updateMaps = useCallback( async (ids, update) => { await Promise.all( - ids.map((id) => database.table("maps").update(id, update)) + ids.map((id: string) => database?.table("maps").update(id, update)) ); }, [database] @@ -203,7 +224,7 @@ export function MapDataProvider({ children }) { const updateMapState = useCallback( async (id, update) => { - await database.table("states").update(id, update); + await database?.table("states").update(id, update); }, [database] ); @@ -223,7 +244,7 @@ export function MapDataProvider({ children }) { false ); if (!success) { - await database.table("maps").put(map); + await database?.table("maps").put(map); } if (map.owner !== userId) { await updateCache(); @@ -238,13 +259,13 @@ export function MapDataProvider({ children }) { return; } - function handleMapChanges(changes) { + function handleMapChanges(changes: any) { for (let change of changes) { if (change.table === "maps") { if (change.type === 1) { // Created - const map = change.obj; - const state = { ...defaultMapState, mapId: map.id }; + const map: Map = change.obj; + const state: MapState = { ...defaultMapState, mapId: map.id }; setMaps((prevMaps) => [map, ...prevMaps]); setMapStates((prevStates) => [state, ...prevStates]); } else if (change.type === 2) { diff --git a/src/contexts/MapInteractionContext.js b/src/contexts/MapInteractionContext.tsx similarity index 82% rename from src/contexts/MapInteractionContext.js rename to src/contexts/MapInteractionContext.tsx index 1132999..1c3364b 100644 --- a/src/contexts/MapInteractionContext.js +++ b/src/contexts/MapInteractionContext.tsx @@ -1,16 +1,16 @@ -import React, { useContext } from "react"; +import React, { ReactChild, useContext } from "react"; import useDebounce from "../hooks/useDebounce"; -export const StageScaleContext = React.createContext(); -export const DebouncedStageScaleContext = React.createContext(); -export const StageWidthContext = React.createContext(); -export const StageHeightContext = React.createContext(); -export const SetPreventMapInteractionContext = React.createContext(); -export const MapWidthContext = React.createContext(); -export const MapHeightContext = React.createContext(); -export const InteractionEmitterContext = React.createContext(); +export const StageScaleContext = React.createContext(undefined) as any; +export const DebouncedStageScaleContext = React.createContext(undefined) as any; +export const StageWidthContext = React.createContext(undefined) as any; +export const StageHeightContext = React.createContext(undefined) as any; +export const SetPreventMapInteractionContext = React.createContext(undefined) as any; +export const MapWidthContext = React.createContext(undefined) as any; +export const MapHeightContext = React.createContext(undefined) as any; +export const InteractionEmitterContext = React.createContext(undefined) as any; -export function MapInteractionProvider({ value, children }) { +export function MapInteractionProvider({ value, children }: { value: any, children: ReactChild[]}) { const { stageScale, stageWidth, diff --git a/src/contexts/MapLoadingContext.js b/src/contexts/MapLoadingContext.tsx similarity index 80% rename from src/contexts/MapLoadingContext.js rename to src/contexts/MapLoadingContext.tsx index 94cb5b6..d4f0fd4 100644 --- a/src/contexts/MapLoadingContext.js +++ b/src/contexts/MapLoadingContext.tsx @@ -1,9 +1,9 @@ import React, { useState, useRef, useContext } from "react"; import { omit, isEmpty } from "../helpers/shared"; -const MapLoadingContext = React.createContext(); +const MapLoadingContext = React.createContext(undefined); -export function MapLoadingProvider({ children }) { +export function MapLoadingProvider({ children }: { children: any}) { const [loadingAssetCount, setLoadingAssetCount] = useState(0); function assetLoadStart() { @@ -14,9 +14,9 @@ export function MapLoadingProvider({ children }) { setLoadingAssetCount((prevLoadingAssets) => prevLoadingAssets - 1); } - const assetProgressRef = useRef({}); - const loadingProgressRef = useRef(null); - function assetProgressUpdate({ id, count, total }) { + const assetProgressRef = useRef({}); + const loadingProgressRef = useRef(null); + function assetProgressUpdate({ id, count, total }: { id: string, count: number, total: number }) { if (count === total) { assetProgressRef.current = omit(assetProgressRef.current, [id]); } else { @@ -28,7 +28,7 @@ export function MapLoadingProvider({ children }) { if (!isEmpty(assetProgressRef.current)) { let total = 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; count += progress.count; } diff --git a/src/contexts/MapStageContext.js b/src/contexts/MapStageContext.tsx similarity index 85% rename from src/contexts/MapStageContext.js rename to src/contexts/MapStageContext.tsx index c5c3952..cd15161 100644 --- a/src/contexts/MapStageContext.js +++ b/src/contexts/MapStageContext.tsx @@ -3,7 +3,7 @@ import React, { useContext } from "react"; const MapStageContext = React.createContext({ mapStageRef: { current: null }, }); -export const MapStageProvider = MapStageContext.Provider; +export const MapStageProvider: any = MapStageContext.Provider; export function useMapStage() { const context = useContext(MapStageContext); diff --git a/src/contexts/PartyContext.js b/src/contexts/PartyContext.tsx similarity index 71% rename from src/contexts/PartyContext.js rename to src/contexts/PartyContext.tsx index 905112e..716076f 100644 --- a/src/contexts/PartyContext.js +++ b/src/contexts/PartyContext.tsx @@ -1,12 +1,14 @@ 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(undefined); -export function PartyProvider({ session, children }) { +export function PartyProvider({ session, children }: { session: Session, children: any}) { const [partyState, setPartyState] = useState({}); useEffect(() => { - function handleSocketPartyState(partyState) { + function handleSocketPartyState(partyState: PartyState) { if (partyState) { const { [session.id]: _, ...otherMembersState } = partyState; setPartyState(otherMembersState); diff --git a/src/contexts/PlayerContext.js b/src/contexts/PlayerContext.tsx similarity index 78% rename from src/contexts/PlayerContext.js rename to src/contexts/PlayerContext.tsx index 26cd95c..4ee863c 100644 --- a/src/contexts/PlayerContext.js +++ b/src/contexts/PlayerContext.tsx @@ -6,11 +6,13 @@ import { useAuth } from "./AuthContext"; import { getRandomMonster } from "../helpers/monsters"; import useNetworkedState from "../hooks/useNetworkedState"; +import Session from "../network/Session"; +import { PlayerInfo } from "../components/party/PartyState"; -export const PlayerStateContext = React.createContext(); -export const PlayerUpdaterContext = React.createContext(() => {}); +export const PlayerStateContext = React.createContext(undefined); +export const PlayerUpdaterContext = React.createContext(() => {}); -export function PlayerProvider({ session, children }) { +export function PlayerProvider({ session, children }: { session: Session, children: any}) { const { userId } = useAuth(); const { database, databaseStatus } = useDatabase(); @@ -33,16 +35,16 @@ export function PlayerProvider({ session, children }) { return; } async function loadNickname() { - const storedNickname = await database.table("user").get("nickname"); + const storedNickname = await database?.table("user").get("nickname"); if (storedNickname !== undefined) { - setPlayerState((prevState) => ({ + setPlayerState((prevState: PlayerInfo) => ({ ...prevState, nickname: storedNickname.value, })); } else { const name = getRandomMonster(); - setPlayerState((prevState) => ({ ...prevState, nickname: name })); - database.table("user").add({ key: "nickname", value: name }); + setPlayerState((prevState: any) => ({ ...prevState, nickname: name })); + database?.table("user").add({ key: "nickname", value: name }); } } @@ -63,7 +65,7 @@ export function PlayerProvider({ session, children }) { useEffect(() => { if (userId) { - setPlayerState((prevState) => { + setPlayerState((prevState: PlayerInfo) => { if (prevState) { return { ...prevState, @@ -77,7 +79,8 @@ export function PlayerProvider({ session, children }) { useEffect(() => { function updateSessionId() { - setPlayerState((prevState) => { + setPlayerState((prevState: PlayerInfo) => { + // TODO: check useNetworkState requirements here if (prevState) { return { ...prevState, @@ -92,7 +95,7 @@ export function PlayerProvider({ session, children }) { updateSessionId(); } - function handleSocketStatus(status) { + function handleSocketStatus(status: string) { if (status === "joined") { updateSessionId(); } diff --git a/src/contexts/SettingsContext.js b/src/contexts/SettingsContext.tsx similarity index 86% rename from src/contexts/SettingsContext.js rename to src/contexts/SettingsContext.tsx index c9df1e7..1e17934 100644 --- a/src/contexts/SettingsContext.js +++ b/src/contexts/SettingsContext.tsx @@ -9,14 +9,14 @@ const SettingsContext = React.createContext({ const settingsProvider = getSettings(); -export function SettingsProvider({ children }) { +export function SettingsProvider({ children }: { children: any }) { const [settings, setSettings] = useState(settingsProvider.getAll()); useEffect(() => { settingsProvider.setAll(settings); }, [settings]); - const value = { + const value: { settings: any, setSettings: any} = { settings, setSettings, }; diff --git a/src/dice/Dice.js b/src/dice/Dice.ts similarity index 67% rename from src/dice/Dice.js rename to src/dice/Dice.ts index 23dee18..785d8a8 100644 --- a/src/dice/Dice.js +++ b/src/dice/Dice.ts @@ -1,7 +1,7 @@ import { Vector3 } from "@babylonjs/core/Maths/math"; import { SceneLoader } from "@babylonjs/core/Loading/sceneLoader"; 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 d6Source from "./shared/d6.glb"; @@ -13,6 +13,7 @@ import d100Source from "./shared/d100.glb"; import { lerp } from "../helpers/shared"; import { importTextureAsync } from "../helpers/babylon"; +import { BaseTexture, InstancedMesh, Material, Mesh, Scene, Texture } from "@babylonjs/core"; const minDiceRollSpeed = 600; const maxDiceRollSpeed = 800; @@ -20,10 +21,10 @@ const maxDiceRollSpeed = 800; class Dice { static instanceCount = 0; - static async loadMeshes(material, scene, sourceOverrides) { - let meshes = {}; - const addToMeshes = async (type, defaultSource) => { - let source = sourceOverrides ? sourceOverrides[type] : defaultSource; + static async loadMeshes(material: Material, scene: Scene, sourceOverrides?: any): Promise> { + let meshes: any = {}; + const addToMeshes = async (type: string | number, defaultSource: any) => { + let source: string = sourceOverrides ? sourceOverrides[type] : defaultSource; const mesh = await this.loadMesh(source, material, scene); meshes[type] = mesh; }; @@ -39,7 +40,7 @@ class Dice { return meshes; } - static async loadMesh(source, material, scene) { + static async loadMesh(source: string, material: Material, scene: Scene) { let mesh = (await SceneLoader.ImportMeshAsync("", source, "", scene)) .meshes[1]; mesh.setParent(null); @@ -51,15 +52,16 @@ class Dice { return mesh; } - static async loadMaterial(materialName, textures, scene) { + static async loadMaterial(materialName: string, textures: any, scene: 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.normal), importTextureAsync(textures.metalRoughness), ]); pbr.albedoTexture = albedo; - pbr.normalTexture = normal; + // pbr.normalTexture = normal; + pbr.bumpTexture = normal; pbr.metallicTexture = metalRoughness; pbr.useRoughnessFromMetallicTextureAlpha = false; pbr.useRoughnessFromMetallicTextureGreen = true; @@ -67,11 +69,16 @@ class Dice { return pbr; } - static createInstanceFromMesh(mesh, name, physicalProperties, scene) { + static createInstanceFromMesh(mesh: Mesh, name: string, physicalProperties: PhysicsImpostorParameters, scene: Scene) { let instance = mesh.createInstance(name); instance.position = mesh.position; 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.name = child.name; instance.addChild(locator); @@ -87,7 +94,7 @@ class Dice { return instance; } - static getDicePhysicalProperties(diceType) { + static getDicePhysicalProperties(diceType: string) { switch (diceType) { case "d4": return { mass: 4, friction: 4 }; @@ -107,17 +114,18 @@ class Dice { } } - static roll(instance) { - instance.physicsImpostor.setLinearVelocity(Vector3.Zero()); - instance.physicsImpostor.setAngularVelocity(Vector3.Zero()); + static roll(instance: Mesh) { + instance.physicsImpostor?.setLinearVelocity(Vector3.Zero()); + instance.physicsImpostor?.setAngularVelocity(Vector3.Zero()); 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 visibleDiceTray = diceTraySingle.isVisible + const visibleDiceTray: any = diceTraySingle?.isVisible ? diceTraySingle : diceTrayDouble; - const trayBounds = visibleDiceTray.getBoundingInfo().boundingBox; + const trayBounds = visibleDiceTray?.getBoundingInfo().boundingBox; const position = new Vector3( trayBounds.center.x + (Math.random() * 2 - 1), @@ -142,13 +150,13 @@ class Dice { .normalizeToNew() .scale(lerp(minDiceRollSpeed, maxDiceRollSpeed, Math.random())); - instance.physicsImpostor.applyImpulse( + instance.physicsImpostor?.applyImpulse( impulse, instance.physicsImpostor.getObjectCenter() ); } - static createInstance(mesh, physicalProperties, scene) { + static createInstanceMesh(mesh: Mesh, physicalProperties: PhysicsImpostorParameters, scene: Scene): InstancedMesh { this.instanceCount++; return this.createInstanceFromMesh( diff --git a/src/dice/diceTray/DiceTray.js b/src/dice/diceTray/DiceTray.ts similarity index 84% rename from src/dice/diceTray/DiceTray.js rename to src/dice/diceTray/DiceTray.ts index 1e9ec71..e2237bd 100644 --- a/src/dice/diceTray/DiceTray.js +++ b/src/dice/diceTray/DiceTray.ts @@ -3,7 +3,9 @@ import { PBRMaterial } from "@babylonjs/core/Materials/PBR/pbrMaterial"; import { PhysicsImpostor } from "@babylonjs/core/Physics/physicsImpostor"; import { Mesh } from "@babylonjs/core/Meshes/mesh"; +//@ts-ignore import singleMeshSource from "./single.glb"; +//@ts-ignore import doubleMeshSource from "./double.glb"; import singleAlbedo from "./singleAlbedo.jpg"; @@ -15,12 +17,15 @@ import doubleMetalRoughness from "./doubleMetalRoughness.jpg"; import doubleNormal from "./doubleNormal.jpg"; import { importTextureAsync } from "../../helpers/babylon"; +import { Scene, ShadowGenerator, Texture } from "@babylonjs/core"; class DiceTray { _size; + get size() { return this._size; } + set size(newSize) { this._size = newSize; const wallOffsetWidth = this.collisionSize / 2 + this.width / 2 - 0.5; @@ -32,21 +37,24 @@ class DiceTray { this.singleMesh.isVisible = newSize === "single"; this.doubleMesh.isVisible = newSize === "double"; } + scene; shadowGenerator; + get width() { return this.size === "single" ? 10 : 20; } + height = 20; collisionSize = 50; - wallTop; - wallRight; - wallBottom; - wallLeft; - singleMesh; - doubleMesh; + wallTop: any; + wallRight: any; + wallBottom: any; + wallLeft: any; + singleMesh: any; + doubleMesh: any; - constructor(initialSize, scene, shadowGenerator) { + constructor(initialSize: string, scene: Scene, shadowGenerator: ShadowGenerator) { this._size = initialSize; this.scene = scene; this.shadowGenerator = shadowGenerator; @@ -57,7 +65,7 @@ class DiceTray { await this.loadMeshes(); } - createCollision(name, x, y, z, friction) { + createCollision(name: string, x: number, y: number, z: number, friction: number) { let collision = Mesh.CreateBox( name, this.collisionSize, @@ -126,6 +134,15 @@ class DiceTray { doubleAlbedoTexture, doubleNormalTexture, doubleMetalRoughnessTexture, + ]: [ + singleMeshes: any, + doubleMeshes: any, + singleAlbedoTexture: Texture, + singleNormalTexture: Texture, + singleMetalRoughnessTexture: Texture, + doubleAlbedoTexture: Texture, + doubleNormalTexture: Texture, + doubleMetalRoughnessTexture: Texture ] = await Promise.all([ SceneLoader.ImportMeshAsync("", singleMeshSource, "", this.scene), SceneLoader.ImportMeshAsync("", doubleMeshSource, "", this.scene), @@ -142,7 +159,9 @@ class DiceTray { this.singleMesh.name = "dice_tray"; let singleMaterial = new PBRMaterial("dice_tray_mat_single", this.scene); singleMaterial.albedoTexture = singleAlbedoTexture; - singleMaterial.normalTexture = singleNormalTexture; + // TODO: ask Mitch about texture + // singleMaterial.normalTexture = singleNormalTexture; + singleMaterial.bumpTexture = singleNormalTexture; singleMaterial.metallicTexture = singleMetalRoughnessTexture; singleMaterial.useRoughnessFromMetallicTextureAlpha = false; singleMaterial.useRoughnessFromMetallicTextureGreen = true; @@ -158,7 +177,9 @@ class DiceTray { this.doubleMesh.name = "dice_tray"; let doubleMaterial = new PBRMaterial("dice_tray_mat_double", this.scene); doubleMaterial.albedoTexture = doubleAlbedoTexture; - doubleMaterial.normalTexture = doubleNormalTexture; + // TODO: ask Mitch about texture + //doubleMaterial.normalTexture = doubleNormalTexture; + doubleMaterial.bumpTexture = doubleNormalTexture; doubleMaterial.metallicTexture = doubleMetalRoughnessTexture; doubleMaterial.useRoughnessFromMetallicTextureAlpha = false; doubleMaterial.useRoughnessFromMetallicTextureGreen = true; diff --git a/src/dice/galaxy/GalaxyDice.js b/src/dice/galaxy/GalaxyDice.ts similarity index 68% rename from src/dice/galaxy/GalaxyDice.js rename to src/dice/galaxy/GalaxyDice.ts index ebe8548..9947f27 100644 --- a/src/dice/galaxy/GalaxyDice.js +++ b/src/dice/galaxy/GalaxyDice.ts @@ -1,3 +1,4 @@ +import { InstancedMesh, Material, Mesh, Scene } from "@babylonjs/core"; import Dice from "../Dice"; import albedo from "./albedo.jpg"; @@ -5,10 +6,10 @@ import metalRoughness from "./metalRoughness.jpg"; import normal from "./normal.jpg"; class GalaxyDice extends Dice { - static meshes; - static material; + static meshes: Record; + static material: Material; - static async load(scene) { + static async load(scene: Scene) { if (!this.material) { this.material = await this.loadMaterial( "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) { throw Error("Dice not loaded, call load before creating an instance"); } - return Dice.createInstance( + return super.createInstanceMesh( this.meshes[diceType], this.getDicePhysicalProperties(diceType), scene diff --git a/src/dice/gemstone/GemstoneDice.js b/src/dice/gemstone/GemstoneDice.ts similarity index 79% rename from src/dice/gemstone/GemstoneDice.js rename to src/dice/gemstone/GemstoneDice.ts index 4545912..b6a2043 100644 --- a/src/dice/gemstone/GemstoneDice.js +++ b/src/dice/gemstone/GemstoneDice.ts @@ -8,17 +8,18 @@ import metalRoughness from "./metalRoughness.jpg"; import normal from "./normal.jpg"; import { importTextureAsync } from "../../helpers/babylon"; +import { Material, Mesh, Scene } from "@babylonjs/core"; class GemstoneDice extends Dice { - static meshes; - static material; + static meshes: Record; + static material: Material; - static getDicePhysicalProperties(diceType) { + static getDicePhysicalProperties(diceType: string) { let properties = super.getDicePhysicalProperties(diceType); 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 [albedo, normal, metalRoughness] = await Promise.all([ importTextureAsync(textures.albedo), @@ -26,7 +27,8 @@ class GemstoneDice extends Dice { importTextureAsync(textures.metalRoughness), ]); pbr.albedoTexture = albedo; - pbr.normalTexture = normal; + // TODO: ask Mitch about texture + pbr.bumpTexture = normal; pbr.metallicTexture = metalRoughness; pbr.useRoughnessFromMetallicTextureAlpha = false; pbr.useRoughnessFromMetallicTextureGreen = true; @@ -41,7 +43,7 @@ class GemstoneDice extends Dice { return pbr; } - static async load(scene) { + static async load(scene: Scene) { if (!this.material) { this.material = await this.loadMaterial( "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) { throw Error("Dice not loaded, call load before creating an instance"); } - return Dice.createInstance( + return Dice.createInstanceMesh( this.meshes[diceType], this.getDicePhysicalProperties(diceType), scene diff --git a/src/dice/glass/GlassDice.js b/src/dice/glass/GlassDice.ts similarity index 80% rename from src/dice/glass/GlassDice.js rename to src/dice/glass/GlassDice.ts index ec063dc..a745487 100644 --- a/src/dice/glass/GlassDice.js +++ b/src/dice/glass/GlassDice.ts @@ -8,17 +8,18 @@ import mask from "./mask.png"; import normal from "./normal.jpg"; import { importTextureAsync } from "../../helpers/babylon"; +import { Material, Mesh, Scene } from "@babylonjs/core"; class GlassDice extends Dice { - static meshes; - static material; + static meshes: Record; + static material: Material; - static getDicePhysicalProperties(diceType) { + static getDicePhysicalProperties(diceType: string) { let properties = super.getDicePhysicalProperties(diceType); 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 [albedo, normal, mask] = await Promise.all([ importTextureAsync(textures.albedo), @@ -26,7 +27,8 @@ class GlassDice extends Dice { importTextureAsync(textures.mask), ]); pbr.albedoTexture = albedo; - pbr.normalTexture = normal; + // pbr.normalTexture = normal; + pbr.bumpTexture = normal; pbr.roughness = 0.25; pbr.metallic = 0; pbr.subSurface.isRefractionEnabled = true; @@ -43,7 +45,7 @@ class GlassDice extends Dice { return pbr; } - static async load(scene) { + static async load(scene: Scene) { if (!this.material) { this.material = await this.loadMaterial( "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) { throw Error("Dice not loaded, call load before creating an instance"); } - return Dice.createInstance( + return Dice.createInstanceMesh( this.meshes[diceType], this.getDicePhysicalProperties(diceType), scene diff --git a/src/dice/index.js b/src/dice/index.ts similarity index 87% rename from src/dice/index.js rename to src/dice/index.ts index 62e22b9..82cf7f0 100644 --- a/src/dice/index.js +++ b/src/dice/index.ts @@ -17,8 +17,11 @@ import SunsetPreview from "./sunset/preview.png"; import WalnutPreview from "./walnut/preview.png"; import GlassPreview from "./glass/preview.png"; import GemstonePreview from "./gemstone/preview.png"; +import Dice from "./Dice"; -export const diceClasses = { +type DiceClasses = Record; + +export const diceClasses: DiceClasses = { galaxy: GalaxyDice, nebula: NebulaDice, sunrise: SunriseDice, @@ -29,7 +32,9 @@ export const diceClasses = { gemstone: GemstoneDice, }; -export const dicePreviews = { +type DicePreview = Record; + +export const dicePreviews: DicePreview = { galaxy: GalaxyPreview, nebula: NebulaPreview, sunrise: SunrisePreview, diff --git a/src/dice/iron/IronDice.js b/src/dice/iron/IronDice.ts similarity index 73% rename from src/dice/iron/IronDice.js rename to src/dice/iron/IronDice.ts index 917ab6c..2e9c5b7 100644 --- a/src/dice/iron/IronDice.js +++ b/src/dice/iron/IronDice.ts @@ -1,3 +1,4 @@ +import { Material, Mesh, Scene } from "@babylonjs/core"; import Dice from "../Dice"; import albedo from "./albedo.jpg"; @@ -5,15 +6,15 @@ import metalRoughness from "./metalRoughness.jpg"; import normal from "./normal.jpg"; class IronDice extends Dice { - static meshes; - static material; + static meshes: Record; + static material: Material; - static getDicePhysicalProperties(diceType) { + static getDicePhysicalProperties(diceType: string) { let properties = super.getDicePhysicalProperties(diceType); return { mass: properties.mass * 2, friction: properties.friction }; } - static async load(scene) { + static async load(scene: Scene) { if (!this.material) { this.material = await this.loadMaterial( "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) { throw Error("Dice not loaded, call load before creating an instance"); } - return Dice.createInstance( + return Dice.createInstanceMesh( this.meshes[diceType], this.getDicePhysicalProperties(diceType), scene diff --git a/src/dice/nebula/NebulaDice.js b/src/dice/nebula/NebulaDice.ts similarity index 73% rename from src/dice/nebula/NebulaDice.js rename to src/dice/nebula/NebulaDice.ts index fe687f1..eb08d26 100644 --- a/src/dice/nebula/NebulaDice.js +++ b/src/dice/nebula/NebulaDice.ts @@ -1,3 +1,4 @@ +import { Material, Mesh, Scene } from "@babylonjs/core"; import Dice from "../Dice"; import albedo from "./albedo.jpg"; @@ -5,10 +6,10 @@ import metalRoughness from "./metalRoughness.jpg"; import normal from "./normal.jpg"; class NebulaDice extends Dice { - static meshes; - static material; + static meshes: Record; + static material: Material; - static async load(scene) { + static async load(scene: Scene) { if (!this.material) { this.material = await this.loadMaterial( "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) { throw Error("Dice not loaded, call load before creating an instance"); } - return Dice.createInstance( + return Dice.createInstanceMesh( this.meshes[diceType], this.getDicePhysicalProperties(diceType), scene diff --git a/src/dice/sunrise/SunriseDice.js b/src/dice/sunrise/SunriseDice.ts similarity index 73% rename from src/dice/sunrise/SunriseDice.js rename to src/dice/sunrise/SunriseDice.ts index d92f6f0..233886c 100644 --- a/src/dice/sunrise/SunriseDice.js +++ b/src/dice/sunrise/SunriseDice.ts @@ -1,3 +1,4 @@ +import { Material, Mesh, Scene } from "@babylonjs/core"; import Dice from "../Dice"; import albedo from "./albedo.jpg"; @@ -5,10 +6,10 @@ import metalRoughness from "./metalRoughness.jpg"; import normal from "./normal.jpg"; class SunriseDice extends Dice { - static meshes; - static material; + static meshes: Record; + static material: Material; - static async load(scene) { + static async load(scene: Scene) { if (!this.material) { this.material = await this.loadMaterial( "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) { throw Error("Dice not loaded, call load before creating an instance"); } - return Dice.createInstance( + return super.createInstanceMesh( this.meshes[diceType], this.getDicePhysicalProperties(diceType), scene diff --git a/src/dice/sunset/SunsetDice.js b/src/dice/sunset/SunsetDice.ts similarity index 73% rename from src/dice/sunset/SunsetDice.js rename to src/dice/sunset/SunsetDice.ts index c0e6884..7e66a20 100644 --- a/src/dice/sunset/SunsetDice.js +++ b/src/dice/sunset/SunsetDice.ts @@ -1,3 +1,4 @@ +import { Material, Mesh, Scene } from "@babylonjs/core"; import Dice from "../Dice"; import albedo from "./albedo.jpg"; @@ -5,10 +6,10 @@ import metalRoughness from "./metalRoughness.jpg"; import normal from "./normal.jpg"; class SunsetDice extends Dice { - static meshes; - static material; + static meshes: Record; + static material: Material; - static async load(scene) { + static async load(scene: Scene) { if (!this.material) { this.material = await this.loadMaterial( "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) { throw Error("Dice not loaded, call load before creating an instance"); } - return Dice.createInstance( + return super.createInstanceMesh( this.meshes[diceType], this.getDicePhysicalProperties(diceType), scene diff --git a/src/dice/walnut/WalnutDice.js b/src/dice/walnut/WalnutDice.ts similarity index 80% rename from src/dice/walnut/WalnutDice.js rename to src/dice/walnut/WalnutDice.ts index b5e5755..979808e 100644 --- a/src/dice/walnut/WalnutDice.js +++ b/src/dice/walnut/WalnutDice.ts @@ -11,6 +11,7 @@ import d10Source from "./d10.glb"; import d12Source from "./d12.glb"; import d20Source from "./d20.glb"; import d100Source from "./d100.glb"; +import { Material, Mesh, Scene } from "@babylonjs/core"; const sourceOverrides = { d4: d4Source, @@ -23,15 +24,15 @@ const sourceOverrides = { }; class WalnutDice extends Dice { - static meshes; - static material; + static meshes: Record; + static material: Material; - static getDicePhysicalProperties(diceType) { + static getDicePhysicalProperties(diceType: string) { let properties = super.getDicePhysicalProperties(diceType); return { mass: properties.mass * 1.4, friction: properties.friction }; } - static async load(scene) { + static async load(scene: Scene) { if (!this.material) { this.material = await this.loadMaterial( "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) { throw Error("Dice not loaded, call load before creating an instance"); } - return Dice.createInstance( + return super.createInstanceMesh( this.meshes[diceType], this.getDicePhysicalProperties(diceType), scene diff --git a/src/helpers/babylon.ts b/src/helpers/babylon.ts index a713bad..230a07c 100644 --- a/src/helpers/babylon.ts +++ b/src/helpers/babylon.ts @@ -1,7 +1,7 @@ import { Texture } from "@babylonjs/core/Materials/Textures/texture"; // 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 { return new Promise((resolve, reject) => { let texture = new Texture( url, diff --git a/src/hooks/useDebounce.js b/src/hooks/useDebounce.tsx similarity index 86% rename from src/hooks/useDebounce.js rename to src/hooks/useDebounce.tsx index bf82898..1ab9dea 100644 --- a/src/hooks/useDebounce.js +++ b/src/hooks/useDebounce.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; -function useDebounce(value, delay) { +function useDebounce(value: any, delay: number): any { const [debouncedValue, setDebouncedValue] = useState(); useEffect(() => { diff --git a/src/hooks/useNetworkedState.js b/src/hooks/useNetworkedState.tsx similarity index 85% rename from src/hooks/useNetworkedState.js rename to src/hooks/useNetworkedState.tsx index 7dbf356..7737661 100644 --- a/src/hooks/useNetworkedState.js +++ b/src/hooks/useNetworkedState.tsx @@ -2,6 +2,7 @@ import { useEffect, useState, useRef, useCallback } from "react"; import useDebounce from "./useDebounce"; import { diff, applyChanges } from "../helpers/diff"; +import Session from "../network/Session"; /** * @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} 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` @@ -23,13 +26,13 @@ import { diff, applyChanges } from "../helpers/diff"; * @returns {[any, setNetworkedState]} */ function useNetworkedState( - initialState, - session, - eventName, - debounceRate = 500, - partialUpdates = true, - partialUpdatesKey = "id" -) { + initialState: any, + session: Session, + eventName: string, + debounceRate: number = 500, + partialUpdates: boolean = true, + partialUpdatesKey: string = "id" +): [any, setNetworkedState] { const [state, _setState] = useState(initialState); // Used to control whether the state needs to be sent to the socket const dirtyRef = useRef(false); @@ -62,6 +65,9 @@ function useNetworkedState( ) { const changes = diff(lastSyncedStateRef.current, debouncedState); if (changes) { + if (!debouncedState) { + return; + } const update = { id: debouncedState[partialUpdatesKey], changes }; session.socket.emit(`${eventName}_update`, update); } @@ -81,13 +87,13 @@ function useNetworkedState( ]); useEffect(() => { - function handleSocketEvent(data) { + function handleSocketEvent(data: any) { _setState(data); lastSyncedStateRef.current = data; } - function handleSocketUpdateEvent(update) { - _setState((prevState) => { + function handleSocketUpdateEvent(update: any) { + _setState((prevState: any) => { if (prevState && prevState[partialUpdatesKey] === update.id) { let newState = { ...prevState }; applyChanges(newState, update.changes); diff --git a/src/maps/index.ts b/src/maps/index.ts index 2b55878..ca3a553 100644 --- a/src/maps/index.ts +++ b/src/maps/index.ts @@ -1,4 +1,5 @@ import Case from "case"; +import { Map } from "../components/map/Map"; import blankImage from "./Blank Grid 22x22.jpg"; import grassImage from "./Grass Grid 22x22.jpg"; @@ -18,7 +19,7 @@ export const mapSources = { wood: woodImage, }; -export const maps = Object.keys(mapSources).map((key) => ({ +export const maps: Array> = Object.keys(mapSources).map((key) => ({ key, name: Case.capital(key), grid: { diff --git a/src/modals/EditMapModal.tsx b/src/modals/EditMapModal.tsx index 16743ae..6f9a53f 100644 --- a/src/modals/EditMapModal.tsx +++ b/src/modals/EditMapModal.tsx @@ -43,7 +43,7 @@ function EditMapModal({ isOpen, onDone, mapId }: EditMapProps) { } const mapState = await getMapStateFromDB(mapId); setMap(loadingMap); - setMapState(mapState); + setMapState(mapState as MapState); setIsLoading(false); } diff --git a/src/modals/SelectMapModal.tsx b/src/modals/SelectMapModal.tsx index 4bb5c2e..88ce28b 100644 --- a/src/modals/SelectMapModal.tsx +++ b/src/modals/SelectMapModal.tsx @@ -30,7 +30,7 @@ import { useAuth } from "../contexts/AuthContext"; import { useKeyboard, useBlur } from "../contexts/KeyboardContext"; import shortcuts from "../shortcuts"; -import { MapState } from "../components/map/Map"; +import { Map, MapState } from "../components/map/Map"; type SelectMapProps = { isOpen: boolean, @@ -175,7 +175,7 @@ function SelectMapModal({ clearFileInput(); } - async function handleImageUpload(file: any) { + async function handleImageUpload(file: File) { if (!file) { return Promise.reject(); } @@ -313,7 +313,7 @@ function SelectMapModal({ // The map selected in the modal const [selectedMapIds, setSelectedMapIds] = useState([]); - const selectedMaps = ownedMaps.filter((map: any) => + const selectedMaps: Map[] = ownedMaps.filter((map: Map) => selectedMapIds.includes(map.id) ); const selectedMapStates = mapStates.filter((state: MapState) => @@ -499,11 +499,15 @@ function SelectMapModal({ + <> {(isLoading || mapsLoading) && } + 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} /> void, isSupported: boolean, - unavailableMessage: string, + unavailableMessage: JSX.Element, stream: MediaStream, noAudioTrack: boolean, - noAudioMessage: string, + noAudioMessage: JSX.Element, onStreamStart: any, onStreamEnd: any, } diff --git a/src/network/NetworkedMapAndTokens.js b/src/network/NetworkedMapAndTokens.tsx similarity index 81% rename from src/network/NetworkedMapAndTokens.js rename to src/network/NetworkedMapAndTokens.tsx index d38413a..b692616 100644 --- a/src/network/NetworkedMapAndTokens.js +++ b/src/network/NetworkedMapAndTokens.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useRef } from "react"; +import { useState, useEffect, useRef } from "react"; import { useToasts } from "react-toast-notifications"; import { useTokenData } from "../contexts/TokenDataContext"; @@ -14,11 +14,13 @@ import useDebounce from "../hooks/useDebounce"; import useNetworkedState from "../hooks/useNetworkedState"; // Load session for auto complete -// eslint-disable-next-line no-unused-vars 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 { PartyState } from "../components/party/PartyState"; +import Action from "../actions/Action"; +import { Token } from "../tokens"; const defaultMapActions = { mapDrawActions: [], @@ -35,10 +37,10 @@ const defaultMapActions = { /** * @param {NetworkedMapProps} props */ -function NetworkedMapAndTokens({ session }) { +function NetworkedMapAndTokens({ session }: { session: Session }) { const { addToast } = useToasts(); const { userId } = useAuth(); - const partyState = useParty(); + const partyState: PartyState = useParty(); const { assetLoadStart, assetLoadFinish, @@ -49,8 +51,8 @@ function NetworkedMapAndTokens({ session }) { const { putToken, getTokenFromDB } = useTokenData(); const { putMap, updateMap, getMapFromDB, updateMapState } = useMapData(); - const [currentMap, setCurrentMap] = useState(null); - const [currentMapState, setCurrentMapState] = useNetworkedState( + const [currentMap, setCurrentMap] = useState(null); + const [currentMapState, setCurrentMapState]: [ currentMapState: MapState, setCurrentMapState: any] = useNetworkedState( null, session, "map_state", @@ -67,8 +69,8 @@ function NetworkedMapAndTokens({ session }) { "mapId" ); - async function loadAssetManifestFromMap(map, mapState) { - const assets = {}; + async function loadAssetManifestFromMap(map: any, mapState: MapState) { + const assets: any = {}; if (map.type === "file") { const { id, lastModified, owner } = map; assets[`map-${id}`] = { type: "map", id, lastModified, owner }; @@ -90,20 +92,20 @@ function NetworkedMapAndTokens({ session }) { 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 true if an asset is out of date - function assetNeedsUpdate(oldAsset, newAsset) { + function assetNeedsUpdate(oldAsset: any, newAsset: any) { return ( compareAssets(oldAsset, newAsset) && oldAsset.lastModified < newAsset.lastModified ); } - function addAssetIfNeeded(asset) { - setAssetManifest((prevManifest) => { + function addAssetIfNeeded(asset: any) { + setAssetManifest((prevManifest: any) => { if (prevManifest?.assets) { const id = asset.type === "map" ? `map-${asset.id}` : `token-${asset.id}`; @@ -133,7 +135,7 @@ function NetworkedMapAndTokens({ session }) { } async function requestAssetsIfNeeded() { - for (let asset of Object.values(assetManifest.assets)) { + for (let asset of Object.values(assetManifest.assets) as any) { if ( asset.owner === userId || requestingAssetsRef.current.has(asset.id) @@ -200,14 +202,14 @@ function NetworkedMapAndTokens({ session }) { debouncedMapState && debouncedMapState.mapId && currentMap && - currentMap.owner === userId && + currentMap?.owner === userId && database ) { updateMapState(debouncedMapState.mapId, debouncedMapState); } }, [currentMap, debouncedMapState, userId, database, updateMapState]); - async function handleMapChange(newMap, newMapState) { + async function handleMapChange(newMap: any, newMapState: any) { // Clear map before sending new one setCurrentMap(null); session.socket?.emit("map", null); @@ -229,15 +231,15 @@ function NetworkedMapAndTokens({ session }) { await loadAssetManifestFromMap(newMap, newMapState); } - function handleMapReset(newMapState) { + function handleMapReset(newMapState: any) { setCurrentMapState(newMapState, true, true); setMapActions(defaultMapActions); } - const [mapActions, setMapActions] = useState(defaultMapActions); + const [mapActions, setMapActions] = useState(defaultMapActions); - function addMapActions(actions, indexKey, actionsKey, shapesKey) { - setMapActions((prevMapActions) => { + function addMapActions(actions: Action[], indexKey: string, actionsKey: any, shapesKey: any) { + setMapActions((prevMapActions: any) => { const newActions = [ ...prevMapActions[actionsKey].slice(0, prevMapActions[indexKey] + 1), ...actions, @@ -250,7 +252,7 @@ function NetworkedMapAndTokens({ session }) { }; }); // Update map state by performing the actions on it - setCurrentMapState((prevMapState) => { + setCurrentMapState((prevMapState: any) => { if (prevMapState) { let shapes = prevMapState[shapesKey]; for (let action of actions) { @@ -264,20 +266,20 @@ function NetworkedMapAndTokens({ session }) { }); } - function updateActionIndex(change, indexKey, actionsKey, shapesKey) { - const prevIndex = mapActions[indexKey]; + function updateActionIndex(change: any, indexKey: any, actionsKey: any, shapesKey: any) { + const prevIndex: any = mapActions[indexKey]; const newIndex = Math.min( Math.max(mapActions[indexKey] + change, -1), mapActions[actionsKey].length - 1 ); - setMapActions((prevMapActions) => ({ + setMapActions((prevMapActions: Action[]) => ({ ...prevMapActions, [indexKey]: newIndex, })); // Update map state by either performing the actions or undoing them - setCurrentMapState((prevMapState) => { + setCurrentMapState((prevMapState: any) => { if (prevMapState) { let shapes = prevMapState[shapesKey]; if (prevIndex < newIndex) { @@ -303,7 +305,7 @@ function NetworkedMapAndTokens({ session }) { return newIndex; } - function handleMapDraw(action) { + function handleMapDraw(action: Action) { addMapActions( [action], "mapDrawActionIndex", @@ -320,7 +322,7 @@ function NetworkedMapAndTokens({ session }) { updateActionIndex(1, "mapDrawActionIndex", "mapDrawActions", "drawShapes"); } - function handleFogDraw(action) { + function handleFogDraw(action: Action) { addMapActions( [action], "fogDrawActionIndex", @@ -338,16 +340,16 @@ function NetworkedMapAndTokens({ session }) { } // If map changes clear map actions - const previousMapIdRef = useRef(); + const previousMapIdRef = useRef(); useEffect(() => { - if (currentMap && currentMap.id !== previousMapIdRef.current) { + if (currentMap && currentMap?.id !== previousMapIdRef.current) { setMapActions(defaultMapActions); - previousMapIdRef.current = currentMap.id; + previousMapIdRef.current = currentMap?.id; } }, [currentMap]); - function handleNoteChange(note) { - setCurrentMapState((prevMapState) => ({ + function handleNoteChange(note: any) { + setCurrentMapState((prevMapState: any) => ({ ...prevMapState, notes: { ...prevMapState.notes, @@ -356,8 +358,8 @@ function NetworkedMapAndTokens({ session }) { })); } - function handleNoteRemove(noteId) { - setCurrentMapState((prevMapState) => ({ + function handleNoteRemove(noteId: string) { + setCurrentMapState((prevMapState: any) => ({ ...prevMapState, notes: omit(prevMapState.notes, [noteId]), })); @@ -367,17 +369,17 @@ function NetworkedMapAndTokens({ session }) { * Token state */ - async function handleMapTokenStateCreate(tokenState) { + async function handleMapTokenStateCreate(tokenState: TokenState) { if (!currentMap || !currentMapState) { return; } // 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") { const { id, lastModified, owner } = token; addAssetIfNeeded({ type: "token", id, lastModified, owner }); } - setCurrentMapState((prevMapState) => ({ + setCurrentMapState((prevMapState: any) => ({ ...prevMapState, tokens: { ...prevMapState.tokens, @@ -386,11 +388,11 @@ function NetworkedMapAndTokens({ session }) { })); } - function handleMapTokenStateChange(change) { + function handleMapTokenStateChange(change: any) { if (!currentMapState) { return; } - setCurrentMapState((prevMapState) => { + setCurrentMapState((prevMapState: any) => { let tokens = { ...prevMapState.tokens }; for (let id in change) { if (id in tokens) { @@ -405,22 +407,21 @@ function NetworkedMapAndTokens({ session }) { }); } - function handleMapTokenStateRemove(tokenState) { - setCurrentMapState((prevMapState) => { + function handleMapTokenStateRemove(tokenState: any) { + setCurrentMapState((prevMapState: any) => { const { [tokenState.id]: old, ...rest } = prevMapState.tokens; return { ...prevMapState, tokens: rest }; }); } 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") { const map = await getMapFromDB(data); - function replyWithMap(preview, resolution) { + function replyWithMap(preview?: string | undefined, resolution?: any) { let response = { ...map, - resolutions: undefined, - file: undefined, thumbnail: undefined, // Remove last modified so if there is an error // during the map request the cache is invalid @@ -429,13 +430,13 @@ function NetworkedMapAndTokens({ session }) { lastUsed: Date.now(), }; // Send preview if available - if (map.resolutions[preview]) { - response.resolutions = { [preview]: map.resolutions[preview] }; + if (preview !== undefined && map.resolutions && map.resolutions[preview]) { + response.resolutions = { [preview]: map.resolutions[preview] } as Resolutions; reply("mapResponse", response, "map"); } // Send full map at the desired resolution if available - if (map.resolutions[resolution]) { - response.file = map.resolutions[resolution].file; + if (map.resolutions && map.resolutions[resolution]) { + response.file = map.resolutions[resolution].file as Uint8Array; } else if (map.file) { // The resolution might not exist for other users so send the file instead 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) { // Corresponding asset load finished called in token and map response assetLoadStart(); @@ -514,7 +515,7 @@ function NetworkedMapAndTokens({ session }) { assetProgressUpdate({ id, total, count }); } - async function handleSocketMap(map) { + async function handleSocketMap(map: any) { if (map) { if (map.type === "file") { const fullMap = await getMapFromDB(map.id); @@ -540,31 +541,31 @@ function NetworkedMapAndTokens({ session }) { const canChangeMap = !isLoading; - const canEditMapDrawing = + const canEditMapDrawing: any = currentMap && currentMapState && (currentMapState.editFlags.includes("drawing") || - currentMap.owner === userId); + currentMap?.owner === userId); const canEditFogDrawing = currentMap && currentMapState && - (currentMapState.editFlags.includes("fog") || currentMap.owner === userId); + (currentMapState.editFlags.includes("fog") || currentMap?.owner === userId); const canEditNotes = currentMap && currentMapState && (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 // and are not the map owner if ( currentMapState && currentMap && !currentMapState.editFlags.includes("tokens") && - currentMap.owner !== userId + currentMap?.owner !== userId ) { for (let token of Object.values(currentMapState.tokens)) { if (token.owner !== userId) { diff --git a/src/network/NetworkedMapPointer.js b/src/network/NetworkedMapPointer.tsx similarity index 86% rename from src/network/NetworkedMapPointer.js rename to src/network/NetworkedMapPointer.tsx index 6398eb1..8b58304 100644 --- a/src/network/NetworkedMapPointer.js +++ b/src/network/NetworkedMapPointer.tsx @@ -8,11 +8,12 @@ import { isEmpty } from "../helpers/shared"; import Vector2 from "../helpers/Vector2"; import useSetting from "../hooks/useSetting"; +import Session from "./Session"; // Send pointer updates every 50ms (20fps) const sendTickRate = 50; -function NetworkedMapPointer({ session, active }) { +function NetworkedMapPointer({ session, active }: { session: Session, active: boolean }) { const { userId } = useAuth(); const [localPointerState, setLocalPointerState] = useState({}); const [pointerColor] = useSetting("pointer.color"); @@ -38,12 +39,12 @@ function NetworkedMapPointer({ session, active }) { // Send pointer updates every sendTickRate to peers to save on bandwidth // We use requestAnimationFrame as setInterval was being blocked during // 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(() => { let prevTime = performance.now(); let request = requestAnimationFrame(update); let counter = 0; - function update(time) { + function update(time: any) { request = requestAnimationFrame(update); const deltaTime = time - prevTime; counter += deltaTime; @@ -70,7 +71,7 @@ function NetworkedMapPointer({ session, active }) { }; }, []); - function updateOwnPointerState(position, visible) { + function updateOwnPointerState(position: any, visible: boolean) { setLocalPointerState((prev) => ({ ...prev, [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); } - function handleOwnPointerMove(position) { + function handleOwnPointerMove(position: any) { updateOwnPointerState(position, true); } - function handleOwnPointerUp(position) { + function handleOwnPointerUp(position: any) { updateOwnPointerState(position, false); } // Handle pointer data receive - const interpolationsRef = useRef({}); + const interpolationsRef: React.MutableRefObject = useRef({}); useEffect(() => { // TODO: Handle player disconnect while pointer visible - function handleSocketPlayerPointer(pointer) { - const interpolations = interpolationsRef.current; + function handleSocketPlayerPointer(pointer: any) { + const interpolations: any = interpolationsRef.current; const id = pointer.id; if (!(id in interpolations)) { interpolations[id] = { @@ -145,8 +146,8 @@ function NetworkedMapPointer({ session, active }) { function animate() { request = requestAnimationFrame(animate); const time = performance.now(); - let interpolatedPointerState = {}; - for (let interp of Object.values(interpolationsRef.current)) { + let interpolatedPointerState: any = {}; + for (let interp of Object.values(interpolationsRef.current) as any) { if (!interp.from || !interp.to) { continue; } @@ -191,7 +192,7 @@ function NetworkedMapPointer({ session, active }) { return ( - {Object.values(localPointerState).map((pointer) => ( + {Object.values(localPointerState).map((pointer: any) => ( (null); const [partyStreams, setPartyStreams] = useState({}); const { addToast } = useToasts(); - function handleStreamStart(localStream) { + function handleStreamStart(localStream: MediaStream) { setStream(localStream); const tracks = localStream.getTracks(); for (let track of tracks) { // Only add the audio track of the stream to the remote peer if (track.kind === "audio") { 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 if (track.kind === "audio") { 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 - const joinedPlayersRef = useRef([]); + const joinedPlayersRef = useRef([]); useEffect(() => { if (joinedPlayersRef.current.length > 0) { for (let id of joinedPlayersRef.current) { @@ -70,12 +72,12 @@ function NetworkedParty({ gameId, session }) { }, [partyState, addToast]); useEffect(() => { - function handlePlayerJoined(sessionId) { + function handlePlayerJoined(sessionId: string) { if (stream) { const tracks = stream.getTracks(); for (let track of tracks) { 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); } - function handlePlayerLeft(sessionId) { + function handlePlayerLeft(sessionId: string) { if (partyState[sessionId]) { addToast(`${partyState[sessionId].nickname} left the party`); } } - function handlePeerTrackAdded({ peer, stream: remoteStream }) { + function handlePeerTrackAdded({ peer, stream: remoteStream }: { peer: SessionPeer, stream: MediaStream}) { setPartyStreams((prevStreams) => ({ ...prevStreams, [peer.id]: remoteStream, })); } - function handlePeerTrackRemoved({ peer, stream: remoteStream }) { + function handlePeerTrackRemoved({ peer, stream: remoteStream }: { peer: SessionPeer, stream: MediaStream }) { if (isStreamStopped(remoteStream)) { setPartyStreams((prevStreams) => omit(prevStreams, [peer.id])); } else { @@ -108,16 +110,16 @@ function NetworkedParty({ gameId, session }) { } } - session.on("playerJoined", handlePlayerJoined); - session.on("playerLeft", handlePlayerLeft); - session.on("peerTrackAdded", handlePeerTrackAdded); - session.on("peerTrackRemoved", handlePeerTrackRemoved); + props.session.on("playerJoined", handlePlayerJoined); + props.session.on("playerLeft", handlePlayerLeft); + props.session.on("peerTrackAdded", handlePeerTrackAdded); + props.session.on("peerTrackRemoved", handlePeerTrackRemoved); return () => { - session.off("playerJoined", handlePlayerJoined); - session.off("playerLeft", handlePlayerLeft); - session.off("peerTrackAdded", handlePeerTrackAdded); - session.off("peerTrackRemoved", handlePeerTrackRemoved); + props.session.off("playerJoined", handlePlayerJoined); + props.session.off("playerLeft", handlePlayerLeft); + props.session.off("peerTrackAdded", handlePeerTrackAdded); + props.session.off("peerTrackRemoved", handlePeerTrackRemoved); }; }); @@ -140,7 +142,7 @@ function NetworkedParty({ gameId, session }) { return ( <> (); - const [stripe, setStripe] = useState(); + const [stripe, setStripe]: [ stripe: Stripe | undefined, setStripe: React.Dispatch] = useState(); useEffect(() => { 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) => { - setStripe(stripe); - setLoading(false); + if (stripe) { + setStripe(stripe); + setLoading(false); + } }) .catch((error) => { logError(error); + // TODO: check setError -> cannot work with value as a string setError(error.message); setLoading(false); }); }); }, []); - async function handleSubmit(event) { + async function handleSubmit(event: any) { event.preventDefault(); if (loading) { return; @@ -64,9 +73,9 @@ function Donate() { } ); 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); } } @@ -74,10 +83,11 @@ function Donate() { const [selectedPrice, setSelectedPrice] = useState("Medium"); const [value, setValue] = useState(15); - function handlePriceChange(price) { + function handlePriceChange(price: Price) { setValue(price.value); setSelectedPrice(price.name); } + return ( setValue(e.target.value)} + onChange={(e: any) => setValue(e.target.value)} /> )} @@ -159,7 +169,7 @@ function Donate() {