From ecc4f67f3777097b2f07a3789e754fa07d3a12c2 Mon Sep 17 00:00:00 2001 From: Nicola Thouliss Date: Wed, 2 Jun 2021 17:49:31 +1000 Subject: [PATCH] Converted /modals to typescript --- .../{LoadingOverlay.js => LoadingOverlay.tsx} | 3 +- src/components/{Modal.js => Modal.tsx} | 16 +- src/components/{Slider.js => Slider.tsx} | 19 ++- src/components/map/{Map.js => Map.tsx} | 142 +++++++++++++++--- ...KeyboardContext.js => KeyboardContext.tsx} | 10 +- ...kenDataContext.js => TokenDataContext.tsx} | 103 +++++++------ src/global.d.ts | 3 +- src/helpers/drawing.ts | 40 ++--- src/helpers/grid.ts | 10 +- src/helpers/image.ts | 2 +- src/helpers/select.tsx | 15 +- src/ml/Model.ts | 2 +- ...MemberModal.js => AddPartyMemberModal.tsx} | 11 +- src/modals/{AuthModal.js => AuthModal.tsx} | 18 +-- ...cknameModal.js => ChangeNicknameModal.tsx} | 12 +- .../{ConfirmModal.js => ConfirmModal.tsx} | 14 +- .../{EditGroupModal.js => EditGroupModal.tsx} | 22 ++- .../{EditMapModal.js => EditMapModal.tsx} | 33 ++-- .../{EditTokenModal.js => EditTokenModal.tsx} | 27 ++-- ...rceUpdateModal.js => ForceUpdateModal.tsx} | 5 +- ...meExpiredModal.js => GameExpiredModal.tsx} | 5 +- ...tartedModal.js => GettingStartedModal.tsx} | 5 +- ...rtExportModal.js => ImportExportModal.tsx} | 37 ++--- src/modals/{JoinModal.js => JoinModal.tsx} | 13 +- ...SelectDataModal.js => SelectDataModal.tsx} | 73 +++++---- ...SelectDiceModal.js => SelectDiceModal.tsx} | 14 +- .../{SelectMapModal.js => SelectMapModal.tsx} | 59 +++++--- ...ctTokensModal.js => SelectTokensModal.tsx} | 57 ++++--- .../{SettingsModal.js => SettingsModal.tsx} | 30 ++-- src/modals/{StartModal.js => StartModal.tsx} | 12 +- ...artStreamModal.js => StartStreamModal.tsx} | 15 +- ...StartTimerModal.js => StartTimerModal.tsx} | 18 ++- src/tokens/index.ts | 37 ++++- 33 files changed, 571 insertions(+), 311 deletions(-) rename src/components/{LoadingOverlay.js => LoadingOverlay.tsx} (89%) rename src/components/{Modal.js => Modal.tsx} (75%) rename src/components/{Slider.js => Slider.tsx} (77%) rename src/components/map/{Map.js => Map.tsx} (71%) rename src/contexts/{KeyboardContext.js => KeyboardContext.tsx} (87%) rename src/contexts/{TokenDataContext.js => TokenDataContext.tsx} (65%) rename src/modals/{AddPartyMemberModal.js => AddPartyMemberModal.tsx} (83%) rename src/modals/{AuthModal.js => AuthModal.tsx} (61%) rename src/modals/{ChangeNicknameModal.js => ChangeNicknameModal.tsx} (76%) rename src/modals/{ConfirmModal.js => ConfirmModal.tsx} (79%) rename src/modals/{EditGroupModal.js => EditGroupModal.tsx} (71%) rename src/modals/{EditMapModal.js => EditMapModal.tsx} (85%) rename src/modals/{EditTokenModal.js => EditTokenModal.tsx} (81%) rename src/modals/{ForceUpdateModal.js => ForceUpdateModal.tsx} (79%) rename src/modals/{GameExpiredModal.js => GameExpiredModal.tsx} (79%) rename src/modals/{GettingStartedModal.js => GettingStartedModal.tsx} (80%) rename src/modals/{ImportExportModal.js => ImportExportModal.tsx} (86%) rename src/modals/{JoinModal.js => JoinModal.tsx} (79%) rename src/modals/{SelectDataModal.js => SelectDataModal.tsx} (78%) rename src/modals/{SelectDiceModal.js => SelectDiceModal.tsx} (77%) rename src/modals/{SelectMapModal.js => SelectMapModal.tsx} (90%) rename src/modals/{SelectTokensModal.js => SelectTokensModal.tsx} (86%) rename src/modals/{SettingsModal.js => SettingsModal.tsx} (87%) rename src/modals/{StartModal.js => StartModal.tsx} (83%) rename src/modals/{StartStreamModal.js => StartStreamModal.tsx} (80%) rename src/modals/{StartTimerModal.js => StartTimerModal.tsx} (90%) diff --git a/src/components/LoadingOverlay.js b/src/components/LoadingOverlay.tsx similarity index 89% rename from src/components/LoadingOverlay.js rename to src/components/LoadingOverlay.tsx index 887f9a1..62cff28 100644 --- a/src/components/LoadingOverlay.js +++ b/src/components/LoadingOverlay.tsx @@ -1,9 +1,8 @@ -import React from "react"; import { Box } from "theme-ui"; import Spinner from "./Spinner"; -function LoadingOverlay({ bg }) { +function LoadingOverlay({ bg }: any ) { return ( @@ -46,7 +50,7 @@ function StyledModal({ StyledModal.defaultProps = { allowClose: true, - style: {}, + style: {} }; export default StyledModal; diff --git a/src/components/Slider.js b/src/components/Slider.tsx similarity index 77% rename from src/components/Slider.js rename to src/components/Slider.tsx index fdc601b..d45abac 100644 --- a/src/components/Slider.js +++ b/src/components/Slider.tsx @@ -1,10 +1,19 @@ -import React, { useState } from "react"; -import { Box, Slider as ThemeSlider } from "theme-ui"; +import { useState } from "react"; +import { Box, Slider as ThemeSlider, SliderProps } from "theme-ui"; -function Slider({ min, max, value, ml, mr, labelFunc, ...rest }) { +type SliderModalProps = SliderProps & { + min: number, + max: number, + value: number, + ml: any, + mr: any, + labelFunc: any +} + +function Slider({ min, max, value, ml, mr, labelFunc, ...rest }: SliderModalProps ) { const percentValue = ((value - min) * 100) / (max - min); - const [labelVisible, setLabelVisible] = useState(false); + const [labelVisible, setLabelVisible] = useState(false); return ( @@ -63,7 +72,7 @@ Slider.defaultProps = { value: 0, ml: 0, mr: 0, - labelFunc: (value) => value, + labelFunc: (value: any) => value, }; export default Slider; diff --git a/src/components/map/Map.js b/src/components/map/Map.tsx similarity index 71% rename from src/components/map/Map.js rename to src/components/map/Map.tsx index 340749e..8f8f539 100644 --- a/src/components/map/Map.js +++ b/src/components/map/Map.tsx @@ -24,6 +24,78 @@ import { EditShapeAction, RemoveShapeAction, } from "../../actions"; +import { Fog, Path, Shape } from "../../helpers/drawing"; +import Session from "../../network/Session"; +import { Grid } from "../../helpers/grid"; +import { ImageFile } from "../../helpers/image"; + +type Resolutions = Record + +export type Map = { + id: string, + name: string, + owner: string, + file: Uint8Array, + quality: string, + resolutions: Resolutions, + grid: Grid, + group: string, + width: number, + height: number, + type: string, + lastUsed: number, + lastModified: number, + created: number, + showGrid: boolean, + snapToGrid: boolean, + thumbnail: ImageFile, +} + +export type Note = { + id: string, + color: string, + lastModified: number, + lastModifiedBy: string, + locked: boolean, + size: number, + text: string, + textOnly: boolean, + visible: boolean, + x: number, + y: number, +} + +export type TokenState = { + id: string, + tokenId: string, + owner: string, + size: number, + label: string, + status: string[], + x: number, + y: number, + lastModifiedBy: string, + lastModified: number, + rotation: number, + locked: boolean, + visible: boolean +} + +interface PathId extends Path { + id: string +} + +interface ShapeId extends Shape { + id: string +} +export type MapState = { + tokens: Record, + drawShapes: PathId | ShapeId, + fogShapes: Fog[], + editFlags: string[], + notes: Note[], + mapId: string, +} function Map({ map, @@ -47,14 +119,36 @@ function Map({ allowNoteEditing, disabledTokens, session, +}: { + map: any + mapState: MapState + mapActions: any, + onMapTokenStateChange: any, + onMapTokenStateRemove: any, + onMapChange: any, + onMapReset: any, + onMapDraw: any, + onMapDrawUndo: any, + onMapDrawRedo: any, + onFogDraw: any, + onFogDrawUndo: any, + onFogDrawRedo: any, + onMapNoteChange: any, + onMapNoteRemove: any, + allowMapDrawing: boolean, + allowFogDrawing: boolean, + allowMapChange: boolean, + allowNoteEditing: boolean, + disabledTokens: any, + session: Session }) { const { tokensById } = useTokenData(); const [selectedToolId, setSelectedToolId] = useState("move"); - const { settings, setSettings } = useSettings(); + const { settings, setSettings }: { settings: any, setSettings: any} = useSettings(); - function handleToolSettingChange(tool, change) { - setSettings((prevSettings) => ({ + function handleToolSettingChange(tool: any, change: any) { + setSettings((prevSettings: any) => ({ ...prevSettings, [tool]: { ...prevSettings[tool], @@ -66,7 +160,7 @@ function Map({ const drawShapes = Object.values(mapState?.drawShapes || {}); const fogShapes = Object.values(mapState?.fogShapes || {}); - function handleToolAction(action) { + function handleToolAction(action: string) { if (action === "eraseAll") { onMapDraw(new RemoveShapeAction(drawShapes.map((s) => s.id))); } @@ -84,27 +178,27 @@ function Map({ } } - function handleMapShapeAdd(shape) { + function handleMapShapeAdd(shape: Shape) { onMapDraw(new AddShapeAction([shape])); } - function handleMapShapesRemove(shapeIds) { + function handleMapShapesRemove(shapeIds: string[]) { onMapDraw(new RemoveShapeAction(shapeIds)); } - function handleFogShapesAdd(shapes) { + function handleFogShapesAdd(shapes: Shape[]) { onFogDraw(new AddShapeAction(shapes)); } - function handleFogShapesCut(shapes) { + function handleFogShapesCut(shapes: Shape[]) { onFogDraw(new CutShapeAction(shapes)); } - function handleFogShapesRemove(shapeIds) { + function handleFogShapesRemove(shapeIds: string[]) { onFogDraw(new RemoveShapeAction(shapeIds)); } - function handleFogShapesEdit(shapes) { + function handleFogShapesEdit(shapes: Shape[]) { onFogDraw(new EditShapeAction(shapes)); } @@ -127,7 +221,7 @@ function Map({ disabledControls.push("note"); } - const disabledSettings = { fog: [], drawing: [] }; + const disabledSettings: { fog: any[], drawing: any[]} = { fog: [], drawing: [] }; if (drawShapes.length === 0) { disabledSettings.drawing.push("erase"); } @@ -166,10 +260,10 @@ function Map({ /> ); - const [isTokenMenuOpen, setIsTokenMenuOpen] = useState(false); - const [tokenMenuOptions, setTokenMenuOptions] = useState({}); - const [tokenDraggingOptions, setTokenDraggingOptions] = useState(); - function handleTokenMenuOpen(tokenStateId, tokenImage) { + const [isTokenMenuOpen, setIsTokenMenuOpen]: [ isTokenMenuOpen: boolean, setIsTokenMenuOpen: React.Dispatch>] = useState(false); + const [tokenMenuOptions, setTokenMenuOptions]: [ tokenMenuOptions: any, setTokenMenuOptions: any ] = useState({}); + const [tokenDraggingOptions, setTokenDraggingOptions]: [ tokenDraggingOptions: any, setTokenDragginOptions: any ] = useState(); + function handleTokenMenuOpen(tokenStateId: string, tokenImage: any) { setTokenMenuOptions({ tokenStateId, tokenImage }); setIsTokenMenuOpen(true); } @@ -200,7 +294,7 @@ function Map({ const tokenDragOverlay = tokenDraggingOptions && ( { + onTokenStateRemove={(state: any) => { onMapTokenStateRemove(state); setTokenDraggingOptions(null); }} @@ -243,7 +337,6 @@ function Map({ ); @@ -254,15 +347,15 @@ function Map({ /> ); - const [isNoteMenuOpen, setIsNoteMenuOpen] = useState(false); - const [noteMenuOptions, setNoteMenuOptions] = useState({}); - const [noteDraggingOptions, setNoteDraggingOptions] = useState(); - function handleNoteMenuOpen(noteId, noteNode) { + const [isNoteMenuOpen, setIsNoteMenuOpen] = useState(false); + const [noteMenuOptions, setNoteMenuOptions] = useState({}); + const [noteDraggingOptions, setNoteDraggingOptions]= useState(); + function handleNoteMenuOpen(noteId: string, noteNode: any) { setNoteMenuOptions({ noteId, noteNode }); setIsNoteMenuOpen(true); } - function sortNotes(a, b, noteDraggingOptions) { + function sortNotes(a: any, b: any, noteDraggingOptions: any) { if ( noteDraggingOptions && noteDraggingOptions.dragging && @@ -287,7 +380,6 @@ function Map({ + onNoteDragStart={(e: any, noteId: any) => setNoteDraggingOptions({ dragging: true, noteId, noteGroup: e.target }) } onNoteDragEnd={() => @@ -328,7 +420,7 @@ function Map({ dragging={!!(noteDraggingOptions && noteDraggingOptions.dragging)} noteGroup={noteDraggingOptions && noteDraggingOptions.noteGroup} noteId={noteDraggingOptions && noteDraggingOptions.noteId} - onNoteRemove={(noteId) => { + onNoteRemove={(noteId: any) => { onMapNoteRemove(noteId); setNoteDraggingOptions(null); }} diff --git a/src/contexts/KeyboardContext.js b/src/contexts/KeyboardContext.tsx similarity index 87% rename from src/contexts/KeyboardContext.js rename to src/contexts/KeyboardContext.tsx index 592e60f..43fdf61 100644 --- a/src/contexts/KeyboardContext.js +++ b/src/contexts/KeyboardContext.tsx @@ -3,10 +3,10 @@ import { EventEmitter } from "events"; const KeyboardContext = React.createContext({ keyEmitter: new EventEmitter() }); -export function KeyboardProvider({ children }) { +export function KeyboardProvider({ children }: { children: any}) { const [keyEmitter] = useState(new EventEmitter()); useEffect(() => { - function handleKeyDown(event) { + function handleKeyDown(event: Event) { // Ignore text input if ( event.target instanceof HTMLInputElement || @@ -17,7 +17,7 @@ export function KeyboardProvider({ children }) { keyEmitter.emit("keyDown", event); } - function handleKeyUp(event) { + function handleKeyUp(event: Event) { // Ignore text input if ( event.target instanceof HTMLInputElement || @@ -49,7 +49,7 @@ export function KeyboardProvider({ children }) { * @param {KeyboardEvent} onKeyDown * @param {KeyboardEvent} onKeyUp */ -export function useKeyboard(onKeyDown, onKeyUp) { +export function useKeyboard(onKeyDown: (...args: any[]) => void, onKeyUp: (...args: any[]) => void) { const context = useContext(KeyboardContext); if (context === undefined) { throw new Error("useKeyboard must be used within a KeyboardProvider"); @@ -78,7 +78,7 @@ export function useKeyboard(onKeyDown, onKeyUp) { * Handler to handle a blur event. Useful when using a shortcut that uses the Alt or Cmd * @param {FocusEvent} onBlur */ -export function useBlur(onBlur) { +export function useBlur(onBlur: EventListenerOrEventListenerObject) { useEffect(() => { if (onBlur) { window.addEventListener("blur", onBlur); diff --git a/src/contexts/TokenDataContext.js b/src/contexts/TokenDataContext.tsx similarity index 65% rename from src/contexts/TokenDataContext.js rename to src/contexts/TokenDataContext.tsx index 329a509..dd49630 100644 --- a/src/contexts/TokenDataContext.js +++ b/src/contexts/TokenDataContext.tsx @@ -10,13 +10,29 @@ import { decode } from "@msgpack/msgpack"; import { useAuth } from "./AuthContext"; import { useDatabase } from "./DatabaseContext"; -import { tokens as defaultTokens } from "../tokens"; +import { DefaultToken, FileToken, Token, tokens as defaultTokens } from "../tokens"; -const TokenDataContext = React.createContext(); +type TokenDataContext = { + tokens: Token[]; + ownedTokens: Token[]; + addToken: (token: Token) => Promise; + removeToken: (id: string) => Promise; + removeTokens: (ids: string[]) => Promise; + updateToken: (id: string, update: Partial) => Promise; + updateTokens: (ids: string[], update: Partial) => Promise; + putToken: (token: Token) => Promise; + getToken: (tokenId: string) => Token | undefined + tokensById: { [key: string]: Token; }; + tokensLoading: boolean; + getTokenFromDB: (tokenId: string) => Promise; + loadTokens: (tokenIds: string[]) => Promise; +} + +const TokenDataContext = React.createContext(undefined); const cachedTokenMax = 100; -export function TokenDataProvider({ children }) { +export function TokenDataProvider({ children }: { children: any }) { const { database, databaseStatus, worker } = useDatabase(); const { userId } = useAuth(); @@ -24,7 +40,7 @@ export function TokenDataProvider({ children }) { * Contains all tokens without any file data, * to ensure file data is present call loadTokens */ - const [tokens, setTokens] = useState([]); + const [tokens, setTokens] = useState([]); const [tokensLoading, setTokensLoading] = useState(true); useEffect(() => { @@ -32,13 +48,12 @@ export function TokenDataProvider({ children }) { return; } function getDefaultTokens() { - const defaultTokensWithIds = []; + const defaultTokensWithIds: Required = []; for (let defaultToken of defaultTokens) { defaultTokensWithIds.push({ ...defaultToken, id: `__default-${defaultToken.name}`, owner: userId, - group: "default", }); } return defaultTokensWithIds; @@ -46,19 +61,19 @@ export function TokenDataProvider({ children }) { // Loads tokens without the file data to save memory async function loadTokens() { - let storedTokens = []; + let storedTokens: any = []; // Try to load tokens with worker, fallback to database if failed - const packedTokens = await worker.loadData("tokens"); + const packedTokens: ArrayLike | BufferSource = await worker.loadData("tokens"); if (packedTokens) { storedTokens = decode(packedTokens); } else { console.warn("Unable to load tokens with worker, loading may be slow"); - await database.table("tokens").each((token) => { - const { file, resolutions, ...rest } = token; + await database?.table("tokens").each((token: FileToken) => { + const { file, ...rest } = token; storedTokens.push(rest); }); } - const sortedTokens = storedTokens.sort((a, b) => b.created - a.created); + const sortedTokens = storedTokens.sort((a: any, b: any) => b.created - a.created); const defaultTokensWithIds = getDefaultTokens(); const allTokens = [...sortedTokens, ...defaultTokensWithIds]; setTokens(allTokens); @@ -79,7 +94,7 @@ export function TokenDataProvider({ children }) { const getTokenFromDB = useCallback( async (tokenId) => { - let token = await database.table("tokens").get(tokenId); + let token = await database?.table("tokens").get(tokenId); return token; }, [database] @@ -90,23 +105,23 @@ export function TokenDataProvider({ children }) { * Sorted by when they we're last used */ const updateCache = useCallback(async () => { - const cachedTokens = await database - .table("tokens") - .where("owner") - .notEqual(userId) - .sortBy("lastUsed"); - if (cachedTokens.length > cachedTokenMax) { - const cacheDeleteCount = cachedTokens.length - cachedTokenMax; + const cachedTokens: Token[] | undefined = await database?.table("tokens").where("owner").notEqual(userId).sortBy("lastUsed"); + // TODO: handle undefined cachedTokens + if (!cachedTokens) { + return; + } + if (cachedTokens?.length > cachedTokenMax) { + const cacheDeleteCount = cachedTokens.length - cachedTokenMax const idsToDelete = cachedTokens .slice(0, cacheDeleteCount) .map((token) => token.id); - database.table("tokens").where("id").anyOf(idsToDelete).delete(); + database?.table("tokens").where("id").anyOf(idsToDelete).delete(); } }, [database, userId]); const addToken = useCallback( async (token) => { - await database.table("tokens").add(token); + await database?.table("tokens").add(token); if (token.owner !== userId) { await updateCache(); } @@ -115,23 +130,23 @@ export function TokenDataProvider({ children }) { ); const removeToken = useCallback( - async (id) => { - await database.table("tokens").delete(id); + async (id: string) => { + await database?.table("tokens").delete(id); }, [database] ); const removeTokens = useCallback( - async (ids) => { - await database.table("tokens").bulkDelete(ids); + async (ids: string[]) => { + await database?.table("tokens").bulkDelete(ids); }, [database] ); const updateToken = useCallback( - async (id, update) => { + async (id: string, update: any) => { const change = { lastModified: Date.now(), ...update }; - await database.table("tokens").update(id, change); + await database?.table("tokens").update(id, change); }, [database] ); @@ -140,7 +155,7 @@ export function TokenDataProvider({ children }) { async (ids, update) => { const change = { lastModified: Date.now(), ...update }; await Promise.all( - ids.map((id) => database.table("tokens").update(id, change)) + ids.map((id: string) => database?.table("tokens").update(id, change)) ); }, [database] @@ -148,7 +163,7 @@ export function TokenDataProvider({ children }) { const putToken = useCallback( async (token) => { - await database.table("tokens").put(token); + await database?.table("tokens").put(token); if (token.owner !== userId) { await updateCache(); } @@ -157,13 +172,17 @@ export function TokenDataProvider({ children }) { ); const loadTokens = useCallback( - async (tokenIds) => { - const loadedTokens = await database.table("tokens").bulkGet(tokenIds); - const loadedTokensById = loadedTokens.reduce((obj, token) => { + async (tokenIds: string[]) => { + const loadedTokens: FileToken[] | undefined = await database?.table("tokens").bulkGet(tokenIds); + const loadedTokensById = loadedTokens?.reduce((obj: { [key: string]: FileToken }, token: FileToken) => { obj[token.id] = token; return obj; }, {}); - setTokens((prevTokens) => { + if (!loadedTokensById) { + // TODO: whatever + return; + } + setTokens((prevTokens: Token[]) => { return prevTokens.map((prevToken) => { if (prevToken.id in loadedTokensById) { return loadedTokensById[prevToken.id]; @@ -176,22 +195,13 @@ export function TokenDataProvider({ children }) { [database] ); - const unloadTokens = useCallback(async () => { - setTokens((prevTokens) => { - return prevTokens.map((prevToken) => { - const { file, ...rest } = prevToken; - return rest; - }); - }); - }, []); - // Create DB observable to sync creating and deleting useEffect(() => { if (!database || databaseStatus === "loading") { return; } - function handleTokenChanges(changes) { + function handleTokenChanges(changes: any) { for (let change of changes) { if (change.table === "tokens") { if (change.type === 1) { @@ -230,12 +240,12 @@ export function TokenDataProvider({ children }) { const ownedTokens = tokens.filter((token) => token.owner === userId); - const tokensById = tokens.reduce((obj, token) => { + const tokensById: { [key: string]: Token; } = tokens.reduce((obj: { [key: string]: Token }, token) => { obj[token.id] = token; return obj; }, {}); - const value = { + const value: TokenDataContext = { tokens, ownedTokens, addToken, @@ -249,7 +259,6 @@ export function TokenDataProvider({ children }) { tokensLoading, getTokenFromDB, loadTokens, - unloadTokens, }; return ( @@ -259,7 +268,7 @@ export function TokenDataProvider({ children }) { ); } -export function useTokenData() { +export function useTokenData(): TokenDataContext { const context = useContext(TokenDataContext); if (context === undefined) { throw new Error("useTokenData must be used within a TokenDataProvider"); diff --git a/src/global.d.ts b/src/global.d.ts index 7fb08ab..9d0600f 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -4,4 +4,5 @@ declare module 'fake-indexeddb'; declare module 'fake-indexeddb/lib/FDBKeyRange'; declare module '*.glb'; declare module '*.png'; -declare module '*.mp4'; \ No newline at end of file +declare module '*.mp4'; +declare module '*.bin'; \ No newline at end of file diff --git a/src/helpers/drawing.ts b/src/helpers/drawing.ts index 1a43972..99f5b73 100644 --- a/src/helpers/drawing.ts +++ b/src/helpers/drawing.ts @@ -70,7 +70,7 @@ type ShapeType = "line" | "rectangle" | "circle" | "triangle" * @typedef {("fill"|"stroke")} PathType */ -// type PathType = "fill" | "stroke" +type PathType = "fill" | "stroke" /** * @typedef Path @@ -83,15 +83,15 @@ type ShapeType = "line" | "rectangle" | "circle" | "triangle" * @property {"path"} type */ -// type Path = { -// blend: boolean, -// color: string, -// data: PointsData, -// id: string, -// pathType: PathType, -// strokeWidth: number, -// type: "path" -// } +export type Path = { + blend: boolean, + color: string, + data: PointsData, + id: string, + pathType: PathType, + strokeWidth: number, + type: "path" +} /** * @typedef Shape @@ -104,15 +104,15 @@ type ShapeType = "line" | "rectangle" | "circle" | "triangle" * @property {"shape"} type */ -// type Shape = { -// blend: boolean, -// color: string, -// data: ShapeData, -// id: string, -// shapeType: ShapeType, -// strokeWidth: number, -// type: "shape" -// } +export type Shape = { + blend: boolean, + color: string, + data: ShapeData, + id: string, + shapeType: ShapeType, + strokeWidth: number, + type: "shape" +} /** * @typedef Fog @@ -124,7 +124,7 @@ type ShapeType = "line" | "rectangle" | "circle" | "triangle" * @property {boolean} visible */ -type Fog = { +export type Fog = { color: string, data: FogData, id: string, diff --git a/src/helpers/grid.ts b/src/helpers/grid.ts index 9ec8f40..20e475f 100644 --- a/src/helpers/grid.ts +++ b/src/helpers/grid.ts @@ -38,10 +38,10 @@ type GridMeasurement ={ * @property {GridMeasurement} measurement */ export type Grid = { - inset: GridInset, + inset?: GridInset, size: Vector2, type: ("square"|"hexVertical"|"hexHorizontal"), - measurement: GridMeasurement + measurement?: GridMeasurement } /** @@ -51,7 +51,7 @@ export type Grid = { * @param {number} baseHeight Height of the grid in pixels before inset * @returns {Size} */ -export function getGridPixelSize(grid: Grid, baseWidth: number, baseHeight: number): Size { +export function getGridPixelSize(grid: Required, baseWidth: number, baseHeight: number): Size { const width = (grid.inset.bottomRight.x - grid.inset.topLeft.x) * baseWidth; const height = (grid.inset.bottomRight.y - grid.inset.topLeft.y) * baseHeight; return new Size(width, height); @@ -225,7 +225,7 @@ export function getGridDefaultInset(grid: Grid, mapWidth: number, mapHeight: num * @param {number} mapHeight Height of the map in pixels before inset * @returns {GridInset} */ -export function getGridUpdatedInset(grid: Grid, mapWidth: number, mapHeight: number): GridInset { +export function getGridUpdatedInset(grid: Required, mapWidth: number, mapHeight: number): GridInset { let inset = grid.inset; // Take current inset width and use it to calculate the new height if (grid.size.x > 0 && grid.size.x > 0) { @@ -295,7 +295,7 @@ export function hexOffsetToCube(offset: Vector2, type: ("hexVertical"|"hexHorizo * @param {Vector2} b * @param {Size} cellSize */ -export function gridDistance(grid: Grid, a: Vector2, b: Vector2, cellSize: Size) { +export function gridDistance(grid: Required, a: Vector2, b: Vector2, cellSize: Size) { // Get grid coordinates const aCoord = getNearestCellCoordinates(grid, a.x, a.y, cellSize); const bCoord = getNearestCellCoordinates(grid, b.x, b.y, cellSize); diff --git a/src/helpers/image.ts b/src/helpers/image.ts index c35b7c3..33477b1 100644 --- a/src/helpers/image.ts +++ b/src/helpers/image.ts @@ -109,7 +109,7 @@ export async function resizeImage(image: HTMLImageElement, size: number, type: s * @property {string} id */ -type ImageFile = { +export type ImageFile = { file: Uint8Array | null, width: number, height: number, diff --git a/src/helpers/select.tsx b/src/helpers/select.tsx index 7ad1ef7..8184add 100644 --- a/src/helpers/select.tsx +++ b/src/helpers/select.tsx @@ -8,7 +8,7 @@ import { groupBy } from "./shared"; */ // Helper for generating search results for items -export function useSearch(items: [], search: string) { +export function useSearch(items: any[], search: string) { // TODO: add types to search items -> don't like the never type const [filteredItems, setFilteredItems]: [ filteredItems: any, @@ -18,10 +18,7 @@ export function useSearch(items: [], search: string) { filteredItemScores: {}, setFilteredItemScores: React.Dispatch> ] = useState({}); - const [fuse, setFuse]: [ - fuse: Fuse | undefined, - setFuse: React.Dispatch | undefined> - ] = useState(); + const [fuse, setFuse] = useState(); // Update search index when items change useEffect(() => { @@ -85,7 +82,7 @@ export function useGroup( export function handleItemSelect( item: any, selectMode: any, - selectedIds: number[], + selectedIds: string[], setSelectedIds: any, itemsByGroup: any, itemGroups: any @@ -120,15 +117,15 @@ export function handleItemSelect( const lastIndex = items.findIndex( (m: any) => m.id === selectedIds[selectedIds.length - 1] ); - let idsToAdd: number[] = []; - let idsToRemove: number[] = []; + let idsToAdd: string[] = []; + let idsToRemove: string[] = []; const direction = mapIndex > lastIndex ? 1 : -1; for ( let i = lastIndex + direction; direction < 0 ? i >= mapIndex : i <= mapIndex; i += direction ) { - const itemId: number = items[i].id; + const itemId: string = items[i].id; if (selectedIds.includes(itemId)) { idsToRemove.push(itemId); } else { diff --git a/src/ml/Model.ts b/src/ml/Model.ts index 3c554bd..98fc567 100644 --- a/src/ml/Model.ts +++ b/src/ml/Model.ts @@ -1,4 +1,4 @@ -import { ModelJSON, WeightsManifestConfig } from "@tensorflow/tfjs-core/dist/io/types"; +import { ModelJSON } from "@tensorflow/tfjs-core/dist/io/types"; import blobToBuffer from "../helpers/blobToBuffer"; class Model { diff --git a/src/modals/AddPartyMemberModal.js b/src/modals/AddPartyMemberModal.tsx similarity index 83% rename from src/modals/AddPartyMemberModal.js rename to src/modals/AddPartyMemberModal.tsx index 7a29568..ece2f7f 100644 --- a/src/modals/AddPartyMemberModal.js +++ b/src/modals/AddPartyMemberModal.tsx @@ -1,9 +1,16 @@ -import React from "react"; import { Box, Label, Text } from "theme-ui"; import Modal from "../components/Modal"; -function AddPartyMemberModal({ isOpen, onRequestClose, gameId }) { +function AddPartyMemberModal({ + isOpen, + onRequestClose, + gameId, +}: { + isOpen: boolean; + onRequestClose: any; + gameId: string; +}) { return ( diff --git a/src/modals/AuthModal.js b/src/modals/AuthModal.tsx similarity index 61% rename from src/modals/AuthModal.js rename to src/modals/AuthModal.tsx index 0c19e13..589d549 100644 --- a/src/modals/AuthModal.js +++ b/src/modals/AuthModal.tsx @@ -1,27 +1,27 @@ -import React, { useState, useRef } from "react"; +import { useState, useRef, ChangeEvent, FormEvent } from "react"; import { Box, Input, Button, Label, Flex } from "theme-ui"; import { useAuth } from "../contexts/AuthContext"; import Modal from "../components/Modal"; -function AuthModal({ isOpen, onSubmit }) { +function AuthModal({ isOpen, onSubmit }: { isOpen: boolean, onSubmit: (newPassword: string) => void}) { const { password, setPassword } = useAuth(); - const [tmpPassword, setTempPassword] = useState(password); + const [tmpPassword, setTempPassword] = useState(password); - function handleChange(event) { - setTempPassword(event.target.value); + function handleChange(event: ChangeEvent): void { + setTempPassword(event.target?.value); } - function handleSubmit(event) { + function handleSubmit(event: FormEvent): void { event.preventDefault(); setPassword(tmpPassword); onSubmit(tmpPassword); } - const inputRef = useRef(); - function focusInput() { - inputRef.current && inputRef.current.focus(); + const inputRef = useRef(); + function focusInput(): void { + inputRef.current && inputRef.current?.focus(); } return ( diff --git a/src/modals/ChangeNicknameModal.js b/src/modals/ChangeNicknameModal.tsx similarity index 76% rename from src/modals/ChangeNicknameModal.js rename to src/modals/ChangeNicknameModal.tsx index 8c6916c..2e4353e 100644 --- a/src/modals/ChangeNicknameModal.js +++ b/src/modals/ChangeNicknameModal.tsx @@ -1,4 +1,4 @@ -import React, { useRef } from "react"; +import { useRef } from "react"; import { Box, Input, Button, Label, Flex } from "theme-ui"; import Modal from "../components/Modal"; @@ -9,10 +9,16 @@ function ChangeNicknameModal({ onChangeSubmit, nickname, onChange, +}: { + isOpen: boolean, + onRequestClose: () => void, + onChangeSubmit: any, + nickname: string, + onChange: any, }) { - const inputRef = useRef(); + const inputRef = useRef(null); function focusInput() { - inputRef.current && inputRef.current.focus(); + inputRef.current && inputRef.current?.focus(); } return ( diff --git a/src/modals/ConfirmModal.js b/src/modals/ConfirmModal.tsx similarity index 79% rename from src/modals/ConfirmModal.js rename to src/modals/ConfirmModal.tsx index 70c1402..16e7598 100644 --- a/src/modals/ConfirmModal.js +++ b/src/modals/ConfirmModal.tsx @@ -1,8 +1,16 @@ -import React from "react"; import { Box, Label, Flex, Button, Text } from "theme-ui"; import Modal from "../components/Modal"; +type ConfirmModalProps = { + isOpen: boolean, + onRequestClose: () => void, + onConfirm: () => void, + confirmText: string, + label: string, + description: string, +} + function ConfirmModal({ isOpen, onRequestClose, @@ -10,12 +18,12 @@ function ConfirmModal({ confirmText, label, description, -}) { +}: ConfirmModalProps ) { return ( diff --git a/src/modals/EditGroupModal.js b/src/modals/EditGroupModal.tsx similarity index 71% rename from src/modals/EditGroupModal.js rename to src/modals/EditGroupModal.tsx index a990bd0..6407274 100644 --- a/src/modals/EditGroupModal.js +++ b/src/modals/EditGroupModal.tsx @@ -1,24 +1,32 @@ -import React, { useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import { Box, Button, Label, Flex } from "theme-ui"; import Modal from "../components/Modal"; import Select from "../components/Select"; +type EditGroupProps = { + isOpen: boolean, + onRequestClose: () => void, + onChange: any, + groups: string[], + defaultGroup: string | undefined | false, +} + function EditGroupModal({ isOpen, onRequestClose, onChange, groups, defaultGroup, -}) { - const [value, setValue] = useState(); - const [options, setOptions] = useState([]); +}: EditGroupProps) { + const [value, setValue] = useState<{ value: string; label: string; } | undefined>(); + const [options, setOptions] = useState<{ value: string; label: string; }[]>([]); useEffect(() => { if (defaultGroup) { setValue({ value: defaultGroup, label: defaultGroup }); } else { - setValue(); + setValue(undefined); } }, [defaultGroup]); @@ -26,7 +34,7 @@ function EditGroupModal({ setOptions(groups.map((group) => ({ value: group, label: group }))); }, [groups]); - function handleCreate(group) { + function handleCreate(group: string) { const newOption = { value: group, label: group }; setValue(newOption); setOptions((prev) => [...prev, newOption]); @@ -40,7 +48,7 @@ function EditGroupModal({ diff --git a/src/modals/EditMapModal.js b/src/modals/EditMapModal.tsx similarity index 85% rename from src/modals/EditMapModal.js rename to src/modals/EditMapModal.tsx index ad38c8c..16743ae 100644 --- a/src/modals/EditMapModal.js +++ b/src/modals/EditMapModal.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import { Button, Flex, Label } from "theme-ui"; import Modal from "../components/Modal"; @@ -12,8 +12,15 @@ import { isEmpty } from "../helpers/shared"; import { getGridDefaultInset } from "../helpers/grid"; import useResponsiveLayout from "../hooks/useResponsiveLayout"; +import { MapState } from "../components/map/Map"; -function EditMapModal({ isOpen, onDone, mapId }) { +type EditMapProps = { + isOpen: boolean, + onDone: any, + mapId: string +} + +function EditMapModal({ isOpen, onDone, mapId }: EditMapProps) { const { updateMap, updateMapState, @@ -23,8 +30,8 @@ function EditMapModal({ isOpen, onDone, mapId }) { } = useMapData(); const [isLoading, setIsLoading] = useState(true); - const [map, setMap] = useState(); - const [mapState, setMapState] = useState(); + const [map, setMap] = useState(); + const [mapState, setMapState] = useState(); // Load full map when modal is opened useEffect(() => { async function loadMap() { @@ -43,8 +50,8 @@ function EditMapModal({ isOpen, onDone, mapId }) { if (isOpen && mapId) { loadMap(); } else { - setMap(); - setMapState(); + setMap(undefined); + setMapState(undefined); } }, [isOpen, mapId, getMapFromDB, getMapStateFromDB, getMap]); @@ -64,19 +71,19 @@ function EditMapModal({ isOpen, onDone, mapId }) { */ // Local cache of map setting changes // Applied when done is clicked or map selection is changed - const [mapSettingChanges, setMapSettingChanges] = useState({}); - const [mapStateSettingChanges, setMapStateSettingChanges] = useState({}); + const [mapSettingChanges, setMapSettingChanges] = useState({}); + const [mapStateSettingChanges, setMapStateSettingChanges] = useState({}); - function handleMapSettingsChange(key, value) { - setMapSettingChanges((prevChanges) => ({ + function handleMapSettingsChange(key: string, value: string) { + setMapSettingChanges((prevChanges: any) => ({ ...prevChanges, [key]: value, lastModified: Date.now(), })); } - function handleMapStateSettingsChange(key, value) { - setMapStateSettingChanges((prevChanges) => ({ + function handleMapStateSettingsChange(key: string, value: string) { + setMapStateSettingChanges((prevChanges: any) => ({ ...prevChanges, [key]: value, })); @@ -137,7 +144,7 @@ function EditMapModal({ isOpen, onDone, mapId }) { void, + tokenId: string, +}; + +function EditTokenModal({ isOpen, onDone, tokenId }: EditModalProps) { const { updateToken, getTokenFromDB } = useTokenData(); const [isLoading, setIsLoading] = useState(true); - const [token, setToken] = useState(); + const [token, setToken] = useState(); useEffect(() => { async function loadToken() { setIsLoading(true); @@ -27,7 +34,7 @@ function EditTokenModal({ isOpen, onDone, tokenId }) { if (isOpen && tokenId) { loadToken(); } else { - setToken(); + setToken(undefined); } }, [isOpen, tokenId, getTokenFromDB]); @@ -41,10 +48,13 @@ function EditTokenModal({ isOpen, onDone, tokenId }) { onDone(); } - const [tokenSettingChanges, setTokenSettingChanges] = useState({}); + const [tokenSettingChanges, setTokenSettingChanges] = useState({}); - function handleTokenSettingsChange(key, value) { - setTokenSettingChanges((prevChanges) => ({ ...prevChanges, [key]: value })); + function handleTokenSettingsChange(key: any, value: any) { + setTokenSettingChanges((prevChanges: any) => ({ + ...prevChanges, + [key]: value, + })); } async function applyTokenChanges() { @@ -72,8 +82,7 @@ function EditTokenModal({ isOpen, onDone, tokenId }) { isOpen={isOpen} onRequestClose={handleClose} style={{ - maxWidth: layout.modalSize, - width: "calc(100% - 16px)", + content: { maxWidth: layout.modalSize, width: "calc(100% - 16px)" }, }} > diff --git a/src/modals/GameExpiredModal.js b/src/modals/GameExpiredModal.tsx similarity index 79% rename from src/modals/GameExpiredModal.js rename to src/modals/GameExpiredModal.tsx index 981a2b7..23458d1 100644 --- a/src/modals/GameExpiredModal.js +++ b/src/modals/GameExpiredModal.tsx @@ -1,14 +1,13 @@ -import React from "react"; import { Box, Label, Flex, Button, Text } from "theme-ui"; import Modal from "../components/Modal"; -function GameExpiredModal({ isOpen, onRequestClose }) { +function GameExpiredModal({ isOpen, onRequestClose }: { isOpen: boolean, onRequestClose: () => void }) { return ( diff --git a/src/modals/GettingStartedModal.js b/src/modals/GettingStartedModal.tsx similarity index 80% rename from src/modals/GettingStartedModal.js rename to src/modals/GettingStartedModal.tsx index 9a8e157..15608fe 100644 --- a/src/modals/GettingStartedModal.js +++ b/src/modals/GettingStartedModal.tsx @@ -1,4 +1,3 @@ -import React from "react"; import { Box, Label, Text } from "theme-ui"; import raw from "raw.macro"; @@ -8,12 +7,12 @@ import Link from "../components/Link"; const gettingStarted = raw("../docs/howTo/gettingStarted.md"); -function GettingStartedModal({ isOpen, onRequestClose }) { +function GettingStartedModal({ isOpen, onRequestClose }: { isOpen: boolean, onRequestClose: () => void } ) { return ( diff --git a/src/modals/ImportExportModal.js b/src/modals/ImportExportModal.tsx similarity index 86% rename from src/modals/ImportExportModal.js rename to src/modals/ImportExportModal.tsx index b6e083c..79f59f2 100644 --- a/src/modals/ImportExportModal.js +++ b/src/modals/ImportExportModal.tsx @@ -1,4 +1,4 @@ -import React, { useRef, useState, useEffect } from "react"; +import { useRef, useState, useEffect } from "react"; import { Box, Label, Text, Button, Flex } from "theme-ui"; import { saveAs } from "file-saver"; import * as Comlink from "comlink"; @@ -16,24 +16,25 @@ import { useDatabase } from "../contexts/DatabaseContext"; import SelectDataModal from "./SelectDataModal"; import { getDatabase } from "../database"; +import { Map, MapState, TokenState } from "../components/map/Map"; const importDBName = "OwlbearRodeoImportDB"; -function ImportExportModal({ isOpen, onRequestClose }) { +function ImportExportModal({ isOpen, onRequestClose }: { isOpen: boolean, onRequestClose: () => void}) { const { worker } = useDatabase(); const { userId } = useAuth(); const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(); + const [error, setError] = useState(); const backgroundTaskRunningRef = useRef(false); - const fileInputRef = useRef(); + const fileInputRef = useRef(); const [showImportSelector, setShowImportSelector] = useState(false); const [showExportSelector, setShowExportSelector] = useState(false); const { addToast } = useToasts(); - function addSuccessToast(message, maps, tokens) { + function addSuccessToast(message: string, maps: any, tokens: TokenState[]) { const mapText = `${maps.length} map${maps.length > 1 ? "s" : ""}`; const tokenText = `${tokens.length} token${tokens.length > 1 ? "s" : ""}`; if (maps.length > 0 && tokens.length > 0) { @@ -53,11 +54,11 @@ function ImportExportModal({ isOpen, onRequestClose }) { const loadingProgressRef = useRef(0); - function handleDBProgress({ completedRows, totalRows }) { + function handleDBProgress({ completedRows, totalRows }: { completedRows: number, totalRows: number }) { loadingProgressRef.current = completedRows / totalRows; } - async function handleImportDatabase(file) { + async function handleImportDatabase(file: File) { setIsLoading(true); backgroundTaskRunningRef.current = true; try { @@ -94,7 +95,7 @@ function ImportExportModal({ isOpen, onRequestClose }) { } useEffect(() => { - function handleBeforeUnload(event) { + function handleBeforeUnload(event: any) { if (backgroundTaskRunningRef.current) { event.returnValue = "Database is still processing, are you sure you want to leave?"; @@ -121,7 +122,7 @@ function ImportExportModal({ isOpen, onRequestClose }) { setShowImportSelector(false); } - async function handleImportSelectorConfirm(checkedMaps, checkedTokens) { + async function handleImportSelectorConfirm(checkedMaps: any, checkedTokens: TokenState[]) { setIsLoading(true); backgroundTaskRunningRef.current = true; setShowImportSelector(false); @@ -131,11 +132,11 @@ function ImportExportModal({ isOpen, onRequestClose }) { const db = getDatabase({}); try { // Keep track of a mapping of old token ids to new ones to apply them to the map states - let newTokenIds = {}; + let newTokenIds: {[id: string]: string} = {}; if (checkedTokens.length > 0) { const tokenIds = checkedTokens.map((token) => token.id); - const tokensToAdd = await importDB.table("tokens").bulkGet(tokenIds); - let newTokens = []; + const tokensToAdd: TokenState[] = await importDB.table("tokens").bulkGet(tokenIds); + let newTokens: TokenState[] = []; for (let token of tokensToAdd) { const newId = shortid.generate(); newTokenIds[token.id] = newId; @@ -146,12 +147,12 @@ function ImportExportModal({ isOpen, onRequestClose }) { } if (checkedMaps.length > 0) { - const mapIds = checkedMaps.map((map) => map.id); + const mapIds = checkedMaps.map((map: any) => map.id); const mapsToAdd = await importDB.table("maps").bulkGet(mapIds); let newMaps = []; let newStates = []; for (let map of mapsToAdd) { - let state = await importDB.table("states").get(map.id); + let state: MapState = await importDB.table("states").get(map.id); // Apply new token ids to imported state for (let tokenState of Object.values(state.tokens)) { if (tokenState.tokenId in newTokenIds) { @@ -179,7 +180,7 @@ function ImportExportModal({ isOpen, onRequestClose }) { backgroundTaskRunningRef.current = false; } - function exportSelectorFilter(table, value) { + function exportSelectorFilter(table: any, value: Map | TokenState) { // Only show owned maps and tokens if (table === "maps" || table === "tokens") { if (value.owner === userId) { @@ -197,7 +198,7 @@ function ImportExportModal({ isOpen, onRequestClose }) { setShowExportSelector(false); } - async function handleExportSelectorConfirm(checkedMaps, checkedTokens) { + async function handleExportSelectorConfirm(checkedMaps: Map[], checkedTokens: TokenState[]) { setShowExportSelector(false); setIsLoading(true); backgroundTaskRunningRef.current = true; @@ -238,7 +239,7 @@ function ImportExportModal({ isOpen, onRequestClose }) { Select import or export then select the data you wish to use handleImportDatabase(event.target.files[0])} + onChange={(event) => event.target.files && handleImportDatabase(event.target.files[0])} type="file" accept=".owlbear" style={{ display: "none" }} @@ -272,7 +273,7 @@ function ImportExportModal({ isOpen, onRequestClose }) { )} - setError()} /> + setError(undefined)} /> ) { + setGameId(event.target?.value); } - function handleSubmit(event) { + function handleSubmit(event: FormEvent) { event.preventDefault(); history.push(`/game/${gameId}`); } - const inputRef = useRef(); + const inputRef = useRef(); function focusInput() { inputRef.current && inputRef.current.focus(); } @@ -27,6 +27,7 @@ function JoinModal({ isOpen, onRequestClose }) { isOpen={isOpen} onRequestClose={onRequestClose} onAfterOpen={focusInput} + > ({}); + const [tokensByMap, setTokensByMap] = useState({}); + const [tokens, setTokens] = useState({}); const [isLoading, setIsLoading] = useState(false); const hasMaps = Object.values(maps).length > 0; @@ -29,9 +38,9 @@ function SelectDataModal({ if (isOpen && databaseName) { setIsLoading(true); const db = getDatabase({ addons: [] }, databaseName); - let loadedMaps = {}; - let loadedTokensByMap = {}; - let loadedTokens = {}; + let loadedMaps: any = []; + let loadedTokensByMap: any = {}; + let loadedTokens: any = []; await db .table("maps") .filter((map) => filter("maps", map, map.id)) @@ -44,7 +53,7 @@ function SelectDataModal({ .each((state) => { loadedTokensByMap[state.mapId] = new Set( Object.values(state.tokens).map( - (tokenState) => tokenState.tokenId + (tokenState: any) => tokenState.tokenId ) ); }); @@ -73,9 +82,9 @@ function SelectDataModal({ }, [isOpen, databaseName, filter]); // An object mapping a tokenId to how many checked maps it is currently used in - const [tokenUsedCount, setTokenUsedCount] = useState({}); + const [tokenUsedCount, setTokenUsedCount] = useState({}); useEffect(() => { - let tokensUsed = {}; + let tokensUsed: any = {}; for (let mapId in maps) { if (maps[mapId].checked && mapId in tokensByMap) { for (let tokenId of tokensByMap[mapId]) { @@ -89,7 +98,7 @@ function SelectDataModal({ } setTokenUsedCount(tokensUsed); // Update tokens to ensure used tokens are checked - setTokens((prevTokens) => { + setTokens((prevTokens: any) => { let newTokens = { ...prevTokens }; for (let id in newTokens) { if (id in tokensUsed) { @@ -101,13 +110,13 @@ function SelectDataModal({ }, [maps, tokensByMap]); function handleConfirm() { - let checkedMaps = Object.values(maps).filter((map) => map.checked); - let checkedTokens = Object.values(tokens).filter((token) => token.checked); + let checkedMaps = Object.values(maps).filter((map: any) => map.checked); + let checkedTokens = Object.values(tokens).filter((token: any) => token.checked); onConfirm(checkedMaps, checkedTokens); } - function handleSelectMapsChanged(event) { - setMaps((prevMaps) => { + function handleSelectMapsChanged(event: ChangeEvent) { + setMaps((prevMaps: any) => { let newMaps = { ...prevMaps }; for (let id in newMaps) { newMaps[id].checked = event.target.checked; @@ -116,7 +125,7 @@ function SelectDataModal({ }); // If all token select is unchecked then ensure all tokens are unchecked if (!event.target.checked && !tokensSelectChecked) { - setTokens((prevTokens) => { + setTokens((prevTokens: any) => { let newTokens = { ...prevTokens }; for (let id in newTokens) { newTokens[id].checked = false; @@ -126,14 +135,14 @@ function SelectDataModal({ } } - function handleMapChange(event, map) { - setMaps((prevMaps) => ({ + function handleMapChange(event: ChangeEvent, map: any) { + setMaps((prevMaps: any) => ({ ...prevMaps, [map.id]: { ...map, checked: event.target.checked }, })); // If all token select is unchecked then ensure tokens assosiated to this map are unchecked if (!event.target.checked && !tokensSelectChecked) { - setTokens((prevTokens) => { + setTokens((prevTokens: any) => { let newTokens = { ...prevTokens }; for (let id in newTokens) { if (tokensByMap[map.id].has(id) && tokenUsedCount[id] === 1) { @@ -145,8 +154,8 @@ function SelectDataModal({ } } - function handleSelectTokensChange(event) { - setTokens((prevTokens) => { + function handleSelectTokensChange(event: ChangeEvent) { + setTokens((prevTokens: any) => { let newTokens = { ...prevTokens }; for (let id in newTokens) { if (!(id in tokenUsedCount)) { @@ -157,8 +166,8 @@ function SelectDataModal({ }); } - function handleTokenChange(event, token) { - setTokens((prevTokens) => ({ + function handleTokenChange(event: ChangeEvent, token: any) { + setTokens((prevTokens: any) => ({ ...prevTokens, [token.id]: { ...token, checked: event.target.checked }, })); @@ -167,14 +176,14 @@ function SelectDataModal({ // Some tokens are checked not by maps or all tokens are checked by maps const tokensSelectChecked = Object.values(tokens).some( - (token) => !(token.id in tokenUsedCount) && token.checked - ) || Object.values(tokens).every((token) => token.id in tokenUsedCount); + (token: any) => !(token.id in tokenUsedCount) && token.checked + ) || Object.values(tokens).every((token: any) => token.id in tokenUsedCount); return ( - {Object.values(maps).map((map) => ( + {Object.values(maps).map((map: any) => ( - {Object.values(tokens).map((token) => ( + {Object.values(tokens).map((token: any) => ( - {storageEstimate && ( + {storageEstimate !&& ( - Storage Used: {prettyBytes(storageEstimate.usage)} of{" "} - {prettyBytes(storageEstimate.quota)} ( + Storage Used: {prettyBytes(storageEstimate.usage as number)} of{" "} + {prettyBytes(storageEstimate.quota as number)} ( {Math.round( - (storageEstimate.usage / Math.max(storageEstimate.quota, 1)) * + (storageEstimate.usage as number / Math.max(storageEstimate.quota as number, 1)) * 100 )} %) diff --git a/src/modals/StartModal.js b/src/modals/StartModal.tsx similarity index 83% rename from src/modals/StartModal.js rename to src/modals/StartModal.tsx index 7aa23fd..58f0c70 100644 --- a/src/modals/StartModal.js +++ b/src/modals/StartModal.tsx @@ -1,4 +1,4 @@ -import React, { useRef } from "react"; +import { ChangeEvent, useRef } from "react"; import { Box, Label, Input, Button, Flex, Checkbox } from "theme-ui"; import { useHistory } from "react-router-dom"; import shortid from "shortid"; @@ -9,20 +9,20 @@ import useSetting from "../hooks/useSetting"; import Modal from "../components/Modal"; -function StartModal({ isOpen, onRequestClose }) { +function StartModal({ isOpen, onRequestClose }: { isOpen: boolean, onRequestClose: () => void}) { let history = useHistory(); const { password, setPassword } = useAuth(); - function handlePasswordChange(event) { + function handlePasswordChange(event: ChangeEvent) { setPassword(event.target.value); } const [usePassword, setUsePassword] = useSetting("game.usePassword"); - function handleUsePasswordChange(event) { + function handleUsePasswordChange(event: ChangeEvent) { setUsePassword(event.target.checked); } - function handleSubmit(event) { + function handleSubmit(event: ChangeEvent) { event.preventDefault(); if (!usePassword) { setPassword(""); @@ -30,7 +30,7 @@ function StartModal({ isOpen, onRequestClose }) { history.push(`/game/${shortid.generate()}`); } - const inputRef = useRef(); + const inputRef = useRef(); function focusInput() { inputRef.current && inputRef.current.focus(); } diff --git a/src/modals/StartStreamModal.js b/src/modals/StartStreamModal.tsx similarity index 80% rename from src/modals/StartStreamModal.js rename to src/modals/StartStreamModal.tsx index 9d44f98..03ac49c 100644 --- a/src/modals/StartStreamModal.js +++ b/src/modals/StartStreamModal.tsx @@ -1,8 +1,19 @@ -import React from "react"; import { Box, Text, Button, Label, Flex } from "theme-ui"; import Modal from "../components/Modal"; +type StartStreamProps = { + isOpen: boolean, + onRequestClose: () => void, + isSupported: boolean, + unavailableMessage: string, + stream: MediaStream, + noAudioTrack: boolean, + noAudioMessage: string, + onStreamStart: any, + onStreamEnd: any, +} + function StartStreamModal({ isOpen, onRequestClose, @@ -13,7 +24,7 @@ function StartStreamModal({ noAudioMessage, onStreamStart, onStreamEnd, -}) { +}: StartStreamProps) { return ( diff --git a/src/modals/StartTimerModal.js b/src/modals/StartTimerModal.tsx similarity index 90% rename from src/modals/StartTimerModal.js rename to src/modals/StartTimerModal.tsx index b6fdd94..d2a021f 100644 --- a/src/modals/StartTimerModal.js +++ b/src/modals/StartTimerModal.tsx @@ -1,4 +1,4 @@ -import React, { useRef } from "react"; +import { ChangeEvent, useRef } from "react"; import { Box, Label, Input, Button, Flex, Text } from "theme-ui"; import Modal from "../components/Modal"; @@ -7,14 +7,22 @@ import { getHMSDuration, getDurationHMS } from "../helpers/timer"; import useSetting from "../hooks/useSetting"; +type StartTimerProps = { + isOpen: boolean, + onRequestClose: () => void, + onTimerStart: any, + onTimerStop: any, + timer: any, +} + function StartTimerModal({ isOpen, onRequestClose, onTimerStart, onTimerStop, timer, -}) { - const inputRef = useRef(); +}: StartTimerProps) { + const inputRef = useRef(); function focusInput() { inputRef.current && inputRef.current.focus(); } @@ -23,7 +31,7 @@ function StartTimerModal({ const [minute, setMinute] = useSetting("timer.minute"); const [second, setSecond] = useSetting("timer.second"); - function handleSubmit(event) { + function handleSubmit(event: ChangeEvent) { event.preventDefault(); if (timer) { onTimerStop(); @@ -44,7 +52,7 @@ function StartTimerModal({ paddingLeft: 0, }; - function parseValue(value, max) { + function parseValue(value: string, max: number) { const num = parseInt(value); if (isNaN(num)) { return 0; diff --git a/src/tokens/index.ts b/src/tokens/index.ts index 122975e..9933d8f 100644 --- a/src/tokens/index.ts +++ b/src/tokens/index.ts @@ -32,6 +32,7 @@ import undead from "./Undead.png"; import warlock from "./Warlock.png"; import wizard from "./Wizard.png"; import unknown from "./Unknown.png"; +import { ImageFile } from "../helpers/image"; export const tokenSources = { barbarian, @@ -80,7 +81,40 @@ function getDefaultTokenSize(key: string) { } } -export const tokens = Object.keys(tokenSources).map((key) => ({ +type TokenCategory = "character" | "vehicle" | "prop" + +export type Token = { + id: string, + name: string, + defaultSize: number, + category: TokenCategory, + hideInSidebar: boolean, + width: number, + height: number, + owner: string, + type: string, + group: string | undefined, + created: number, + lastModified: number, + lastUsed: number, +} + +export interface DefaultToken extends Omit { + id?: string, + owner?: string, + created?: number, + lastModified?: number, + lastUsed?: number, + key: string, + type: "default", + group: "default", +} +export interface FileToken extends Token { + file: Uint8Array, + thumbnail: ImageFile, + type: "file", +} +export const tokens: DefaultToken[] = Object.keys(tokenSources).map((key) => ({ key, name: Case.capital(key), type: "default", @@ -89,6 +123,7 @@ export const tokens = Object.keys(tokenSources).map((key) => ({ hideInSidebar: false, width: 256, height: 256, + group: "default", })); export const unknownSource = unknown;