From 308320d169a3038c5e63bac8e70d353c4723d0ba Mon Sep 17 00:00:00 2001 From: Johannes Loher Date: Fri, 26 Jan 2024 15:36:27 +0100 Subject: [PATCH] Convert Threatbar to TypeScript and fix issues Signed-off-by: Johannes Loher --- src/client/components/board/board.test.tsx | 2 +- src/client/components/board/board.tsx | 26 +- src/client/components/deck/deck.tsx | 2 +- .../components/sidebar/sidebar.test.tsx | 2 +- src/client/components/status/status.test.tsx | 2 +- src/client/components/threatbar/threatbar.jsx | 299 ------------------ .../{threatbar.test.js => threatbar.test.tsx} | 46 ++- src/client/components/threatbar/threatbar.tsx | 294 +++++++++++++++++ src/game/gameState.ts | 2 +- src/game/utils.ts | 2 +- src/types/reactstrap-confirm.d.ts | 19 ++ 11 files changed, 377 insertions(+), 319 deletions(-) delete mode 100644 src/client/components/threatbar/threatbar.jsx rename src/client/components/threatbar/{threatbar.test.js => threatbar.test.tsx} (72%) create mode 100644 src/client/components/threatbar/threatbar.tsx create mode 100644 src/types/reactstrap-confirm.d.ts diff --git a/src/client/components/board/board.test.tsx b/src/client/components/board/board.test.tsx index 8d7d556..d804873 100644 --- a/src/client/components/board/board.test.tsx +++ b/src/client/components/board/board.test.tsx @@ -22,7 +22,7 @@ const G: GameState = { players: [['T3', 'T4', 'T5']], scores: [0, 0, 0], identifiedThreats: {}, - selectedDiagram: 0, + selectedDiagram: 'diagram1', selectedComponent: '', threat: { modal: false, diff --git a/src/client/components/board/board.tsx b/src/client/components/board/board.tsx index 5f69b42..7bf5145 100644 --- a/src/client/components/board/board.tsx +++ b/src/client/components/board/board.tsx @@ -46,12 +46,13 @@ const Board: FC = ({ ? '/api' : `${window.location.protocol}//${window.location.hostname}:${API_PORT}`; - const updateName = useCallback( - (index: number, name: string) => { - setNames([...names].splice(index, 1, name)); - }, - [setNames, names], - ); + const updateName = useCallback((index: number, name: string) => { + setNames((names) => { + const newNames = [...names]; + newNames[index] = name; + return newNames; + }); + }, []); const apiGetRequest = useCallback( async (endpoint: string) => { @@ -88,15 +89,18 @@ const Board: FC = ({ const model = modelResponse?.body as Record | undefined; setModel(model); - }, [apiGetRequest, setModel]); + }, [apiGetRequest]); // consider using react-query instead useEffect(() => { - updateNames(); if (G.modelType !== ModelType.IMAGE) { updateModel(); } - }, [updateNames, G.modelType, updateModel]); + }, [G.modelType, updateModel]); + + useEffect(() => { + updateNames(); + }, [updateNames]); const current = playerID === ctx.currentPlayer; @@ -134,7 +138,7 @@ const Board: FC = ({ matchID={matchID} /> )} - {G.modelType === ModelType.THREAT_DRAGON && ( + {G.modelType === ModelType.THREAT_DRAGON && model !== undefined && ( = ({ isInThreatStage={isInThreatStage} /> - {playerID && G.suit && ( + {playerID && ( { scores: [0, 0], lastWinner: 0, maxRounds: 10, - selectedDiagram: 0, + selectedDiagram: 'diagram0', selectedComponent: 'some-component', selectedThreat: 'some-threat', threat: { diff --git a/src/client/components/status/status.test.tsx b/src/client/components/status/status.test.tsx index c51dec8..58a5d92 100644 --- a/src/client/components/status/status.test.tsx +++ b/src/client/components/status/status.test.tsx @@ -19,7 +19,7 @@ describe('Status', () => { numCardsPlayed: 0, lastWinner: 0, maxRounds: 0, - selectedDiagram: 0, + selectedDiagram: 'diagram0', selectedComponent: '', selectedThreat: '', threat: { diff --git a/src/client/components/threatbar/threatbar.jsx b/src/client/components/threatbar/threatbar.jsx deleted file mode 100644 index 3953c3a..0000000 --- a/src/client/components/threatbar/threatbar.jsx +++ /dev/null @@ -1,299 +0,0 @@ -import { - faBolt, - faEdit, - faPlus, - faTrash, -} from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import PropTypes from 'prop-types'; -import React from 'react'; -import nl2br from 'react-nl2br'; -import { - Button, - Card, - CardBody, - CardFooter, - CardHeader, - CardText, - Col, - Collapse, - ListGroup, - ListGroupItem, - Row, -} from 'reactstrap'; -import confirm from 'reactstrap-confirm'; -import { getSuitDisplayName } from '../../../utils/cardDefinitions'; -import { getComponentName } from '../../../utils/utils'; -import ThreatModal from '../threatmodal/threatmodal'; -import './threatbar.css'; - -class Threatbar extends React.Component { - static get propTypes() { - return { - playerID: PropTypes.any, - model: PropTypes.any, - G: PropTypes.any.isRequired, - ctx: PropTypes.any.isRequired, - moves: PropTypes.any.isRequired, - active: PropTypes.bool.isRequired, - names: PropTypes.any.isRequired, - isInThreatStage: PropTypes.bool, - }; - } - - static get defaultProps() { - return { - isInThreatStage: false, - }; - } - - getSelectedComponent() { - if (this.props.G.selectedComponent === '' || this.props.model === null) { - return null; - } - - let diagram = - this.props.model.detail.diagrams[this.props.G.selectedDiagram] - .diagramJson; - for (let i = 0; i < diagram.cells.length; i++) { - let cell = diagram.cells[i]; - if (cell.id === this.props.G.selectedComponent) { - return cell; - } - } - - return null; - } - - getThreatsForSelectedComponent() { - let threats = []; - if (this.props.G.selectedComponent === '' || this.props.model === null) { - return threats; - } - - let diagram = - this.props.model.detail.diagrams[this.props.G.selectedDiagram] - .diagramJson; - for (let i = 0; i < diagram.cells.length; i++) { - let cell = diagram.cells[i]; - if (this.props.G.selectedComponent !== '') { - if (cell.id === this.props.G.selectedComponent) { - if (Array.isArray(cell.threats)) { - // fix threat ids - for (let j = 0; j < cell.threats.length; j++) { - if (!('id' in cell.threats[j])) { - cell.threats[j].id = j + ''; - } - } - return cell.threats; - } - } - } else { - /* - if (Array.isArray(cell.threats)) { - threats = threats.concat(cell.threats); - } - */ - } - } - return threats; - } - - getIdentifiedThreatsForSelectedComponent() { - let threats = []; - if (this.props.G.selectedDiagram in this.props.G.identifiedThreats) { - if ( - this.props.G.selectedComponent in - this.props.G.identifiedThreats[this.props.G.selectedDiagram] - ) { - for (let k in this.props.G.identifiedThreats[ - this.props.G.selectedDiagram - ][this.props.G.selectedComponent]) { - let t = - this.props.G.identifiedThreats[this.props.G.selectedDiagram][ - this.props.G.selectedComponent - ][k]; - threats.push(t); - } - } - } - - return threats; - } - - render() { - const threats = [...this.getThreatsForSelectedComponent()].reverse(); - const identifiedThreats = [ - ...this.getIdentifiedThreatsForSelectedComponent(), - ].reverse(); - const component = this.getSelectedComponent(); - const componentName = getComponentName(component); - - return ( - - ); - } -} - -export default Threatbar; diff --git a/src/client/components/threatbar/threatbar.test.js b/src/client/components/threatbar/threatbar.test.tsx similarity index 72% rename from src/client/components/threatbar/threatbar.test.js rename to src/client/components/threatbar/threatbar.test.tsx index ba48e0d..728c8d2 100644 --- a/src/client/components/threatbar/threatbar.test.js +++ b/src/client/components/threatbar/threatbar.test.tsx @@ -2,12 +2,16 @@ import { render, screen } from '@testing-library/react'; import { GameMode } from '../../../utils/GameMode'; import React from 'react'; import Threatbar from './threatbar'; +import type { GameState } from '../../../game/gameState'; +import type { Ctx } from 'boardgame.io'; +import { ModelType } from '../../../utils/constants'; describe('', () => { - const G = { + const G: GameState = { gameMode: GameMode.EOP, threat: { modal: false, + new: false, }, selectedDiagram: 'diagram1', selectedComponent: 'component1', @@ -16,13 +20,31 @@ describe('', () => { component1: { threat1: { title: 'Identified Threat 1', + modal: false, + new: false, }, threat2: { title: 'Identified Threat 2', + modal: false, + new: false, }, }, }, }, + dealt: [], + passed: [], + suit: undefined, + dealtBy: '', + players: [], + round: 0, + numCardsPlayed: 0, + scores: [], + lastWinner: 0, + maxRounds: 0, + selectedThreat: '', + startingCard: '', + turnDuration: 0, + modelType: ModelType.IMAGE, }; it('shows identified threats in reverse order', () => { @@ -57,7 +79,16 @@ describe('', () => { }; render( - , + , ); const threats = screen.getAllByText(/^Identified Threat \d+$/); @@ -98,7 +129,16 @@ describe('', () => { }; render( - , + , ); const threats = screen.getAllByText(/^Existing Threat \d+$/); diff --git a/src/client/components/threatbar/threatbar.tsx b/src/client/components/threatbar/threatbar.tsx new file mode 100644 index 0000000..bfb23a7 --- /dev/null +++ b/src/client/components/threatbar/threatbar.tsx @@ -0,0 +1,294 @@ +import { + faBolt, + faEdit, + faPlus, + faTrash, +} from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import React from 'react'; +import type { FC } from 'react'; +import nl2br from 'react-nl2br'; +import { + Button, + Card, + CardBody, + CardFooter, + CardHeader, + CardText, + Col, + Collapse, + ListGroup, + ListGroupItem, + Row, +} from 'reactstrap'; +import confirm from 'reactstrap-confirm'; +import { getSuitDisplayName } from '../../../utils/cardDefinitions'; +import { getComponentName } from '../../../utils/utils'; +import ThreatModal from '../threatmodal/threatmodal'; +import './threatbar.css'; +import type { GameState } from '../../../game/gameState'; +import type { BoardProps } from 'boardgame.io/react'; +import type { Threat } from '../../../game/threat'; + +type ThreatbarProps = { + model?: Record; // TODO: improve + active: boolean; + names: string[]; + isInThreatStage: boolean; +} & Pick, 'G' | 'ctx' | 'moves' | 'playerID'>; + +const Threatbar: FC = ({ + playerID, + model, + G, + ctx, + moves, + active, + names, + isInThreatStage, +}) => { + const getSelectedComponent = () => { + if (G.selectedComponent === '' || model === undefined) { + return null; + } + + const diagram = model.detail.diagrams[G.selectedDiagram].diagramJson; + for (let i = 0; i < diagram.cells.length; i++) { + const cell = diagram.cells[i]; + if (cell.id === G.selectedComponent) { + console.log("Cell:", cell); + return cell; + } + } + + return null; + }; + + const getThreatsForSelectedComponent = (): Threat[] => { + const threats: Threat[] = []; + if (G.selectedComponent === '' || model === undefined) { + return threats; + } + + const diagram = model.detail.diagrams[G.selectedDiagram].diagramJson; + for (let i = 0; i < diagram.cells.length; i++) { + const cell = diagram.cells[i]; + if (G.selectedComponent !== '') { + if (cell.id === G.selectedComponent) { + if (Array.isArray(cell.threats)) { + // fix threat ids + for (let j = 0; j < cell.threats.length; j++) { + if (!('id' in cell.threats[j])) { + cell.threats[j].id = j + ''; + } + } + return cell.threats; + } + } + } else { + /* + if (Array.isArray(cell.threats)) { + threats = threats.concat(cell.threats); + } + */ + } + } + return threats; + }; + + const getIdentifiedThreatsForSelectedComponent = () => { + const threats = []; + if (G.selectedDiagram in G.identifiedThreats) { + if (G.selectedComponent in G.identifiedThreats[G.selectedDiagram]) { + for (const k in G.identifiedThreats[G.selectedDiagram][ + G.selectedComponent + ]) { + const t = + G.identifiedThreats[G.selectedDiagram][G.selectedComponent][k]; + threats.push(t); + } + } + } + + return threats; + }; + + const threats = getThreatsForSelectedComponent().reverse(); + const identifiedThreats = + getIdentifiedThreatsForSelectedComponent().reverse(); + const component = getSelectedComponent(); + const componentName = getComponentName(component); + + return ( + + ); +}; + +export default Threatbar; diff --git a/src/game/gameState.ts b/src/game/gameState.ts index 8e73ed0..66fa153 100644 --- a/src/game/gameState.ts +++ b/src/game/gameState.ts @@ -15,7 +15,7 @@ export interface GameState { scores: number[]; lastWinner: number; maxRounds: number; - selectedDiagram: number; + selectedDiagram: string; selectedComponent: string; selectedThreat: string; threat: Threat; diff --git a/src/game/utils.ts b/src/game/utils.ts index 5cda0e3..41b70be 100644 --- a/src/game/utils.ts +++ b/src/game/utils.ts @@ -43,7 +43,7 @@ export function setupGame( scores, lastWinner: getPlayerHoldingStartingCard(handsPerPlayers, startingCard), maxRounds: getNumberOfCardsPerHand(handsPerPlayers), - selectedDiagram: 0, + selectedDiagram: 'dummy', // as image models or links don't have components, put a dummy id here to treat the entire image as selected selectedComponent: modelType === ModelType.THREAT_DRAGON ? '' : 'dummy', selectedThreat: '', diff --git a/src/types/reactstrap-confirm.d.ts b/src/types/reactstrap-confirm.d.ts new file mode 100644 index 0000000..97171a6 --- /dev/null +++ b/src/types/reactstrap-confirm.d.ts @@ -0,0 +1,19 @@ +declare module 'reactstrap-confirm' { + type Props = { + message?: React.ReactNode; + title?: React.ReactNode; + confirmTex?: React.ReactNode; + cancelText?: React.ReactNode; + confirmColor?: 'string'; + cancelColor?: string; + className?: string; + size?: string | null; + buttonsComponent?: React.ComponentType | null; + bodyComponent?: React.ComponentType | null; + modalProps?: React.ComponentProps; + }; + + const confirm: (props?: Props) => Promise; + + export default confirm; +}