Added all files successfully converted

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

View File

@ -9,7 +9,7 @@
"@msgpack/msgpack": "^2.4.1",
"@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",

View File

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

View File

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

View File

@ -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,7 +31,6 @@ function MapLoadingOverlay() {
loadingProgressRef={loadingProgressRef}
/>
</Box>
)
);
}

View File

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

View File

@ -1,10 +1,10 @@
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, ChangeEvent } from "react";
import { IconButton } from "theme-ui";
import 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 (
<>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,7 +6,7 @@ import Link from "../Link";
import StartStreamModal from "../../modals/StartStreamModal";
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

View File

@ -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() {

View File

@ -1,4 +1,4 @@
import React, { useState, useRef, useEffect } from "react";
import React, { useState, useRef, useEffect, ChangeEvent } from "react";
import { Text, IconButton, Box, Flex } from "theme-ui";
import 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();

View File

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

View File

@ -1,13 +1,16 @@
import React, { useState, useEffect, useContext } from "react";
import React, { useState, useEffect, useContext, SetStateAction } from "react";
import shortid from "shortid";
import { 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 });
}
}

View File

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

View File

@ -1,8 +1,14 @@
import React, { useState, useContext } from "react";
import React, { useState, useContext, ReactChild } from "react";
const DiceLoadingContext = React.createContext();
type DiceLoadingContext = {
assetLoadStart: any,
assetLoadFinish: any,
isLoading: boolean,
}
export function DiceLoadingProvider({ children }) {
const DiceLoadingContext = React.createContext<DiceLoadingContext | undefined>(undefined);
export function DiceLoadingProvider({ children }: { children: ReactChild }) {
const [loadingAssetCount, setLoadingAssetCount] = useState(0);
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");

View File

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

View File

@ -1,27 +1,28 @@
import React, { useContext, useState, useEffect } from "react";
import React, { useContext, useState, useEffect, ReactChild } from "react";
import { ImageFile } from "../helpers/image";
import { omit } from "../helpers/shared";
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,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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(() => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef } from "react";
import { useState, useEffect, useRef } from "react";
import { useToasts } from "react-toast-notifications";
import { 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) {

View File

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

View File

@ -1,14 +1,14 @@
import React, { useState, useEffect, useCallback, useRef } from "react";
import { useState, useEffect, useCallback, useRef } from "react";
import { useToasts } from "react-toast-notifications";
// 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}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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