Typescript

This commit is contained in:
Mitchell McCaffrey 2021-07-13 08:59:28 +10:00
parent 49b8caa2d7
commit 569ed696fc
26 changed files with 298 additions and 138 deletions

View File

@ -100,6 +100,7 @@
"@types/jest": "^26.0.23", "@types/jest": "^26.0.23",
"@types/lodash.clonedeep": "^4.5.6", "@types/lodash.clonedeep": "^4.5.6",
"@types/lodash.get": "^4.4.6", "@types/lodash.get": "^4.4.6",
"@types/lodash.set": "^4.3.6",
"@types/node": "^15.6.0", "@types/node": "^15.6.0",
"@types/react": "^17.0.6", "@types/react": "^17.0.6",
"@types/react-dom": "^17.0.5", "@types/react-dom": "^17.0.5",

View File

@ -5,12 +5,13 @@ import DiceTile from "./DiceTile";
import useResponsiveLayout from "../../hooks/useResponsiveLayout"; import useResponsiveLayout from "../../hooks/useResponsiveLayout";
import { DefaultDice } from "../../types/Dice"; import { DefaultDice } from "../../types/Dice";
import { DiceSelectEventHandler } from "../../types/Events";
type DiceTileProps = { type DiceTileProps = {
dice: DefaultDice[]; dice: DefaultDice[];
onDiceSelect: (dice: DefaultDice) => void; onDiceSelect: DiceSelectEventHandler;
selectedDice: DefaultDice; selectedDice: DefaultDice;
onDone: (dice: DefaultDice) => void; onDone: DiceSelectEventHandler;
}; };
function DiceTiles({ function DiceTiles({

View File

@ -1,26 +1,38 @@
import React, { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { Box, IconButton } from "theme-ui"; import { Box, IconButton } from "theme-ui";
import { Node } from "konva/types/Node";
import RemoveTokenIcon from "../../icons/RemoveTokenIcon"; import RemoveTokenIcon from "../../icons/RemoveTokenIcon";
function DragOverlay({ dragging, node, onRemove }) { type DragOverlayProps = {
dragging: boolean;
node: Node;
onRemove: () => void;
};
function DragOverlay({ dragging, node, onRemove }: DragOverlayProps) {
const [isRemoveHovered, setIsRemoveHovered] = useState(false); const [isRemoveHovered, setIsRemoveHovered] = useState(false);
const removeTokenRef = useRef(); const removeTokenRef = useRef<HTMLDivElement>(null);
// Detect token hover on remove icon manually to support touch devices // Detect token hover on remove icon manually to support touch devices
useEffect(() => { useEffect(() => {
const map = document.querySelector(".map");
const mapRect = map.getBoundingClientRect();
function detectRemoveHover() { function detectRemoveHover() {
if (!node || !dragging || !removeTokenRef.current) { if (!node || !dragging || !removeTokenRef.current) {
return; return;
} }
const map = document.querySelector(".map");
if (!map) {
return;
}
const mapRect = map.getBoundingClientRect();
const stage = node.getStage(); const stage = node.getStage();
if (!stage) { if (!stage) {
return; return;
} }
const pointerPosition = stage.getPointerPosition(); const pointerPosition = stage.getPointerPosition();
if (!pointerPosition) {
return;
}
const screenSpacePointerPosition = { const screenSpacePointerPosition = {
x: pointerPosition.x + mapRect.left, x: pointerPosition.x + mapRect.left,
y: pointerPosition.y + mapRect.top, y: pointerPosition.y + mapRect.top,
@ -41,7 +53,7 @@ function DragOverlay({ dragging, node, onRemove }) {
} }
} }
let handler; let handler: NodeJS.Timeout;
if (node && dragging) { if (node && dragging) {
handler = setInterval(detectRemoveHover, 100); handler = setInterval(detectRemoveHover, 100);
} }

View File

@ -21,6 +21,22 @@ import FullScreenExitIcon from "../../icons/FullScreenExitIcon";
import NoteToolIcon from "../../icons/NoteToolIcon"; import NoteToolIcon from "../../icons/NoteToolIcon";
import useSetting from "../../hooks/useSetting"; import useSetting from "../../hooks/useSetting";
import { Map } from "../../types/Map";
import { MapState } from "../../types/MapState";
type MapControlsProps = {
onMapChange: () => void;
onMapReset: () => void;
currentMap?: Map;
currentMapState?: MapState;
selectedToolId: string;
onSelectedToolChange: () => void;
toolSettings: any;
onToolSettingChange: () => void;
onToolAction: () => void;
disabledControls: string[];
disabledSettings: string[];
};
function MapContols({ function MapContols({
onMapChange, onMapChange,
@ -34,7 +50,7 @@ function MapContols({
onToolAction, onToolAction,
disabledControls, disabledControls,
disabledSettings, disabledSettings,
}) { }: MapControlsProps) {
const [isExpanded, setIsExpanded] = useState(true); const [isExpanded, setIsExpanded] = useState(true);
const [fullScreen, setFullScreen] = useSetting("map.fullScreen"); const [fullScreen, setFullScreen] = useSetting("map.fullScreen");

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { Flex, Close, IconButton } from "theme-ui"; import { Flex, Close, IconButton } from "theme-ui";
import { groupsFromIds, itemsFromGroups } from "../../helpers/group"; import { groupsFromIds, itemsFromGroups } from "../../helpers/group";
@ -13,8 +13,27 @@ import { useMapData } from "../../contexts/MapDataContext";
import { useKeyboard } from "../../contexts/KeyboardContext"; import { useKeyboard } from "../../contexts/KeyboardContext";
import shortcuts from "../../shortcuts"; import shortcuts from "../../shortcuts";
import { Map } from "../../types/Map";
import {
MapChangeEventHandler,
MapResetEventHandler,
} from "../../types/Events";
function MapEditBar({ currentMap, disabled, onMapChange, onMapReset, onLoad }) { type MapEditBarProps = {
currentMap?: Map;
disabled: boolean;
onMapChange: MapChangeEventHandler;
onMapReset: MapResetEventHandler;
onLoad: (loading: boolean) => void;
};
function MapEditBar({
currentMap,
disabled,
onMapChange,
onMapReset,
onLoad,
}: MapEditBarProps) {
const [hasMapState, setHasMapState] = useState(false); const [hasMapState, setHasMapState] = useState(false);
const { maps, mapStates, removeMaps, resetMap } = useMapData(); const { maps, mapStates, removeMaps, resetMap } = useMapData();
@ -56,11 +75,11 @@ function MapEditBar({ currentMap, disabled, onMapChange, onMapReset, onLoad }) {
setIsMapsRemoveModalOpen(false); setIsMapsRemoveModalOpen(false);
const selectedMaps = getSelectedMaps(); const selectedMaps = getSelectedMaps();
const selectedMapIds = selectedMaps.map((map) => map.id); const selectedMapIds = selectedMaps.map((map) => map.id);
onGroupSelect(); onGroupSelect(undefined);
await removeMaps(selectedMapIds); await removeMaps(selectedMapIds);
// Removed the map from the map screen if needed // Removed the map from the map screen if needed
if (currentMap && selectedMapIds.includes(currentMap.id)) { if (currentMap && selectedMapIds.includes(currentMap.id)) {
onMapChange(null, null); onMapChange(undefined, undefined);
} }
onLoad(false); onLoad(false);
} }
@ -84,7 +103,7 @@ function MapEditBar({ currentMap, disabled, onMapChange, onMapReset, onLoad }) {
/** /**
* Shortcuts * Shortcuts
*/ */
function handleKeyDown(event) { function handleKeyDown(event: KeyboardEvent) {
if (disabled) { if (disabled) {
return; return;
} }
@ -117,7 +136,7 @@ function MapEditBar({ currentMap, disabled, onMapChange, onMapReset, onLoad }) {
<Close <Close
title="Clear Selection" title="Clear Selection"
aria-label="Clear Selection" aria-label="Clear Selection"
onClick={() => onGroupSelect()} onClick={() => onGroupSelect(undefined)}
/> />
<Flex> <Flex>
<IconButton <IconButton

View File

@ -11,37 +11,58 @@ import { useDatabase } from "./DatabaseContext";
import { Map } from "../types/Map"; import { Map } from "../types/Map";
import { MapState } from "../types/MapState"; import { MapState } from "../types/MapState";
import { Note } from "../types/Note";
import { removeGroupsItems } from "../helpers/group"; import { removeGroupsItems } from "../helpers/group";
// TODO: fix differences in types between default maps and imported maps import { Group } from "../types/Group";
export type AddMapEventHandler = (map: Map) => Promise<void>;
export type RemoveMapsEventHandler = (ids: string[]) => Promise<void>;
export type ResetMapEventHandler = (id: string) => Promise<MapState>;
export type UpdateMapEventHanlder = (
id: string,
update: Partial<Map>
) => Promise<void>;
export type UpdateMapStateEventHandler = (
id: string,
update: Partial<MapState>
) => Promise<void>;
export type GetMapStateEventHandler = (
id: string
) => Promise<MapState | undefined>;
export type GetMapEventHandler = (id: string) => Promise<Map | undefined>;
export type UpdateMapGroupsEventHandler = (groups: Group[]) => Promise<void>;
type MapDataContext = { type MapDataContext = {
maps: Array<Map>; maps: Array<Map>;
mapStates: MapState[]; mapStates: MapState[];
addMap: (map: Map) => void; /** Adds a map to the database, also adds an assosiated state and group for that map */
removeMaps: (ids: string[]) => void; addMap: AddMapEventHandler;
resetMap: (id: string) => void; removeMaps: RemoveMapsEventHandler;
updateMap: (id: string, update: Partial<Map>) => void; resetMap: ResetMapEventHandler;
updateMapState: (id: string, update: Partial<MapState>) => void; updateMap: UpdateMapEventHanlder;
getMapState: (id: string) => Promise<MapState>; updateMapState: UpdateMapStateEventHandler;
getMap: (id: string) => Promise<Map | undefined>; getMapState: GetMapStateEventHandler;
getMap: GetMapEventHandler;
mapsLoading: boolean; mapsLoading: boolean;
updateMapGroups: (groups: any) => void; updateMapGroups: UpdateMapGroupsEventHandler;
mapsById: Record<string, Map>; mapsById: Record<string, Map>;
mapGroups: any[]; mapGroups: Group[];
}; };
const MapDataContext = const MapDataContext =
React.createContext<MapDataContext | undefined>(undefined); React.createContext<MapDataContext | undefined>(undefined);
const defaultMapState = { const defaultMapState: Pick<
MapState,
"tokens" | "drawShapes" | "fogShapes" | "editFlags" | "notes"
> = {
tokens: {}, tokens: {},
drawShapes: {}, drawShapes: {},
fogShapes: {}, fogShapes: {},
// Flags to determine what other people can edit // Flags to determine what other people can edit
editFlags: ["drawing", "tokens", "notes", "fog"], editFlags: ["drawing", "tokens", "notes", "fog"],
notes: {} as Note[], notes: {},
}; };
export function MapDataProvider({ children }: { children: React.ReactNode }) { export function MapDataProvider({ children }: { children: React.ReactNode }) {
@ -68,7 +89,7 @@ export function MapDataProvider({ children }: { children: React.ReactNode }) {
[database] [database]
); );
const [mapGroups, setMapGroups] = useState([]); const [mapGroups, setMapGroups] = useState<Group[]>([]);
useEffect(() => { useEffect(() => {
async function updateMapGroups() { async function updateMapGroups() {
const group = await database?.table("groups").get("maps"); const group = await database?.table("groups").get("maps");
@ -79,27 +100,23 @@ export function MapDataProvider({ children }: { children: React.ReactNode }) {
} }
}, [mapGroupQuery, database]); }, [mapGroupQuery, database]);
const getMap = useCallback( const getMap = useCallback<GetMapEventHandler>(
async (mapId: string) => { async (mapId) => {
let map = (await database?.table("maps").get(mapId)) as Map; let map = await database?.table("maps").get(mapId);
return map; return map;
}, },
[database] [database]
); );
const getMapState = useCallback( const getMapState = useCallback<GetMapStateEventHandler>(
async (mapId) => { async (mapId) => {
let mapState = (await database?.table("states").get(mapId)) as MapState; let mapState = await database?.table("states").get(mapId);
return mapState; return mapState;
}, },
[database] [database]
); );
/** const addMap = useCallback<AddMapEventHandler>(
* Adds a map to the database, also adds an assosiated state and group for that map
* @param {Object} map map to add
*/
const addMap = useCallback(
async (map) => { async (map) => {
if (database) { if (database) {
// Just update map database as react state will be updated with an Observable // Just update map database as react state will be updated with an Observable
@ -115,7 +132,7 @@ export function MapDataProvider({ children }: { children: React.ReactNode }) {
[database] [database]
); );
const removeMaps = useCallback( const removeMaps = useCallback<RemoveMapsEventHandler>(
async (ids) => { async (ids) => {
if (database) { if (database) {
const maps = await database.table("maps").bulkGet(ids); const maps = await database.table("maps").bulkGet(ids);
@ -143,30 +160,30 @@ export function MapDataProvider({ children }: { children: React.ReactNode }) {
[database] [database]
); );
const resetMap = useCallback( const resetMap = useCallback<ResetMapEventHandler>(
async (id) => { async (id) => {
const state = { ...defaultMapState, mapId: id }; const state: MapState = { ...defaultMapState, mapId: id };
await database?.table("states").put(state); await database?.table("states").put(state);
return state; return state;
}, },
[database] [database]
); );
const updateMap = useCallback( const updateMap = useCallback<UpdateMapEventHanlder>(
async (id, update) => { async (id, update) => {
await database?.table("maps").update(id, update); await database?.table("maps").update(id, update);
}, },
[database] [database]
); );
const updateMapState = useCallback( const updateMapState = useCallback<UpdateMapStateEventHandler>(
async (id, update) => { async (id, update) => {
await database?.table("states").update(id, update); await database?.table("states").update(id, update);
}, },
[database] [database]
); );
const updateMapGroups = useCallback( const updateMapGroups = useCallback<UpdateMapGroupsEventHandler>(
async (groups) => { async (groups) => {
// Update group state immediately to avoid animation delay // Update group state immediately to avoid animation delay
setMapGroups(groups); setMapGroups(groups);

View File

@ -2,21 +2,26 @@ import React, { useState, useEffect, useContext } from "react";
import { getSettings } from "../settings"; import { getSettings } from "../settings";
const SettingsContext = React.createContext({ import { Settings } from "../types/Settings";
settings: {},
setSettings: () => {}, type SettingsContext = {
}); settings: Settings;
setSettings: React.Dispatch<React.SetStateAction<Settings>>;
};
const SettingsContext =
React.createContext<SettingsContext | undefined>(undefined);
const settingsProvider = getSettings(); const settingsProvider = getSettings();
export function SettingsProvider({ children }: { children: any }) { export function SettingsProvider({ children }: { children: any }) {
const [settings, setSettings] = useState(settingsProvider.getAll()); const [settings, setSettings] = useState<Settings>(settingsProvider.getAll());
useEffect(() => { useEffect(() => {
settingsProvider.setAll(settings); settingsProvider.setAll(settings);
}, [settings]); }, [settings]);
const value: { settings: any, setSettings: any} = { const value = {
settings, settings,
setSettings, setSettings,
}; };

View File

@ -9,20 +9,37 @@ import { useLiveQuery } from "dexie-react-hooks";
import { useDatabase } from "./DatabaseContext"; import { useDatabase } from "./DatabaseContext";
import { Token } from "../tokens";
import { removeGroupsItems } from "../helpers/group"; import { removeGroupsItems } from "../helpers/group";
import { Token } from "../types/Token";
import { Group } from "../types/Group";
export type AddTokenEventHandler = (token: Token) => Promise<void>;
export type RemoveTokensEventHandler = (ids: string[]) => Promise<void>;
export type UpdateTokenEventHandler = (
id: string,
update: Partial<Token>
) => Promise<void>;
export type GetTokenEventHandler = (
tokenId: string
) => Promise<Token | undefined>;
export type UpdateTokenGroupsEventHandler = (groups: any[]) => Promise<void>;
export type UpdateTokensHiddenEventHandler = (
ids: string[],
hideInSidebar: boolean
) => Promise<void>;
type TokenDataContext = { type TokenDataContext = {
tokens: Token[]; tokens: Token[];
addToken: (token: Token) => Promise<void>; addToken: AddTokenEventHandler;
tokenGroups: any[]; tokenGroups: Group[];
removeTokens: (ids: string[]) => Promise<void>; removeTokens: RemoveTokensEventHandler;
updateToken: (id: string, update: Partial<Token>) => Promise<void>; updateToken: UpdateTokenEventHandler;
getToken: (tokenId: string) => Promise<Token | undefined>; getToken: GetTokenEventHandler;
tokensById: Record<string, Token>; tokensById: Record<string, Token>;
tokensLoading: boolean; tokensLoading: boolean;
updateTokenGroups: (groups: any[]) => void; updateTokenGroups: UpdateTokenGroupsEventHandler;
updateTokensHidden: (ids: string[], hideInSidebar: boolean) => void; updateTokensHidden: UpdateTokensHiddenEventHandler;
}; };
const TokenDataContext = const TokenDataContext =
@ -44,7 +61,7 @@ export function TokenDataProvider({ children }: { children: React.ReactNode }) {
[database] [database]
); );
const [tokenGroups, setTokenGroups] = useState([]); const [tokenGroups, setTokenGroups] = useState<Group[]>([]);
useEffect(() => { useEffect(() => {
async function updateTokenGroups() { async function updateTokenGroups() {
const group = await database?.table("groups").get("tokens"); const group = await database?.table("groups").get("tokens");
@ -55,7 +72,7 @@ export function TokenDataProvider({ children }: { children: React.ReactNode }) {
} }
}, [tokenGroupQuery, database]); }, [tokenGroupQuery, database]);
const getToken = useCallback( const getToken = useCallback<GetTokenEventHandler>(
async (tokenId) => { async (tokenId) => {
let token = await database?.table("tokens").get(tokenId); let token = await database?.table("tokens").get(tokenId);
return token; return token;
@ -64,7 +81,7 @@ export function TokenDataProvider({ children }: { children: React.ReactNode }) {
); );
// Add token and add it to the token group // Add token and add it to the token group
const addToken = useCallback( const addToken = useCallback<AddTokenEventHandler>(
async (token) => { async (token) => {
if (database) { if (database) {
await database.table("tokens").add(token); await database.table("tokens").add(token);
@ -77,7 +94,7 @@ export function TokenDataProvider({ children }: { children: React.ReactNode }) {
[database] [database]
); );
const removeTokens = useCallback( const removeTokens = useCallback<RemoveTokensEventHandler>(
async (ids) => { async (ids) => {
if (database) { if (database) {
const tokens = await database.table("tokens").bulkGet(ids); const tokens = await database.table("tokens").bulkGet(ids);
@ -100,14 +117,14 @@ export function TokenDataProvider({ children }: { children: React.ReactNode }) {
[database] [database]
); );
const updateToken = useCallback( const updateToken = useCallback<UpdateTokenEventHandler>(
async (id, update) => { async (id, update) => {
await database?.table("tokens").update(id, update); await database?.table("tokens").update(id, update);
}, },
[database] [database]
); );
const updateTokensHidden = useCallback( const updateTokensHidden = useCallback<UpdateTokensHiddenEventHandler>(
async (ids: string[], hideInSidebar: boolean) => { async (ids: string[], hideInSidebar: boolean) => {
await Promise.all( await Promise.all(
ids.map((id) => database?.table("tokens").update(id, { hideInSidebar })) ids.map((id) => database?.table("tokens").update(id, { hideInSidebar }))
@ -116,8 +133,8 @@ export function TokenDataProvider({ children }: { children: React.ReactNode }) {
[database] [database]
); );
const updateTokenGroups = useCallback( const updateTokenGroups = useCallback<UpdateTokenGroupsEventHandler>(
async (groups) => { async (groups: Group[]) => {
// Update group state immediately to avoid animation delay // Update group state immediately to avoid animation delay
setTokenGroups(groups); setTokenGroups(groups);
await database?.table("groups").update("tokens", { items: groups }); await database?.table("groups").update("tokens", { items: groups });

View File

@ -5,14 +5,14 @@ import { useSettings } from "../contexts/SettingsContext";
/** /**
* Helper to get and set nested settings that are saved in local storage * Helper to get and set nested settings that are saved in local storage
* @param {String} path The path to the setting within the Settings object provided by the SettingsContext * @param {string} path The path to the setting within the Settings object provided by the SettingsContext
*/ */
function useSetting(path) { function useSetting<Type>(path: string): [Type, (value: Type) => void] {
const { settings, setSettings } = useSettings(); const { settings, setSettings } = useSettings();
const setting = get(settings, path); const setting = get(settings, path) as Type;
const setSetting = (value) => const setSetting = (value: Type) =>
setSettings((prev) => { setSettings((prev) => {
const updated = set({ ...prev }, path, value); const updated = set({ ...prev }, path, value);
return updated; return updated;

View File

@ -9,13 +9,16 @@ import TokenPreview from "../components/token/TokenPreview";
import { isEmpty } from "../helpers/shared"; import { isEmpty } from "../helpers/shared";
import useResponsiveLayout from "../hooks/useResponsiveLayout"; import useResponsiveLayout from "../hooks/useResponsiveLayout";
import { Token } from "../types/Token"; import { Token } from "../types/Token";
import { UpdateTokenEventHandler } from "../contexts/TokenDataContext";
type EditModalProps = { type EditModalProps = {
isOpen: boolean; isOpen: boolean;
onDone: () => void; onDone: () => void;
token: Token; token: Token;
onUpdateToken: (id: string, update: Partial<Token>) => void; onUpdateToken: UpdateTokenEventHandler;
}; };
function EditTokenModal({ function EditTokenModal({

View File

@ -2,9 +2,11 @@ import { Box, Label, Flex, Button, Text } from "theme-ui";
import Modal from "../components/Modal"; import Modal from "../components/Modal";
import { RequestCloseEventHandler } from "../types/Events";
type GameExpiredModalProps = { type GameExpiredModalProps = {
isOpen: boolean; isOpen: boolean;
onRequestClose: () => void; onRequestClose: RequestCloseEventHandler;
}; };
function GameExpiredModal({ isOpen, onRequestClose }: GameExpiredModalProps) { function GameExpiredModal({ isOpen, onRequestClose }: GameExpiredModalProps) {

View File

@ -5,11 +5,13 @@ import Modal from "../components/Modal";
import Markdown from "../components/Markdown"; import Markdown from "../components/Markdown";
import Link from "../components/Link"; import Link from "../components/Link";
import { RequestCloseEventHandler } from "../types/Events";
const gettingStarted = raw("../docs/howTo/gettingStarted.md"); const gettingStarted = raw("../docs/howTo/gettingStarted.md");
type GettingStartedModalProps = { type GettingStartedModalProps = {
isOpen: boolean; isOpen: boolean;
onRequestClose: () => void; onRequestClose: RequestCloseEventHandler;
}; };
function GettingStartedModal({ function GettingStartedModal({

View File

@ -3,11 +3,15 @@ import { Box, Input, Button, Label, Flex } from "theme-ui";
import Modal from "../components/Modal"; import Modal from "../components/Modal";
import { RequestCloseEventHandler } from "../types/Events";
export type GroupNameEventHandler = (name: string) => void;
type GroupNameModalProps = { type GroupNameModalProps = {
isOpen: boolean; isOpen: boolean;
onRequestClose: () => void; onRequestClose: RequestCloseEventHandler;
name: string; name: string;
onSubmit: (name: string) => void; onSubmit: GroupNameEventHandler;
}; };
function GroupNameModal({ function GroupNameModal({

View File

@ -21,6 +21,7 @@ import { Map } from "../types/Map";
import { MapState } from "../types/MapState"; import { MapState } from "../types/MapState";
import { Token } from "../types/Token"; import { Token } from "../types/Token";
import { Group } from "../types/Group"; import { Group } from "../types/Group";
import { RequestCloseEventHandler } from "../types/Events";
const importDBName = "OwlbearRodeoImportDB"; const importDBName = "OwlbearRodeoImportDB";
@ -36,7 +37,7 @@ function ImportExportModal({
onRequestClose, onRequestClose,
}: { }: {
isOpen: boolean; isOpen: boolean;
onRequestClose: () => void; onRequestClose: RequestCloseEventHandler;
}) { }) {
const { worker } = useDatabase(); const { worker } = useDatabase();
const userId = useUserId(); const userId = useUserId();

View File

@ -4,9 +4,11 @@ import { useHistory } from "react-router-dom";
import Modal from "../components/Modal"; import Modal from "../components/Modal";
import { RequestCloseEventHandler } from "../types/Events";
type JoinModalProps = { type JoinModalProps = {
isOpen: boolean; isOpen: boolean;
onRequestClose: () => void; onRequestClose: RequestCloseEventHandler;
}; };
function JoinModal({ isOpen, onRequestClose }: JoinModalProps) { function JoinModal({ isOpen, onRequestClose }: JoinModalProps) {

View File

@ -12,21 +12,7 @@ import { Map } from "../types/Map";
import { Group, GroupContainer } from "../types/Group"; import { Group, GroupContainer } from "../types/Group";
import { MapState } from "../types/MapState"; import { MapState } from "../types/MapState";
import { Token } from "../types/Token"; import { Token } from "../types/Token";
import { RequestCloseEventHandler } from "../types/Events";
type SelectDataProps = {
isOpen: boolean;
onRequestClose: () => void;
onConfirm: (
checkedMaps: SelectData[],
checkedTokens: SelectData[],
checkedMapGroups: Group[],
checkedTokenGroups: Group[]
) => void;
confirmText: string;
label: string;
databaseName: string;
filter: (table: string, data: Map | MapState | Token, id: string) => boolean;
};
export type SelectData = { export type SelectData = {
name: string; name: string;
@ -35,6 +21,23 @@ export type SelectData = {
checked: boolean; checked: boolean;
}; };
export type ConfirmDataEventHandler = (
checkedMaps: SelectData[],
checkedTokens: SelectData[],
checkedMapGroups: Group[],
checkedTokenGroups: Group[]
) => void;
type SelectDataProps = {
isOpen: boolean;
onRequestClose: RequestCloseEventHandler;
onConfirm: ConfirmDataEventHandler;
confirmText: string;
label: string;
databaseName: string;
filter: (table: string, data: Map | MapState | Token, id: string) => boolean;
};
type DataRecord = Record<string, SelectData>; type DataRecord = Record<string, SelectData>;
function SelectDataModal({ function SelectDataModal({

View File

@ -8,12 +8,16 @@ import { dice } from "../dice";
import useResponsiveLayout from "../hooks/useResponsiveLayout"; import useResponsiveLayout from "../hooks/useResponsiveLayout";
import {
DiceSelectEventHandler,
RequestCloseEventHandler,
} from "../types/Events";
import { DefaultDice } from "../types/Dice"; import { DefaultDice } from "../types/Dice";
type SelectDiceProps = { type SelectDiceProps = {
isOpen: boolean; isOpen: boolean;
onRequestClose: () => void; onRequestClose: RequestCloseEventHandler;
onDone: (dice: DefaultDice) => void; onDone: DiceSelectEventHandler;
defaultDice: DefaultDice; defaultDice: DefaultDice;
}; };

View File

@ -31,13 +31,17 @@ import { GroupProvider } from "../contexts/GroupContext";
import { TileDragProvider } from "../contexts/TileDragContext"; import { TileDragProvider } from "../contexts/TileDragContext";
import { Map } from "../types/Map"; import { Map } from "../types/Map";
import { MapState } from "../types/MapState"; import {
MapChangeEventHandler,
MapResetEventHandler,
RequestCloseEventHandler,
} from "../types/Events";
type SelectMapProps = { type SelectMapProps = {
isOpen: boolean; isOpen: boolean;
onDone: () => void; onDone: RequestCloseEventHandler;
onMapChange: (map?: Map, mapState?: MapState) => void; onMapChange: MapChangeEventHandler;
onMapReset: (newState: MapState) => void; onMapReset: MapResetEventHandler;
currentMap?: Map; currentMap?: Map;
}; };
@ -294,7 +298,7 @@ function SelectMapModal({
</TileDragProvider> </TileDragProvider>
<MapEditBar <MapEditBar
currentMap={currentMap} currentMap={currentMap}
disabled={isLoading || editingMapId} disabled={isLoading || !!editingMapId}
onMapChange={onMapChange} onMapChange={onMapChange}
onMapReset={onMapReset} onMapReset={onMapReset}
onLoad={setIsLoading} onLoad={setIsLoading}

View File

@ -36,11 +36,15 @@ import { TileDragProvider } from "../contexts/TileDragContext";
import { useMapStage } from "../contexts/MapStageContext"; import { useMapStage } from "../contexts/MapStageContext";
import { TokenState } from "../types/TokenState"; import { TokenState } from "../types/TokenState";
import {
MapTokensStateCreateHandler,
RequestCloseEventHandler,
} from "../types/Events";
type SelectTokensModalProps = { type SelectTokensModalProps = {
isOpen: boolean; isOpen: boolean;
onRequestClose: () => void; onRequestClose: RequestCloseEventHandler;
onMapTokensStateCreate: (states: TokenState[]) => void; onMapTokensStateCreate: MapTokensStateCreateHandler;
}; };
function SelectTokensModal({ function SelectTokensModal({

View File

@ -21,24 +21,27 @@ import useSetting from "../hooks/useSetting";
import ConfirmModal from "./ConfirmModal"; import ConfirmModal from "./ConfirmModal";
import ImportExportModal from "./ImportExportModal"; import ImportExportModal from "./ImportExportModal";
import { MapState } from "../components/map/Map";
import { MapState } from "../types/MapState";
import { RequestCloseEventHandler } from "../types/Events";
function SettingsModal({ function SettingsModal({
isOpen, isOpen,
onRequestClose, onRequestClose,
}: { }: {
isOpen: boolean; isOpen: boolean;
onRequestClose: () => void; onRequestClose: RequestCloseEventHandler;
}) { }) {
const { database, databaseStatus } = useDatabase(); const { database, databaseStatus } = useDatabase();
const userId = useUserId(); const userId = useUserId();
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [labelSize, setLabelSize] = useSetting("map.labelSize"); const [labelSize, setLabelSize] = useSetting<number>("map.labelSize");
const [gridSnappingSensitivity, setGridSnappingSensitivity] = useSetting( const [gridSnappingSensitivity, setGridSnappingSensitivity] =
"map.gridSnappingSensitivity" useSetting<number>("map.gridSnappingSensitivity");
); const [showFogGuides, setShowFogGuides] =
const [showFogGuides, setShowFogGuides] = useSetting("fog.showGuides"); useSetting<boolean>("fog.showGuides");
const [fogEditOpacity, setFogEditOpacity] = useSetting("fog.editOpacity"); const [fogEditOpacity, setFogEditOpacity] =
useSetting<number>("fog.editOpacity");
const [storageEstimate, setStorageEstimate] = useState<StorageEstimate>(); const [storageEstimate, setStorageEstimate] = useState<StorageEstimate>();
const [isImportExportModalOpen, setIsImportExportModalOpen] = useState(false); const [isImportExportModalOpen, setIsImportExportModalOpen] = useState(false);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);

View File

@ -9,7 +9,15 @@ import useSetting from "../hooks/useSetting";
import Modal from "../components/Modal"; import Modal from "../components/Modal";
function StartModal({ isOpen, onRequestClose }: { isOpen: boolean, onRequestClose: () => void}) { import { RequestCloseEventHandler } from "../types/Events";
function StartModal({
isOpen,
onRequestClose,
}: {
isOpen: boolean;
onRequestClose: RequestCloseEventHandler;
}) {
let history = useHistory(); let history = useHistory();
const { password, setPassword } = useAuth(); const { password, setPassword } = useAuth();
@ -17,7 +25,7 @@ function StartModal({ isOpen, onRequestClose }: { isOpen: boolean, onRequestClos
setPassword(event.target.value); setPassword(event.target.value);
} }
const [usePassword, setUsePassword] = useSetting("game.usePassword"); const [usePassword, setUsePassword] = useSetting<boolean>("game.usePassword");
function handleUsePasswordChange(event: ChangeEvent<HTMLInputElement>) { function handleUsePasswordChange(event: ChangeEvent<HTMLInputElement>) {
setUsePassword(event.target.checked); setUsePassword(event.target.checked);
} }

View File

@ -2,17 +2,22 @@ import { Box, Text, Button, Label, Flex } from "theme-ui";
import Modal from "../components/Modal"; import Modal from "../components/Modal";
import { RequestCloseEventHandler } from "../types/Events";
export type StreamStartEventHandler = () => void;
export type StreamEndEventHandler = (stream: MediaStream) => void;
type StartStreamProps = { type StartStreamProps = {
isOpen: boolean, isOpen: boolean;
onRequestClose: () => void, onRequestClose: RequestCloseEventHandler;
isSupported: boolean, isSupported: boolean;
unavailableMessage: JSX.Element, unavailableMessage: JSX.Element;
stream: MediaStream, stream: MediaStream;
noAudioTrack: boolean, noAudioTrack: boolean;
noAudioMessage: JSX.Element, noAudioMessage: JSX.Element;
onStreamStart: any, onStreamStart: StreamStartEventHandler;
onStreamEnd: any, onStreamEnd: StreamEndEventHandler;
} };
function StartStreamModal({ function StartStreamModal({
isOpen, isOpen,

View File

@ -7,13 +7,19 @@ import { getHMSDuration, getDurationHMS } from "../helpers/timer";
import useSetting from "../hooks/useSetting"; import useSetting from "../hooks/useSetting";
import { RequestCloseEventHandler } from "../types/Events";
import { Timer } from "../types/Timer";
export type TimerStartEventHandler = (event: Timer) => void;
export type TimerStopEventHandler = () => void;
type StartTimerProps = { type StartTimerProps = {
isOpen: boolean, isOpen: boolean;
onRequestClose: () => void, onRequestClose: RequestCloseEventHandler;
onTimerStart: any, onTimerStart: TimerStartEventHandler;
onTimerStop: any, onTimerStop: TimerStopEventHandler;
timer: any, timer?: Timer;
} };
function StartTimerModal({ function StartTimerModal({
isOpen, isOpen,
@ -27,9 +33,9 @@ function StartTimerModal({
inputRef.current && inputRef.current.focus(); inputRef.current && inputRef.current.focus();
} }
const [hour, setHour] = useSetting("timer.hour"); const [hour, setHour] = useSetting<number>("timer.hour");
const [minute, setMinute] = useSetting("timer.minute"); const [minute, setMinute] = useSetting<number>("timer.minute");
const [second, setSecond] = useSetting("timer.second"); const [second, setSecond] = useSetting<number>("timer.second");
function handleSubmit(event: ChangeEvent<HTMLInputElement>) { function handleSubmit(event: ChangeEvent<HTMLInputElement>) {
event.preventDefault(); event.preventDefault();
@ -85,10 +91,10 @@ function StartTimerModal({
</Text> </Text>
<Input <Input
sx={inputStyle} sx={inputStyle}
value={`${timer ? timerHMS.hour : hour}`} value={`${timerHMS ? timerHMS.hour : hour}`}
onChange={(e) => setHour(parseValue(e.target.value, 24))} onChange={(e) => setHour(parseValue(e.target.value, 24))}
type="number" type="number"
disabled={timer} disabled={!!timer}
min={0} min={0}
max={24} max={24}
/> />
@ -97,11 +103,11 @@ function StartTimerModal({
</Text> </Text>
<Input <Input
sx={inputStyle} sx={inputStyle}
value={`${timer ? timerHMS.minute : minute}`} value={`${timerHMS ? timerHMS.minute : minute}`}
onChange={(e) => setMinute(parseValue(e.target.value, 59))} onChange={(e) => setMinute(parseValue(e.target.value, 59))}
type="number" type="number"
ref={inputRef} ref={inputRef}
disabled={timer} disabled={!!timer}
min={0} min={0}
max={59} max={59}
/> />
@ -110,10 +116,10 @@ function StartTimerModal({
</Text> </Text>
<Input <Input
sx={inputStyle} sx={inputStyle}
value={`${timer ? timerHMS.second : second}`} value={`${timerHMS ? timerHMS.second : second}`}
onChange={(e) => setSecond(parseValue(e.target.value, 59))} onChange={(e) => setSecond(parseValue(e.target.value, 59))}
type="number" type="number"
disabled={timer} disabled={!!timer}
min={0} min={0}
max={59} max={59}
/> />

14
src/types/Events.ts Normal file
View File

@ -0,0 +1,14 @@
import { DefaultDice } from "./Dice";
import { Map } from "./Map";
import { MapState } from "./MapState";
import { TokenState } from "./TokenState";
export type MapChangeEventHandler = (map?: Map, mapState?: MapState) => void;
export type MapResetEventHandler = (newState: MapState) => void;
export type DiceSelectEventHandler = (dice: DefaultDice) => void;
export type RequestCloseEventHandler = () => void;
export type MapTokensStateCreateHandler = (states: TokenState[]) => void;

View File

@ -2998,6 +2998,13 @@
dependencies: dependencies:
"@types/lodash" "*" "@types/lodash" "*"
"@types/lodash.set@^4.3.6":
version "4.3.6"
resolved "https://registry.yarnpkg.com/@types/lodash.set/-/lodash.set-4.3.6.tgz#33e635c2323f855359225df6a5c8c6f1f1908264"
integrity sha512-ZeGDDlnRYTvS31Laij0RsSaguIUSBTYIlJFKL3vm3T2OAZAQj2YpSvVWJc0WiG4jqg9fGX6PAPGvDqBcHfSgFg==
dependencies:
"@types/lodash" "*"
"@types/lodash@*": "@types/lodash@*":
version "4.14.170" version "4.14.170"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.170.tgz#0d67711d4bf7f4ca5147e9091b847479b87925d6" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.170.tgz#0d67711d4bf7f4ca5147e9091b847479b87925d6"