Added all files successfully converted
This commit is contained in:
parent
c590adf836
commit
bfd0529207
@ -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",
|
||||
|
@ -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 && (
|
||||
|
@ -29,15 +29,14 @@ import Session from "../../network/Session";
|
||||
import { Grid } from "../../helpers/grid";
|
||||
import { ImageFile } from "../../helpers/image";
|
||||
|
||||
type Resolutions = Record<string, ImageFile>
|
||||
|
||||
export type Resolutions = Record<string, ImageFile>
|
||||
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<string, TokenState>,
|
||||
drawShapes: PathId | ShapeId,
|
||||
fogShapes: Fog[],
|
||||
editFlags: string[],
|
||||
editFlags: ["drawing", "tokens", "notes", "fog"],
|
||||
notes: Note[],
|
||||
mapId: string,
|
||||
}
|
||||
|
@ -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 && (
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
@ -29,8 +31,7 @@ function MapLoadingOverlay() {
|
||||
loadingProgressRef={loadingProgressRef}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
export default MapLoadingOverlay;
|
@ -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);
|
@ -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<HTMLInputElement>) {
|
||||
setChangedNickname(event.target?.value);
|
||||
}
|
||||
return (
|
||||
<>
|
@ -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 (
|
||||
<Flex sx={{ flexWrap: "wrap" }}>
|
||||
<Box sx={{ transform: "scale(0.8)" }}>{children}</Box>
|
||||
{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) => (
|
||||
<Text as="p" my={1} variant="caption" mx={1} key={index}>
|
||||
{dice.roll}
|
||||
</Text>
|
@ -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<boolean>(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(
|
||||
<DiceRoll rolls={rolls} type={icon.type} key={icon.type}>
|
||||
<icon.Icon />
|
||||
@ -40,8 +40,11 @@ function DiceRolls({ rolls }) {
|
||||
}
|
||||
}
|
||||
|
||||
if (total <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
total > 0 && (
|
||||
<Flex sx={{ flexDirection: "column" }}>
|
||||
<Flex sx={{ alignItems: "center" }}>
|
||||
<IconButton
|
||||
@ -65,7 +68,6 @@ function DiceRolls({ rolls }) {
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -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({
|
||||
<DiceTrayOverlay
|
||||
isOpen={isExpanded}
|
||||
shareDice={shareDice}
|
||||
onShareDiceChage={onShareDiceChage}
|
||||
onShareDiceChange={onShareDiceChange}
|
||||
diceRolls={diceRolls}
|
||||
onDiceRollsChange={onDiceRollsChange}
|
||||
/>
|
@ -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 (
|
||||
<Flex sx={{ flexDirection: "column" }}>
|
||||
<Text
|
@ -10,33 +10,34 @@ import SettingsButton from "../SettingsButton";
|
||||
import StartTimerButton from "./StartTimerButton";
|
||||
import Timer from "./Timer";
|
||||
import DiceTrayButton from "./DiceTrayButton";
|
||||
import { PartyState, PlayerDice, PlayerInfo, Timer as PartyTimer } from "./PartyState"
|
||||
|
||||
import useSetting from "../../hooks/useSetting";
|
||||
|
||||
import { useParty } from "../../contexts/PartyContext";
|
||||
import { usePlayerState, usePlayerUpdater } from "../../contexts/PlayerContext";
|
||||
|
||||
function Party({ gameId, stream, partyStreams, onStreamStart, onStreamEnd }) {
|
||||
function Party({ gameId, stream, partyStreams, onStreamStart, onStreamEnd }: { gameId: string, stream: any, partyStreams: any, onStreamStart: any, onStreamEnd: any}) {
|
||||
const setPlayerState = usePlayerUpdater();
|
||||
const playerState = usePlayerState();
|
||||
const partyState = useParty();
|
||||
const playerState: PlayerInfo = usePlayerState();
|
||||
const partyState: PartyState = useParty();
|
||||
|
||||
const [fullScreen] = useSetting("map.fullScreen");
|
||||
const [shareDice, setShareDice] = useSetting("dice.shareDice");
|
||||
|
||||
function handleTimerStart(newTimer) {
|
||||
setPlayerState((prevState) => ({ ...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 */}
|
||||
<Nickname
|
||||
nickname={`${playerState.nickname} (you)`}
|
||||
diceRolls={shareDice && playerState.dice.rolls}
|
||||
@ -167,7 +169,7 @@ function Party({ gameId, stream, partyStreams, onStreamStart, onStreamEnd }) {
|
||||
</Box>
|
||||
<DiceTrayButton
|
||||
shareDice={shareDice}
|
||||
onShareDiceChage={handleShareDiceChange}
|
||||
onShareDiceChange={handleShareDiceChange}
|
||||
diceRolls={(playerState.dice && playerState.dice.rolls) || []}
|
||||
onDiceRollsChange={handleDiceRollsChange}
|
||||
/>
|
39
src/components/party/PartyState.ts
Normal file
39
src/components/party/PartyState.ts
Normal file
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @typedef {object} Timer
|
||||
* @property {number} current
|
||||
* @property {number} max
|
||||
*/
|
||||
export type Timer = {
|
||||
current: number,
|
||||
max: number
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {object} PlayerDice
|
||||
* @property {boolean} share
|
||||
* @property {[]} rolls
|
||||
*/
|
||||
export type PlayerDice = { share: boolean, rolls: [] }
|
||||
|
||||
/**
|
||||
* @typedef {object} PlayerInfo
|
||||
* @property {string} nickname
|
||||
* @property {Timer | null} timer
|
||||
* @property {PlayerDice} dice
|
||||
* @property {string} sessionId
|
||||
* @property {string} userId
|
||||
*/
|
||||
export type PlayerInfo = {
|
||||
nickname: string,
|
||||
timer: Timer | null,
|
||||
dice: PlayerDice,
|
||||
sessionId: string,
|
||||
userId: string
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {object} PartyState
|
||||
* @property {string} player
|
||||
* @property {PlayerInfo} playerInfo
|
||||
*/
|
||||
export type PartyState = { [player: string]: PlayerInfo }
|
@ -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
|
@ -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() {
|
@ -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<any>();
|
||||
|
||||
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<HTMLInputElement>) {
|
||||
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<any>();
|
||||
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();
|
@ -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<any>();
|
||||
|
||||
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;
|
@ -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<any>; }
|
||||
|
||||
let storage;
|
||||
// TODO: check what default value we want here
|
||||
const AuthContext = React.createContext<AuthContext | undefined>(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<string>(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<SetStateAction<string>> ] = 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 });
|
||||
}
|
||||
}
|
||||
|
@ -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<any>; }
|
||||
|
||||
// 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<SetStateAction<Dexie | undefined>>] = useState();
|
||||
const [databaseStatus, setDatabaseStatus]: [ datebaseStatus: any, setDatabaseStatus: React.Dispatch<SetStateAction<string>>] = useState("loading");
|
||||
const [databaseError, setDatabaseError]: [ databaseError: Error | undefined, setDatabaseError: React.Dispatch<SetStateAction<Error | undefined>>] = 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}
|
||||
<ErrorBanner
|
||||
error={databaseError}
|
||||
onRequestClose={() => setDatabaseError()}
|
||||
onRequestClose={() => setDatabaseError(undefined)}
|
||||
/>
|
||||
</>
|
||||
</DatabaseContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useDatabase() {
|
||||
export function useDatabase(): DatabaseContext {
|
||||
const context = useContext(DatabaseContext);
|
||||
if (context === undefined) {
|
||||
throw new Error("useDatabase must be used within a DatabaseProvider");
|
@ -1,8 +1,14 @@
|
||||
import React, { useState, useContext } from "react";
|
||||
import React, { useState, useContext, ReactChild } from "react";
|
||||
|
||||
const DiceLoadingContext = React.createContext();
|
||||
type DiceLoadingContext = {
|
||||
assetLoadStart: any,
|
||||
assetLoadFinish: any,
|
||||
isLoading: boolean,
|
||||
}
|
||||
|
||||
export function DiceLoadingProvider({ children }) {
|
||||
const DiceLoadingContext = React.createContext<DiceLoadingContext | undefined>(undefined);
|
||||
|
||||
export function DiceLoadingProvider({ children }: { children: ReactChild }) {
|
||||
const [loadingAssetCount, setLoadingAssetCount] = useState(0);
|
||||
|
||||
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");
|
@ -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<Grid>, 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<Grid>;
|
||||
}
|
||||
|
||||
const [gridPixelSize, setGridPixelSize] = useState(
|
@ -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<any>({});
|
||||
|
||||
// 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,
|
@ -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<Map>,
|
||||
ownedMaps: Array<Map>
|
||||
mapStates: MapState[],
|
||||
addMap: (map: Map) => void,
|
||||
removeMap: (id: string) => void,
|
||||
removeMaps: (ids: string[]) => void,
|
||||
resetMap: (id: string) => void,
|
||||
updateMap: (id: string, update: Partial<Map>) => void,
|
||||
updateMaps: (ids: string[], update: Partial<Map>) => void,
|
||||
updateMapState: (id: string, update: Partial<MapState>) => void,
|
||||
putMap: (map: Map) => void,
|
||||
getMap: (id: string) => Map | undefined,
|
||||
getMapFromDB: (id: string) => Promise<Map>,
|
||||
mapsLoading: boolean,
|
||||
getMapStateFromDB: (id: string) => Promise<MapState>,
|
||||
}
|
||||
|
||||
const MapDataContext = React.createContext<MapDataContext | undefined>(undefined);
|
||||
|
||||
// Maximum number of maps to keep in the cache
|
||||
const cachedMapMax = 15;
|
||||
|
||||
const defaultMapState = {
|
||||
tokens: {},
|
||||
drawShapes: {},
|
||||
fogShapes: {},
|
||||
const defaultMapState: MapState = {
|
||||
mapId: "",
|
||||
tokens: {} as Record<string, TokenState>,
|
||||
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<Array<Map>>([]);
|
||||
const [mapStates, setMapStates] = useState<MapState[]>([]);
|
||||
const [mapsLoading, setMapsLoading] = useState<boolean>(true);
|
||||
|
||||
// Load maps from the database and ensure state is properly setup
|
||||
// Load maps from the database and ensure state is properly seup
|
||||
useEffect(() => {
|
||||
if (!userId || !database || databaseStatus === "loading") {
|
||||
return;
|
||||
}
|
||||
async function getDefaultMaps() {
|
||||
const defaultMapsWithIds = [];
|
||||
async function getDefaultMaps(): Promise<Map[]> {
|
||||
const defaultMapsWithIds: Array<Map> = [];
|
||||
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<Map> = [...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) {
|
@ -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,
|
@ -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<any | undefined>(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<any>({});
|
||||
const loadingProgressRef = useRef<number | null>(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;
|
||||
}
|
@ -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);
|
@ -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<PartyState | undefined>(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);
|
@ -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<any>(undefined);
|
||||
export const PlayerUpdaterContext = React.createContext<any>(() => {});
|
||||
|
||||
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();
|
||||
}
|
@ -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,
|
||||
};
|
@ -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<Record<string, Mesh>> {
|
||||
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(
|
@ -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;
|
@ -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<string, Mesh>;
|
||||
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
|
@ -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<string, Mesh>;
|
||||
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
|
@ -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<string, Mesh>;
|
||||
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
|
@ -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<string, Dice>;
|
||||
|
||||
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<string, string>;
|
||||
|
||||
export const dicePreviews: DicePreview = {
|
||||
galaxy: GalaxyPreview,
|
||||
nebula: NebulaPreview,
|
||||
sunrise: SunrisePreview,
|
@ -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<string, Mesh>;
|
||||
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
|
@ -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<string, Mesh>;
|
||||
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
|
@ -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<string, Mesh>;
|
||||
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
|
@ -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<string, Mesh>;
|
||||
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
|
@ -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<string, Mesh>;
|
||||
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
|
@ -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<Texture> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let texture = new Texture(
|
||||
url,
|
||||
|
@ -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(() => {
|
@ -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);
|
@ -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<Omit<Map, "lastModified" | "created" | "showGrid" | "snapToGrid" | "id" | "owner" | "group" |"file" | "quality" | "resolutions" | "lastUsed" |"thumbnail">> = Object.keys(mapSources).map((key) => ({
|
||||
key,
|
||||
name: Case.capital(key),
|
||||
grid: {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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<string[]>([]);
|
||||
|
||||
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({
|
||||
</Button>
|
||||
</Flex>
|
||||
</ImageDrop>
|
||||
<>
|
||||
{(isLoading || mapsLoading) && <LoadingOverlay bg="overlay" />}
|
||||
</>
|
||||
<EditMapModal
|
||||
isOpen={isEditModalOpen}
|
||||
onDone={() => setIsEditModalOpen(false)}
|
||||
mapId={selectedMaps.length === 1 && selectedMaps[0].id}
|
||||
// TODO: check with Mitch what to do here if length > 1
|
||||
//selectedMaps.length === 1 &&
|
||||
mapId={selectedMaps[0].id}
|
||||
/>
|
||||
<EditGroupModal
|
||||
isOpen={isGroupModalOpen}
|
||||
|
@ -6,10 +6,10 @@ type StartStreamProps = {
|
||||
isOpen: boolean,
|
||||
onRequestClose: () => void,
|
||||
isSupported: boolean,
|
||||
unavailableMessage: string,
|
||||
unavailableMessage: JSX.Element,
|
||||
stream: MediaStream,
|
||||
noAudioTrack: boolean,
|
||||
noAudioMessage: string,
|
||||
noAudioMessage: JSX.Element,
|
||||
onStreamStart: any,
|
||||
onStreamEnd: any,
|
||||
}
|
||||
|
@ -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<any>(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<any>(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<any>();
|
||||
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) {
|
@ -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<any> = 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 (
|
||||
<Group>
|
||||
{Object.values(localPointerState).map((pointer) => (
|
||||
{Object.values(localPointerState).map((pointer: any) => (
|
||||
<MapPointer
|
||||
key={pointer.id}
|
||||
active={pointer.id === userId ? active : false}
|
@ -1,14 +1,14 @@
|
||||
import React, { useState, useEffect, useCallback, useRef } from "react";
|
||||
import { useState, useEffect, useCallback, useRef } from "react";
|
||||
import { useToasts } from "react-toast-notifications";
|
||||
|
||||
// Load session for auto complete
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import Session from "./Session";
|
||||
import Session, { SessionPeer } from "./Session";
|
||||
import { isStreamStopped, omit } from "../helpers/shared";
|
||||
|
||||
import { useParty } from "../contexts/PartyContext";
|
||||
|
||||
import Party from "../components/party/Party";
|
||||
import { PartyState } from "../components/party/PartyState";
|
||||
|
||||
/**
|
||||
* @typedef {object} NetworkedPartyProps
|
||||
@ -16,24 +16,26 @@ import Party from "../components/party/Party";
|
||||
* @property {Session} session
|
||||
*/
|
||||
|
||||
type NetworkedPartyProps = { gameId: string, session: Session }
|
||||
|
||||
/**
|
||||
* @param {NetworkedPartyProps} props
|
||||
*/
|
||||
function NetworkedParty({ gameId, session }) {
|
||||
const partyState = useParty();
|
||||
const [stream, setStream] = useState(null);
|
||||
function NetworkedParty(props: NetworkedPartyProps) {
|
||||
const partyState: PartyState = useParty();
|
||||
const [stream, setStream] = useState<MediaStream | null>(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<string[]>([]);
|
||||
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 (
|
||||
<>
|
||||
<Party
|
||||
gameId={gameId}
|
||||
gameId={props.gameId}
|
||||
onStreamStart={handleStreamStart}
|
||||
onStreamEnd={handleStreamEnd}
|
||||
stream={stream}
|
@ -15,7 +15,7 @@ import { SimplePeerData } from "simple-peer";
|
||||
* @property {boolean} initiator - Is this peer the initiator of the connection
|
||||
* @property {boolean} ready - Ready for data to be sent
|
||||
*/
|
||||
type SessionPeer = {
|
||||
export type SessionPeer = {
|
||||
id: string;
|
||||
connection: Connection;
|
||||
initiator: boolean;
|
||||
@ -137,7 +137,7 @@ class Session extends EventEmitter {
|
||||
* @param {object} data
|
||||
* @param {string} channel
|
||||
*/
|
||||
sendTo(sessionId: string, eventId: string, data: SimplePeerData, channel: string) {
|
||||
sendTo(sessionId: string, eventId: string, data: SimplePeerData, channel?: string) {
|
||||
if (!(sessionId in this.peers)) {
|
||||
if (!this._addPeer(sessionId, true)) {
|
||||
return;
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import { Flex, Text, Link as ExternalLink } from "theme-ui";
|
||||
|
||||
import Footer from "../components/Footer";
|
@ -16,37 +16,46 @@ import ErrorBanner from "../components/banner/ErrorBanner";
|
||||
import LoadingOverlay from "../components/LoadingOverlay";
|
||||
|
||||
import { logError } from "../helpers/logging";
|
||||
import { Stripe } from "@stripe/stripe-js";
|
||||
|
||||
const prices = [
|
||||
type Price = { price?: string, name: string, value: number }
|
||||
|
||||
const prices: Price[] = [
|
||||
{ price: "$5.00", name: "Small", value: 5 },
|
||||
{ price: "$15.00", name: "Medium", value: 15 },
|
||||
{ price: "$30.00", name: "Large", value: 30 },
|
||||
];
|
||||
|
||||
function Donate() {
|
||||
const location = useLocation();
|
||||
const query = new URLSearchParams(location.search);
|
||||
const hasDonated = query.has("success");
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
// TODO: check with Mitch about changes here from useState(null)
|
||||
// TODO: typing with error a little messy
|
||||
const [error, setError]= useState<any>();
|
||||
|
||||
const [stripe, setStripe] = useState();
|
||||
const [stripe, setStripe]: [ stripe: Stripe | undefined, setStripe: React.Dispatch<Stripe | undefined >] = useState();
|
||||
useEffect(() => {
|
||||
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 (
|
||||
<Flex
|
||||
sx={{
|
||||
@ -149,7 +159,7 @@ function Donate() {
|
||||
name="donation"
|
||||
min={1}
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
onChange={(e: any) => setValue(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
@ -159,7 +169,7 @@ function Donate() {
|
||||
</Flex>
|
||||
<Footer />
|
||||
{loading && <LoadingOverlay />}
|
||||
<ErrorBanner error={error} onRequestClose={() => setError()} />
|
||||
<ErrorBanner error={error as Error} onRequestClose={() => setError(undefined)} />
|
||||
</Flex>
|
||||
);
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import { Flex, Text, Box } from "theme-ui";
|
||||
import raw from "raw.macro";
|
||||
|
@ -25,7 +25,7 @@ import NetworkedParty from "../network/NetworkedParty";
|
||||
import Session from "../network/Session";
|
||||
|
||||
function Game() {
|
||||
const { id: gameId } = useParams();
|
||||
const { id: gameId }: { id: string } = useParams();
|
||||
const { password } = useAuth();
|
||||
const { databaseStatus } = useDatabase();
|
||||
|
||||
@ -44,9 +44,9 @@ function Game() {
|
||||
}, [session]);
|
||||
|
||||
// Handle session errors
|
||||
const [peerError, setPeerError] = useState(null);
|
||||
const [peerError, setPeerError]: [ peerError: any, setPeerError: React.Dispatch<any>] = useState(null);
|
||||
useEffect(() => {
|
||||
function handlePeerError({ error }) {
|
||||
function handlePeerError({ error }: { error: any }) {
|
||||
if (error.code === "ERR_WEBRTC_SUPPORT") {
|
||||
setPeerError("WebRTC not supported.");
|
||||
} else if (error.code === "ERR_CREATE_OFFER") {
|
||||
@ -60,7 +60,7 @@ function Game() {
|
||||
}, [session]);
|
||||
|
||||
useEffect(() => {
|
||||
function handleStatus(status) {
|
||||
function handleStatus(status: any) {
|
||||
setSessionStatus(status);
|
||||
}
|
||||
|
||||
@ -92,7 +92,7 @@ function Game() {
|
||||
}
|
||||
}, [gameId, password, databaseStatus, session, sessionStatus]);
|
||||
|
||||
function handleAuthSubmit(newPassword) {
|
||||
function handleAuthSubmit(newPassword: string) {
|
||||
if (databaseStatus !== "loading") {
|
||||
session.joinGame(gameId, newPassword);
|
||||
}
|
||||
@ -100,7 +100,7 @@ function Game() {
|
||||
|
||||
// A ref to the Konva stage
|
||||
// the ref will be assigned in the MapInteraction component
|
||||
const mapStageRef = useRef();
|
||||
const mapStageRef: React.MutableRefObject<any> = useRef();
|
||||
|
||||
return (
|
||||
<PlayerProvider session={session}>
|
@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import { Flex, Text } from "theme-ui";
|
||||
import raw from "raw.macro";
|
||||
import { useLocation } from "react-router-dom";
|
@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import { Flex, Text } from "theme-ui";
|
||||
import raw from "raw.macro";
|
||||
import { useLocation } from "react-router-dom";
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"target": "es6",
|
||||
"lib": [
|
||||
"dom",
|
||||
|
82
yarn.lock
82
yarn.lock
@ -2603,35 +2603,35 @@
|
||||
"@svgr/plugin-svgo" "^5.5.0"
|
||||
loader-utils "^2.0.0"
|
||||
|
||||
"@tensorflow/tfjs-backend-cpu@3.3.0":
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-3.3.0.tgz#aa0a3ed2c6237a6e0c169678c5bd4b5a88766b1c"
|
||||
integrity sha512-DLctv+PUZni26kQW1hq8jwQQ8u+GGc/p764WQIC4/IDagGtfGAUW1mHzWcTxtni2l4re1VrwE41ogWLhv4sGHg==
|
||||
"@tensorflow/tfjs-backend-cpu@3.6.0":
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-3.6.0.tgz#4e64a7cf1c33b203f71f8f77cd7b0ac1ef25a871"
|
||||
integrity sha512-ZpAs17hPdKXadbtNjAsymYUILe8V7+pY4fYo8j25nfDTW/HfBpyAwsHPbMcA/n5zyJ7ZJtGKFcCUv1sl24KL1Q==
|
||||
dependencies:
|
||||
"@types/seedrandom" "2.4.27"
|
||||
seedrandom "2.4.3"
|
||||
|
||||
"@tensorflow/tfjs-backend-webgl@3.3.0":
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-3.3.0.tgz#29dd665f6a856c9defcb9108164f845e1fdcd02e"
|
||||
integrity sha512-GWCtXbrjPTyye3ooId9GlcNDwnIMskZarUpNIQ5g/zeISLfwEQoutA/UqJF+HzuEHgGMsWFkmaO3xKVT7UMpdg==
|
||||
"@tensorflow/tfjs-backend-webgl@3.6.0":
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-3.6.0.tgz#1ea1a73abea8d6324fd81aedf7f187ab6eb73692"
|
||||
integrity sha512-zp7l4TmD1khgeSux/Ujaaj8M/v+e8JVIKjOci6HCGaeMNrn74lTSH9oqGPWKUCmpZME17/V0LfRHK34ddmrPSA==
|
||||
dependencies:
|
||||
"@tensorflow/tfjs-backend-cpu" "3.3.0"
|
||||
"@tensorflow/tfjs-backend-cpu" "3.6.0"
|
||||
"@types/offscreencanvas" "~2019.3.0"
|
||||
"@types/seedrandom" "2.4.27"
|
||||
"@types/webgl-ext" "0.0.30"
|
||||
"@types/webgl2" "0.0.5"
|
||||
seedrandom "2.4.3"
|
||||
|
||||
"@tensorflow/tfjs-converter@3.3.0":
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@tensorflow/tfjs-converter/-/tfjs-converter-3.3.0.tgz#d9f2ffd0fbdbb47c07d5fd7c3e5dc180cff317aa"
|
||||
integrity sha512-k57wN4yelePhmO9orcT/wzGMIuyedrMpVtg0FhxpV6BQu0+TZ/ti3W4Kb97GWJsoHKXMoing9SnioKfVnBW6hw==
|
||||
"@tensorflow/tfjs-converter@3.6.0":
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@tensorflow/tfjs-converter/-/tfjs-converter-3.6.0.tgz#32b3ff31b47e29630a82e30fbe01708facad7fd6"
|
||||
integrity sha512-9MtatbTSvo3gpEulYI6+byTA3OeXSMT2lzyGAegXO9nMxsvjR01zBvlZ5SmsNyecNh6fMSzdL2+cCdQfQtsIBg==
|
||||
|
||||
"@tensorflow/tfjs-core@3.3.0":
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@tensorflow/tfjs-core/-/tfjs-core-3.3.0.tgz#3d26bd03cb58e0ecf46c96d118c39c4a90b7f5ed"
|
||||
integrity sha512-6G+LcCiQBl4Kza5mDbWbf8QSWBTW3l7SDjGhQzMO1ITtQatHzxkuHGHcJ4CTUJvNA0JmKf4QJWOvlFqEmxwyLQ==
|
||||
"@tensorflow/tfjs-core@3.6.0":
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@tensorflow/tfjs-core/-/tfjs-core-3.6.0.tgz#6b4d8175790bdff78868eabe6adc6442eb4dc276"
|
||||
integrity sha512-bb2c3zwK4SgXZRvkTiC7EhCpWbCGp0GMd+1/3Vo2/Z54jiLB/h3sXIgHQrTNiWwhKPtst/xxA+MsslFlvD0A5w==
|
||||
dependencies:
|
||||
"@types/offscreencanvas" "~2019.3.0"
|
||||
"@types/seedrandom" "2.4.27"
|
||||
@ -2639,30 +2639,30 @@
|
||||
node-fetch "~2.6.1"
|
||||
seedrandom "2.4.3"
|
||||
|
||||
"@tensorflow/tfjs-data@3.3.0":
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@tensorflow/tfjs-data/-/tfjs-data-3.3.0.tgz#ba943bd6a486fa4cb3ca312c12646ea4dcf6cce4"
|
||||
integrity sha512-0x28tRe6RJu5GmYq3IYN2GNnOgXU0nY+o6zZrlijkK+W3vjSTJlZzaBSifoeD6J8gzVpjs8W8qd/JKHQ1MQp8w==
|
||||
"@tensorflow/tfjs-data@3.6.0":
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@tensorflow/tfjs-data/-/tfjs-data-3.6.0.tgz#af2f03cffb75ad8e4c2f46e192e392d9b7f977ed"
|
||||
integrity sha512-5KU7fnU7cj/opb4aCNDoW4qma64ggDwI0PCs5KEO41T3waVHDLk6bjlFlBVRdjfZqvM0K6EfWEyoiXzdvz/Ieg==
|
||||
dependencies:
|
||||
"@types/node-fetch" "^2.1.2"
|
||||
node-fetch "~2.6.1"
|
||||
|
||||
"@tensorflow/tfjs-layers@3.3.0":
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@tensorflow/tfjs-layers/-/tfjs-layers-3.3.0.tgz#d2097c5b22ec12e5fdbe470a88ca0a34a95ca11f"
|
||||
integrity sha512-qO+TL2I29vWUiuFcQJXNyayWFYagwR+SIfbex8p5jjYaCGHGwE5GQcrH+ngoCgKZxm5tdMvYJsJPnih2M3fYzQ==
|
||||
"@tensorflow/tfjs-layers@3.6.0":
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@tensorflow/tfjs-layers/-/tfjs-layers-3.6.0.tgz#5358af559fc8baed304b3e567319fe93f1aa46a6"
|
||||
integrity sha512-B7EHwAT6KFqhKzdf0e2Sr6haj9qpqpyEATV8OCPHdk+g8z2AGXOLlFfbgW6vCMjy1wb5jzYqCyZDoY3EWdgJAw==
|
||||
|
||||
"@tensorflow/tfjs@^3.3.0":
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@tensorflow/tfjs/-/tfjs-3.3.0.tgz#db92099dd48c0eb1c1673f705125d2b57496a1a3"
|
||||
integrity sha512-xo22GCUCGcPtNGIdDpLPrp9ms3atXmzX8AF4y3aIBEwK5KlvGe+ZhcoQ2xEOCPQGBr7NB7AO6rwT8gRoziAHVg==
|
||||
"@tensorflow/tfjs@^3.6.0":
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@tensorflow/tfjs/-/tfjs-3.6.0.tgz#e65956cd40c96523e3f5ec7a58a4bef9ef5e349c"
|
||||
integrity sha512-uLDMDzyRkJa3fYBeR6etQTFD/t+nkQIH/DznL9hxmYoIYG8PigY2gcrc482TAvsdhiuvxCZ9rl5SyDtP93MvxQ==
|
||||
dependencies:
|
||||
"@tensorflow/tfjs-backend-cpu" "3.3.0"
|
||||
"@tensorflow/tfjs-backend-webgl" "3.3.0"
|
||||
"@tensorflow/tfjs-converter" "3.3.0"
|
||||
"@tensorflow/tfjs-core" "3.3.0"
|
||||
"@tensorflow/tfjs-data" "3.3.0"
|
||||
"@tensorflow/tfjs-layers" "3.3.0"
|
||||
"@tensorflow/tfjs-backend-cpu" "3.6.0"
|
||||
"@tensorflow/tfjs-backend-webgl" "3.6.0"
|
||||
"@tensorflow/tfjs-converter" "3.6.0"
|
||||
"@tensorflow/tfjs-core" "3.6.0"
|
||||
"@tensorflow/tfjs-data" "3.6.0"
|
||||
"@tensorflow/tfjs-layers" "3.6.0"
|
||||
argparse "^1.0.10"
|
||||
chalk "^4.1.0"
|
||||
core-js "3"
|
||||
@ -2873,6 +2873,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
|
||||
integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
|
||||
|
||||
"@types/file-saver@^2.0.2":
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/file-saver/-/file-saver-2.0.2.tgz#bd593ccfaee42ff94a5c1c83bf69ae9be83493b9"
|
||||
integrity sha512-xbqnZmGrCEqi/KUzOkeUSe77p7APvLuyellGaAoeww3CHJ1AbjQWjPSCFtKIzZn8L7LpEax4NXnC+gfa6nM7IA==
|
||||
|
||||
"@types/glob@^7.1.1":
|
||||
version "7.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183"
|
||||
@ -2943,6 +2948,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
||||
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
|
||||
|
||||
"@types/lodash.clonedeep@^4.5.6":
|
||||
version "4.5.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.6.tgz#3b6c40a0affe0799a2ce823b440a6cf33571d32b"
|
||||
integrity sha512-cE1jYr2dEg1wBImvXlNtp0xDoS79rfEdGozQVgliDZj1uERH4k+rmEMTudP9b4VQ8O6nRb5gPqft0QzEQGMQgA==
|
||||
dependencies:
|
||||
"@types/lodash" "*"
|
||||
|
||||
"@types/lodash.get@^4.4.6":
|
||||
version "4.4.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash.get/-/lodash.get-4.4.6.tgz#0c7ac56243dae0f9f09ab6f75b29471e2e777240"
|
||||
|
Loading…
Reference in New Issue
Block a user