From 549b56d5f62d78d5f84693bff661da3d3c354df4 Mon Sep 17 00:00:00 2001 From: Jessica Mulein Date: Thu, 7 Nov 2024 10:18:41 +0000 Subject: [PATCH] - Version 0.10.3 - Improve FontAwesome icons for goruped actions - Add icon to the log for grouped actions - Version 0.10.2 - Add FontAwesome - Add custom icon support to recipes/common actions - Add icons to Common Actions/recipes tab --- .devcontainer/devcontainer.json | 2 +- .env.example | 3 +- .github/workflows/deploy-to-s3.yml | 10 + .gitignore | 1 + README.md | 14 + fontawesome-npmrc.sh | 5 + package.json | 5 + src/assets/images/favicon.ico | 0 src/components/GameInterface.tsx | 4 +- src/components/GameLogEntry.tsx | 11 + src/components/RecipeCard.tsx | 28 +- src/components/RecipeList.tsx | 59 + src/components/Recipes.tsx | 504 +++++- ...ominion-lib-log-applyGroupedAction.spec.ts | 32 + ...nion-lib-undo-reconstructGameState.spec.ts | 1 + src/game/constants.ts | 2 +- src/game/dominion-lib-log.ts | 13 +- src/game/interfaces/grouped-action.ts | 2 + src/game/interfaces/log-entry-raw.ts | 3 + src/game/interfaces/log-entry.ts | 5 +- src/game/recipes.ts | 415 ----- src/index.html | 5 +- tmp.patch | 1450 +++++++++++++++++ yarn.lock | 33 + 24 files changed, 2119 insertions(+), 488 deletions(-) create mode 100755 fontawesome-npmrc.sh create mode 100644 src/assets/images/favicon.ico create mode 100644 src/components/RecipeList.tsx delete mode 100644 src/game/recipes.ts create mode 100644 tmp.patch diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 03295ad..8475311 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -38,7 +38,7 @@ "forwardPorts": [], // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "./setup-nvm.sh && yarn install && ./install-globals.sh && ./install-copilot-cli.sh && ./setup-shell-config.sh && export $(grep -v '^#' ${DOTENV_CONFIG_PATH} | xargs)" + "postCreateCommand": "export $(grep -v '^#' /workspaces/dominion-assistant/.env | xargs) && ./fontawesome-npmrc.sh && ./setup-nvm.sh && yarn install && ./install-globals.sh && ./install-copilot-cli.sh && ./setup-shell-config.sh && export $(grep -v '^#' ${DOTENV_CONFIG_PATH} | xargs)" // Configure tool-specific properties. // "customizations": {}, diff --git a/.env.example b/.env.example index 7e3f97f..2291094 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,2 @@ -CODACY_PROJECT_TOKEN=your-codacy-project-token \ No newline at end of file +CODACY_PROJECT_TOKEN=your-codacy-project-token +FONTAWESOME_KEY=your-fontawesome-key \ No newline at end of file diff --git a/.github/workflows/deploy-to-s3.yml b/.github/workflows/deploy-to-s3.yml index e8075ab..0dc88d5 100644 --- a/.github/workflows/deploy-to-s3.yml +++ b/.github/workflows/deploy-to-s3.yml @@ -21,6 +21,11 @@ jobs: with: node-version: '20' + - name: Set up FontAwesome NPM registry + run: ./fontawesome-npmrc.sh + env: + FONTAWESOME_KEY: ${{ secrets.FONTAWESOME_KEY }} + - name: Install dependencies run: yarn install @@ -59,6 +64,11 @@ jobs: with: node-version: '20' + - name: Set up FontAwesome NPM registry + run: ./fontawesome-npmrc.sh + env: + FONTAWESOME_KEY: ${{ secrets.FONTAWESOME_KEY }} + - name: Install dependencies run: yarn install diff --git a/.gitignore b/.gitignore index f41e43d..b5ae9f0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # See http://help.github.com/ignore-files/ for more about ignoring files. /.env +/.npmrc # game storage /game-storage/ diff --git a/README.md b/README.md index fcf3a30..97a8989 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,7 @@ A popup should offer to open the application in a browser. - Copy the .env.example in the project root to .env - Fill in the CODACY_PROJECT_TOKEN from https://app.codacy.com/gh/Digital-Defiance/DominionAssistant/settings/coverage +- Fill in the FONTAWESOME_KEY from https://fontawesome.com/kits/1111eb8cf6/package - Save the .env file 4. When prompted, click "Reopen in Container" or use the command palette (F1) and select "Remote-Containers: Reopen in Container". @@ -200,6 +201,19 @@ Join our community of developers. ## Changelog +### Thu Nov 07 02:06:00 2024 + +- Version 0.10.3 + - Improve FontAwesome icons for goruped actions + - Add icon to the log for grouped actions + +### Thu Nov 07 00:40:00 2024 + +- Version 0.10.2 + - Add FontAwesome + - Add custom icon support to recipes/common actions + - Add icons to Common Actions/recipes tab + ### Wed Nov 06 23:09:00 2024 - Version 0.10.1 diff --git a/fontawesome-npmrc.sh b/fontawesome-npmrc.sh new file mode 100755 index 0000000..f876534 --- /dev/null +++ b/fontawesome-npmrc.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +echo "@fortawesome:registry=https://npm.fontawesome.com/ +@awesome.me:registry=https://npm.fontawesome.com/ +//npm.fontawesome.com/:_authToken=$FONTAWESOME_KEY" > .npmrc \ No newline at end of file diff --git a/package.json b/package.json index 06557c6..1209904 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,13 @@ }, "private": true, "dependencies": { + "@awesome.me/kit-1111eb8cf6": "^1.0.4", "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", + "@fortawesome/fontawesome-common-types": "^6.6.0", + "@fortawesome/fontawesome-svg-core": "^6.6.0", + "@fortawesome/pro-solid-svg-icons": "^6.6.0", + "@fortawesome/react-fontawesome": "^0.2.2", "@mui/icons-material": "^6.1.3", "@mui/material": "^6.1.3", "chart.js": "^4.4.5", diff --git a/src/assets/images/favicon.ico b/src/assets/images/favicon.ico new file mode 100644 index 0000000..e69de29 diff --git a/src/components/GameInterface.tsx b/src/components/GameInterface.tsx index b6159f9..a933344 100644 --- a/src/components/GameInterface.tsx +++ b/src/components/GameInterface.tsx @@ -28,7 +28,7 @@ import { IGame } from '@/game/interfaces/game'; import { deepClone } from '@/game/utils'; import TurnAdjustmentsSummary from '@/components/TurnAdjustments'; import FloatingCounter from '@/components/FloatingCounter'; -import { RecipesComponent } from '@/components/Recipes'; +import { RecipesList } from '@/components/RecipeList'; import ForwardRefBox from './ForwardRefBox'; interface GameInterfaceProps { @@ -168,7 +168,7 @@ const GameInterface: FC = ({ nextTurn, endGame, undoLastActi )} {tabValue === 1 && } {tabValue === 2 && } - {tabValue === 3 && } + {tabValue === 3 && } diff --git a/src/components/GameLogEntry.tsx b/src/components/GameLogEntry.tsx index f236545..b34fb77 100644 --- a/src/components/GameLogEntry.tsx +++ b/src/components/GameLogEntry.tsx @@ -28,6 +28,7 @@ import { AdjustmentActions } from '@/game/constants'; import ColoredPlayerName from '@/components/ColoredPlayerName'; import { getAdjustedDurationFromCacheByIndex } from '@/game/dominion-lib-time'; import '@/styles.scss'; +import { Recipes } from './Recipes'; interface GameLogEntryProps { logIndex: number; @@ -186,6 +187,16 @@ const GameLogEntry: FC = ({ logIndex, entry, onOpenTurnAdjust {relevantPlayer !== undefined && !isNotTriggeredByPlayer && ( )} + {entry.action === GameLogAction.GROUPED_ACTION && + entry.actionKey && + Recipes[entry.actionKey] && ( + + {Recipes[entry.actionKey].icon} + + )} {actionText} diff --git a/src/components/RecipeCard.tsx b/src/components/RecipeCard.tsx index 63e1c8e..3558e77 100644 --- a/src/components/RecipeCard.tsx +++ b/src/components/RecipeCard.tsx @@ -5,13 +5,14 @@ import { prepareGroupedActionTriggers, } from '@/game/dominion-lib-log'; import { IGroupedAction } from '@/game/interfaces/grouped-action'; -import { Recipes } from '@/game/recipes'; -import { Box, Link } from '@mui/material'; +import { RecipeKey, Recipes } from '@/components/Recipes'; +import { Box, Link, Typography } from '@mui/material'; +import PlayArrowIcon from '@mui/icons-material/PlayArrow'; import { useGameContext } from '@/components/GameContext'; import { useAlert } from '@/components/AlertContext'; interface RecipeCardProps { - recipeKey: string; + recipeKey: RecipeKey; recipe: IGroupedAction; } @@ -30,7 +31,8 @@ export const RecipeCard: FC = ({ recipeKey, recipe }) => { groupedAction, new Date(), applyGroupedActionSubAction, - prepareGroupedActionTriggers + prepareGroupedActionTriggers, + recipeKey ); setGameState(newGame); } catch (error) { @@ -43,9 +45,21 @@ export const RecipeCard: FC = ({ recipeKey, recipe }) => { }; return ( - - handleRecipe(event, recipeKey)}> - {recipe.name} + + handleRecipe(event, recipeKey)} + sx={{ display: 'flex', alignItems: 'center' }} + > + + {recipe.icon ?? } + + + {recipe.name} + ); diff --git a/src/components/RecipeList.tsx b/src/components/RecipeList.tsx new file mode 100644 index 0000000..a90ff96 --- /dev/null +++ b/src/components/RecipeList.tsx @@ -0,0 +1,59 @@ +import React, { CSSProperties, FC, memo, RefObject, useEffect, useState } from 'react'; +import TabTitle from '@/components/TabTitle'; +import { Recipes } from '@/components/Recipes'; +import { FixedSizeList } from 'react-window'; +import { RecipeCard } from '@/components/RecipeCard'; + +interface RecipesProps { + viewBoxRef: RefObject; +} + +export const RecipesList: FC = ({ viewBoxRef }) => { + const [listHeight, setListHeight] = useState( + viewBoxRef.current?.getBoundingClientRect().height ?? 0 + ); + const [listWidth, setListWidth] = useState( + viewBoxRef.current?.getBoundingClientRect().width ?? 0 + ); + const MemoizedRecipeCard = memo(RecipeCard); + + useEffect(() => { + const handleResize = () => { + const viewBoxBound = viewBoxRef.current?.getBoundingClientRect(); + const viewBoxHeight = viewBoxBound?.height ?? 0; + const viewBoxWidth = viewBoxBound?.width ?? 0; + setListHeight(viewBoxHeight); + setListWidth(viewBoxWidth); + }; + + window.addEventListener('resize', handleResize); + handleResize(); // Set initial height + + return () => { + window.removeEventListener('resize', handleResize); + }; + }, [viewBoxRef]); + + const recipesArray = Object.entries(Recipes); + + const Row = ({ index, style }: { index: number; style: CSSProperties }) => ( +
+ +
+ ); + + return ( + <> + Common Actions + + {Row} + + + ); +}; diff --git a/src/components/Recipes.tsx b/src/components/Recipes.tsx index 6edd1d6..b9e5a59 100644 --- a/src/components/Recipes.tsx +++ b/src/components/Recipes.tsx @@ -1,59 +1,451 @@ -import React, { CSSProperties, FC, memo, RefObject, useEffect, useState } from 'react'; -import TabTitle from '@/components/TabTitle'; -import { Recipes } from '@/game/recipes'; -import { FixedSizeList } from 'react-window'; -import { RecipeCard } from '@/components/RecipeCard'; +import React from 'react'; +import { GameLogAction } from '@/game/enumerations/game-log-action'; +import { IGroupedAction } from '@/game/interfaces/grouped-action'; +import { GroupedActionDest } from '@/game/enumerations/grouped-action-dest'; +import { GroupedActionTrigger } from '@/game/enumerations/grouped-action-trigger'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { + faStore, + faFlask, + faHome, + faHammer, + faGlassCheers, + faShoppingCart, + faMonument, + faCoins, + faUserFriends, + faShoppingBag, + faTasks, + faCrown, + faHandshake, + faLandmark, + faChessBishop, +} from '@fortawesome/pro-solid-svg-icons'; -interface RecipesProps { - viewBoxRef: RefObject; -} +export type RecipeKey = keyof typeof Recipes; -export const RecipesComponent: FC = ({ viewBoxRef }) => { - const [listHeight, setListHeight] = useState( - viewBoxRef.current?.getBoundingClientRect().height ?? 0 - ); - const [listWidth, setListWidth] = useState( - viewBoxRef.current?.getBoundingClientRect().width ?? 0 - ); - const MemoizedRecipeCard = memo(RecipeCard); - - useEffect(() => { - const handleResize = () => { - const viewBoxBound = viewBoxRef.current?.getBoundingClientRect(); - const viewBoxHeight = viewBoxBound?.height ?? 0; - const viewBoxWidth = viewBoxBound?.width ?? 0; - setListHeight(viewBoxHeight); - setListWidth(viewBoxWidth); - }; - - window.addEventListener('resize', handleResize); - handleResize(); // Set initial height - - return () => { - window.removeEventListener('resize', handleResize); - }; - }, [viewBoxRef]); - - const recipesArray = Object.entries(Recipes); - - const Row = ({ index, style }: { index: number; style: CSSProperties }) => ( -
- -
- ); - - return ( - <> - Common Actions - - {Row} - - - ); +export const Recipes: Record = { + OneCardOneAction: { + name: 'One Card, One Action', + actions: { + [GroupedActionDest.CurrentPlayerIndex]: [ + { + action: GameLogAction.REMOVE_ACTIONS, + count: 1, + }, + { + action: GameLogAction.ADD_CARDS, + count: 1, + }, + { + action: GameLogAction.ADD_ACTIONS, + count: 1, + }, + ], + [GroupedActionDest.SelectedPlayerIndex]: [], + [GroupedActionDest.AllPlayers]: [], + [GroupedActionDest.AllPlayersExceptCurrent]: [], + [GroupedActionDest.AllPlayersExceptSelected]: [], + }, + }, + OneCardTwoActions: { + name: 'One Card, Two Actions', + icon: , + actions: { + [GroupedActionDest.CurrentPlayerIndex]: [ + { + action: GameLogAction.REMOVE_ACTIONS, + count: 1, + }, + { + action: GameLogAction.ADD_CARDS, + count: 1, + }, + { + action: GameLogAction.ADD_ACTIONS, + count: 2, + }, + ], + [GroupedActionDest.SelectedPlayerIndex]: [], + [GroupedActionDest.AllPlayers]: [], + [GroupedActionDest.AllPlayersExceptCurrent]: [], + [GroupedActionDest.AllPlayersExceptSelected]: [], + }, + }, + OneCardTwoActionsOneBuy: { + name: 'One Card, Two Actions, One Buy', + icon: , + actions: { + [GroupedActionDest.CurrentPlayerIndex]: [ + { + action: GameLogAction.REMOVE_ACTIONS, + count: 1, + }, + { + action: GameLogAction.ADD_CARDS, + count: 1, + }, + { + action: GameLogAction.ADD_ACTIONS, + count: 2, + }, + { + action: GameLogAction.ADD_BUYS, + count: 1, + }, + ], + [GroupedActionDest.SelectedPlayerIndex]: [], + [GroupedActionDest.AllPlayers]: [], + [GroupedActionDest.AllPlayersExceptCurrent]: [], + [GroupedActionDest.AllPlayersExceptSelected]: [], + }, + }, + OneCardThreeActions: { + name: 'One Card, Three Actions', + icon: , + actions: { + [GroupedActionDest.CurrentPlayerIndex]: [ + { + action: GameLogAction.REMOVE_ACTIONS, + count: 1, + }, + { + action: GameLogAction.ADD_CARDS, + count: 1, + }, + { + action: GameLogAction.ADD_ACTIONS, + count: 3, + }, + ], + [GroupedActionDest.SelectedPlayerIndex]: [], + [GroupedActionDest.AllPlayers]: [], + [GroupedActionDest.AllPlayersExceptCurrent]: [], + [GroupedActionDest.AllPlayersExceptSelected]: [], + }, + }, + Festival: { + name: 'Festival', + icon: , + actions: { + [GroupedActionDest.CurrentPlayerIndex]: [ + { + action: GameLogAction.REMOVE_ACTIONS, + count: 1, + }, + { + action: GameLogAction.ADD_ACTIONS, + count: 2, + }, + { + action: GameLogAction.ADD_BUYS, + count: 1, + }, + { + action: GameLogAction.ADD_COINS, + count: 2, + }, + ], + [GroupedActionDest.SelectedPlayerIndex]: [], + [GroupedActionDest.AllPlayers]: [], + [GroupedActionDest.AllPlayersExceptCurrent]: [], + [GroupedActionDest.AllPlayersExceptSelected]: [], + }, + }, + Laboratory: { + name: 'Laboratory', + icon: , + actions: { + [GroupedActionDest.CurrentPlayerIndex]: [ + { + action: GameLogAction.REMOVE_ACTIONS, + count: 1, + }, + { + action: GameLogAction.ADD_CARDS, + count: 2, + }, + { + action: GameLogAction.ADD_ACTIONS, + count: 1, + }, + ], + [GroupedActionDest.SelectedPlayerIndex]: [], + [GroupedActionDest.AllPlayers]: [], + [GroupedActionDest.AllPlayersExceptCurrent]: [], + [GroupedActionDest.AllPlayersExceptSelected]: [], + }, + }, + Smithy: { + name: 'Smithy', + icon: , + actions: { + [GroupedActionDest.CurrentPlayerIndex]: [ + { + action: GameLogAction.REMOVE_ACTIONS, + count: 1, + }, + { + action: GameLogAction.ADD_CARDS, + count: 3, + }, + ], + [GroupedActionDest.SelectedPlayerIndex]: [], + [GroupedActionDest.AllPlayers]: [], + [GroupedActionDest.AllPlayersExceptCurrent]: [], + [GroupedActionDest.AllPlayersExceptSelected]: [], + }, + }, + CouncilRoom: { + name: 'Council Room', + icon: , + actions: { + [GroupedActionDest.CurrentPlayerIndex]: [ + { + action: GameLogAction.REMOVE_ACTIONS, + count: 1, + }, + { + action: GameLogAction.ADD_CARDS, + count: 4, + }, + { + action: GameLogAction.ADD_BUYS, + count: 1, + }, + ], + [GroupedActionDest.SelectedPlayerIndex]: [], + [GroupedActionDest.AllPlayers]: [], + [GroupedActionDest.AllPlayersExceptCurrent]: [ + { + action: GameLogAction.ADD_NEXT_TURN_CARDS, + count: 1, + }, + ], + [GroupedActionDest.AllPlayersExceptSelected]: [], + }, + triggers: { + [GroupedActionTrigger.AfterNextTurnBegins]: { + [GroupedActionDest.AllPlayersExceptCurrent]: [ + { + action: GameLogAction.REMOVE_NEXT_TURN_CARDS, + count: 1, + }, + ], + [GroupedActionDest.AllPlayersExceptSelected]: [], + [GroupedActionDest.CurrentPlayerIndex]: [], + [GroupedActionDest.SelectedPlayerIndex]: [], + [GroupedActionDest.AllPlayers]: [], + }, + }, + }, + Market: { + name: 'Market', + icon: , + actions: { + [GroupedActionDest.CurrentPlayerIndex]: [ + { + action: GameLogAction.REMOVE_ACTIONS, + count: 1, + }, + { + action: GameLogAction.ADD_CARDS, + count: 1, + }, + { + action: GameLogAction.ADD_ACTIONS, + count: 1, + }, + { + action: GameLogAction.ADD_BUYS, + count: 1, + }, + { + action: GameLogAction.ADD_COINS, + count: 1, + }, + ], + [GroupedActionDest.SelectedPlayerIndex]: [], + [GroupedActionDest.AllPlayers]: [], + [GroupedActionDest.AllPlayersExceptCurrent]: [], + [GroupedActionDest.AllPlayersExceptSelected]: [], + }, + }, + GrandMarket: { + name: 'Grand Market', + icon: , + actions: { + [GroupedActionDest.CurrentPlayerIndex]: [ + { + action: GameLogAction.REMOVE_ACTIONS, + count: 1, + }, + { + action: GameLogAction.ADD_CARDS, + count: 1, + }, + { + action: GameLogAction.ADD_ACTIONS, + count: 1, + }, + { + action: GameLogAction.ADD_BUYS, + count: 1, + }, + { + action: GameLogAction.ADD_COINS, + count: 2, + }, + ], + [GroupedActionDest.SelectedPlayerIndex]: [], + [GroupedActionDest.AllPlayers]: [], + [GroupedActionDest.AllPlayersExceptCurrent]: [], + [GroupedActionDest.AllPlayersExceptSelected]: [], + }, + }, + Dominate: { + name: 'Dominate', + icon: , + actions: { + [GroupedActionDest.CurrentPlayerIndex]: [ + { + action: GameLogAction.REMOVE_BUYS, + count: 1, + }, + { + action: GameLogAction.ADD_PROVINCES, + count: 1, + }, + { + action: GameLogAction.ADD_VP_TOKENS, + count: 9, + }, + ], + [GroupedActionDest.SelectedPlayerIndex]: [], + [GroupedActionDest.AllPlayers]: [], + [GroupedActionDest.AllPlayersExceptCurrent]: [], + [GroupedActionDest.AllPlayersExceptSelected]: [], + }, + }, + Alliance: { + name: 'Alliance', + icon: , + actions: { + [GroupedActionDest.CurrentPlayerIndex]: [ + { + action: GameLogAction.REMOVE_BUYS, + count: 1, + }, + { + action: GameLogAction.ADD_PROVINCES, + count: 1, + }, + { + action: GameLogAction.ADD_DUCHIES, + count: 1, + }, + { + action: GameLogAction.ADD_ESTATES, + count: 1, + }, + ], + [GroupedActionDest.SelectedPlayerIndex]: [], + [GroupedActionDest.AllPlayers]: [], + [GroupedActionDest.AllPlayersExceptCurrent]: [], + [GroupedActionDest.AllPlayersExceptSelected]: [], + }, + }, + Demesne: { + name: 'Demesne', + icon: , + actions: { + [GroupedActionDest.CurrentPlayerIndex]: [ + { + action: GameLogAction.REMOVE_ACTIONS, + count: 1, + }, + { + action: GameLogAction.ADD_ACTIONS, + count: 2, + }, + { + action: GameLogAction.ADD_BUYS, + count: 2, + }, + ], + [GroupedActionDest.SelectedPlayerIndex]: [], + [GroupedActionDest.AllPlayers]: [], + [GroupedActionDest.AllPlayersExceptCurrent]: [], + [GroupedActionDest.AllPlayersExceptSelected]: [], + }, + }, + BishopEstate: { + name: 'Bishop an Estate', + icon: , + actions: { + [GroupedActionDest.CurrentPlayerIndex]: [ + { + action: GameLogAction.REMOVE_ACTIONS, + count: 1, + }, + { + action: GameLogAction.REMOVE_ESTATES, + count: 1, + trash: true, + }, + { + action: GameLogAction.ADD_VP_TOKENS, + count: 2, + }, + ], + [GroupedActionDest.SelectedPlayerIndex]: [], + [GroupedActionDest.AllPlayers]: [], + [GroupedActionDest.AllPlayersExceptCurrent]: [], + [GroupedActionDest.AllPlayersExceptSelected]: [], + }, + }, + Monument: { + name: 'Monument', + icon: , + actions: { + [GroupedActionDest.CurrentPlayerIndex]: [ + { + action: GameLogAction.REMOVE_ACTIONS, + count: 1, + }, + { + action: GameLogAction.ADD_COINS, + count: 2, + }, + { + action: GameLogAction.ADD_VP_TOKENS, + count: 1, + }, + ], + [GroupedActionDest.SelectedPlayerIndex]: [], + [GroupedActionDest.AllPlayers]: [], + [GroupedActionDest.AllPlayersExceptCurrent]: [], + [GroupedActionDest.AllPlayersExceptSelected]: [], + }, + }, + Ducat: { + name: 'Ducat', + icon: , + actions: { + [GroupedActionDest.CurrentPlayerIndex]: [ + { + action: GameLogAction.ADD_COFFERS, + count: 1, + }, + { + action: GameLogAction.ADD_BUYS, + count: 1, + }, + ], + [GroupedActionDest.SelectedPlayerIndex]: [], + [GroupedActionDest.AllPlayers]: [], + [GroupedActionDest.AllPlayersExceptCurrent]: [], + [GroupedActionDest.AllPlayersExceptSelected]: [], + }, + }, }; diff --git a/src/game/__tests__/dominion-lib-log-applyGroupedAction.spec.ts b/src/game/__tests__/dominion-lib-log-applyGroupedAction.spec.ts index 1b9bc55..eb85a27 100644 --- a/src/game/__tests__/dominion-lib-log-applyGroupedAction.spec.ts +++ b/src/game/__tests__/dominion-lib-log-applyGroupedAction.spec.ts @@ -10,6 +10,7 @@ import { getGameStartTime, prepareGroupedActionTriggers, } from '@/game/dominion-lib-log'; +import { RecipeKey } from '@/components/Recipes'; function createGroupedActionBase(): IGroupedAction { return { @@ -608,6 +609,37 @@ describe('applyGroupedAction', () => { expect(prepareGroupedActionTriggersMock).toHaveBeenCalledTimes(1); expect(updatedGame.pendingGroupedActions).toStrictEqual([]); }); + + it('should throw an error for an invalid grouped action key', () => { + const invalidGroupedActionKey = 'InvalidKey' as RecipeKey; + expect(() => { + applyGroupedAction( + mockGame, + groupedAction, + actionDate, + applyGroupedActionSubActionMock, + prepareGroupedActionTriggersMock, + invalidGroupedActionKey + ); + }).toThrow(`Invalid recipe key: ${invalidGroupedActionKey}`); + }); + + it('should create a log with the actionKey if provided and valid', () => { + groupedAction.actions[GroupedActionDest.CurrentPlayerIndex] = [ + { action: GameLogAction.ADD_ACTIONS, count: 1 }, + ]; + const updatedGame = applyGroupedAction( + mockGame, + groupedAction, + actionDate, + applyGroupedActionSubActionMock, + prepareGroupedActionTriggersMock, + 'OneCardOneAction' as RecipeKey + ); + expect(updatedGame.log.length).toBe(3); + expect(updatedGame.log[1].action).toBe(GameLogAction.GROUPED_ACTION); + expect(updatedGame.log[1].actionKey).toBe('OneCardOneAction'); + }); }); describe('applyGroupedAction with different current and selected players', () => { diff --git a/src/game/__tests__/dominion-lib-undo-reconstructGameState.spec.ts b/src/game/__tests__/dominion-lib-undo-reconstructGameState.spec.ts index 75c5e48..d28c3e0 100644 --- a/src/game/__tests__/dominion-lib-undo-reconstructGameState.spec.ts +++ b/src/game/__tests__/dominion-lib-undo-reconstructGameState.spec.ts @@ -21,6 +21,7 @@ import { } from '@/game/dominion-lib-log'; import { createMockGame } from '@/__fixtures__/dominion-lib-fixtures'; import { GroupedActionDest } from '@/game/enumerations/grouped-action-dest'; +import { RecipeKey } from '@/components/Recipes'; describe('reconstructGameState', () => { let baseGame: IGame; diff --git a/src/game/constants.ts b/src/game/constants.ts index 3307546..c8697f5 100644 --- a/src/game/constants.ts +++ b/src/game/constants.ts @@ -14,7 +14,7 @@ import { IRisingSunFeatures } from '@/game/interfaces/set-features/rising-sun'; import { IExpansionsEnabled } from '@/game/interfaces/expansions-enabled'; import { calculateInitialSunTokens } from '@/game/interfaces/set-mats/prophecy'; -export const VERSION_NUMBER = '0.10.1'; +export const VERSION_NUMBER = '0.10.3'; export const LAST_COMPATIBLE_SAVE_VERSION = '0.10.0'; export const MIN_PLAYERS = 2; diff --git a/src/game/dominion-lib-log.ts b/src/game/dominion-lib-log.ts index 5cf9d0a..7cac84b 100644 --- a/src/game/dominion-lib-log.ts +++ b/src/game/dominion-lib-log.ts @@ -37,6 +37,7 @@ import { GroupedActionDest } from '@/game/enumerations/grouped-action-dest'; import { InvalidPlayerIndexError } from '@/game/errors/invalid-player-index'; import { InvalidActionError } from '@/game/errors/invalid-action'; import { GroupedActionTrigger } from '@/game/enumerations/grouped-action-trigger'; +import { RecipeKey, Recipes } from '@/components/Recipes'; /** * Map a victory field and subfield to a game log action. @@ -826,8 +827,11 @@ export function getGroupedActionTargetPlayers(game: IGame, dest: GroupedActionDe /** * Apply a grouped action to the game. * @param game - The game state - * @param playerIndex - The index of the player performing the action + * @param groupedActionKey - The key of the grouped action to apply * @param groupedAction - The grouped action to apply + * @param actionDate - The date of the action + * @param applyGroupedActionSubAction - A function to apply sub-actions to the game + * @param prepareGroupedActionTriggers - A function to prepare triggers for the grouped action * @returns The updated game state */ export function applyGroupedAction( @@ -845,9 +849,13 @@ export function applyGroupedAction( game: IGame, groupedAction: IGroupedAction, groupedActionId: string - ) => IGame + ) => IGame, + groupedActionKey?: RecipeKey ): IGame { try { + if (groupedActionKey && Recipes[groupedActionKey] === undefined) { + throw new Error(`Invalid recipe key: ${groupedActionKey}`); + } let updatedGame = deepClone(game); const groupedActionId = uuidv4(); // Create a log entry for the grouped action @@ -859,6 +867,7 @@ export function applyGroupedAction( currentPlayerIndex: updatedGame.currentPlayerIndex, turn: updatedGame.currentTurn, actionName: groupedAction.name, + actionKey: groupedActionKey, }; updatedGame.log.push(groupedActionLog); const { timeCache, turnStatisticsCache } = updateCachesForEntry(updatedGame, groupedActionLog); diff --git a/src/game/interfaces/grouped-action.ts b/src/game/interfaces/grouped-action.ts index 0cd1981..933a62d 100644 --- a/src/game/interfaces/grouped-action.ts +++ b/src/game/interfaces/grouped-action.ts @@ -1,9 +1,11 @@ import { ILogEntry } from '@/game/interfaces/log-entry'; import { GroupedActionDest } from '@/game/enumerations/grouped-action-dest'; import { GroupedActionTrigger } from '@/game/enumerations/grouped-action-trigger'; +import { ReactElement } from 'react'; export interface IGroupedAction { name: string; + icon?: ReactElement; actions: Record>>; triggers?: Record>>>; } diff --git a/src/game/interfaces/log-entry-raw.ts b/src/game/interfaces/log-entry-raw.ts index 4e672f8..6186a84 100644 --- a/src/game/interfaces/log-entry-raw.ts +++ b/src/game/interfaces/log-entry-raw.ts @@ -1,3 +1,4 @@ +import { RecipeKey } from '@/components/Recipes'; import { IPlayerGameTurnDetails } from '@/game/interfaces/player-game-turn-details'; export interface ILogEntryRaw { @@ -56,4 +57,6 @@ export interface ILogEntryRaw { * Name of the action taken, for grouped actions */ actionName?: string; + /** Key for the grouped action */ + actionKey?: RecipeKey; } diff --git a/src/game/interfaces/log-entry.ts b/src/game/interfaces/log-entry.ts index 40493bf..8b83539 100644 --- a/src/game/interfaces/log-entry.ts +++ b/src/game/interfaces/log-entry.ts @@ -1,3 +1,4 @@ +import { RecipeKey } from '@/components/Recipes'; import { GameLogAction } from '@/game/enumerations/game-log-action'; import { IPlayerGameTurnDetails } from '@/game/interfaces/player-game-turn-details'; @@ -53,7 +54,9 @@ export interface ILogEntry { */ playerTurnDetails?: IPlayerGameTurnDetails[]; /** - * Name of the action taken, for grouped actions + * Name of the grouped action taken, for grouped actions */ actionName?: string; + /** Key for the grouped action */ + actionKey?: RecipeKey; } diff --git a/src/game/recipes.ts b/src/game/recipes.ts deleted file mode 100644 index 3353de7..0000000 --- a/src/game/recipes.ts +++ /dev/null @@ -1,415 +0,0 @@ -import { GameLogAction } from '@/game/enumerations/game-log-action'; -import { IGroupedAction } from '@/game/interfaces/grouped-action'; -import { GroupedActionDest } from '@/game/enumerations/grouped-action-dest'; -import { GroupedActionTrigger } from '@/game/enumerations/grouped-action-trigger'; - -export const Recipes: Record = { - OneCardOneAction: { - name: 'One Card, One Action', - actions: { - [GroupedActionDest.CurrentPlayerIndex]: [ - { - action: GameLogAction.REMOVE_ACTIONS, - count: 1, - }, - { - action: GameLogAction.ADD_CARDS, - count: 1, - }, - { - action: GameLogAction.ADD_ACTIONS, - count: 1, - }, - ], - [GroupedActionDest.SelectedPlayerIndex]: [], - [GroupedActionDest.AllPlayers]: [], - [GroupedActionDest.AllPlayersExceptCurrent]: [], - [GroupedActionDest.AllPlayersExceptSelected]: [], - }, - }, - OneCardTwoActions: { - name: 'One Card, Two Actions', - actions: { - [GroupedActionDest.CurrentPlayerIndex]: [ - { - action: GameLogAction.REMOVE_ACTIONS, - count: 1, - }, - { - action: GameLogAction.ADD_CARDS, - count: 1, - }, - { - action: GameLogAction.ADD_ACTIONS, - count: 2, - }, - ], - [GroupedActionDest.SelectedPlayerIndex]: [], - [GroupedActionDest.AllPlayers]: [], - [GroupedActionDest.AllPlayersExceptCurrent]: [], - [GroupedActionDest.AllPlayersExceptSelected]: [], - }, - }, - OneCardTwoActionsOneBuy: { - name: 'One Card, Two Actions, One Buy', - actions: { - [GroupedActionDest.CurrentPlayerIndex]: [ - { - action: GameLogAction.REMOVE_ACTIONS, - count: 1, - }, - { - action: GameLogAction.ADD_CARDS, - count: 1, - }, - { - action: GameLogAction.ADD_ACTIONS, - count: 2, - }, - { - action: GameLogAction.ADD_BUYS, - count: 1, - }, - ], - [GroupedActionDest.SelectedPlayerIndex]: [], - [GroupedActionDest.AllPlayers]: [], - [GroupedActionDest.AllPlayersExceptCurrent]: [], - [GroupedActionDest.AllPlayersExceptSelected]: [], - }, - }, - OneCardThreeActions: { - name: 'One Card, Three Actions', - actions: { - [GroupedActionDest.CurrentPlayerIndex]: [ - { - action: GameLogAction.REMOVE_ACTIONS, - count: 1, - }, - { - action: GameLogAction.ADD_CARDS, - count: 1, - }, - { - action: GameLogAction.ADD_ACTIONS, - count: 3, - }, - ], - [GroupedActionDest.SelectedPlayerIndex]: [], - [GroupedActionDest.AllPlayers]: [], - [GroupedActionDest.AllPlayersExceptCurrent]: [], - [GroupedActionDest.AllPlayersExceptSelected]: [], - }, - }, - Festival: { - name: 'Festival', - actions: { - [GroupedActionDest.CurrentPlayerIndex]: [ - { - action: GameLogAction.REMOVE_ACTIONS, - count: 1, - }, - { - action: GameLogAction.ADD_ACTIONS, - count: 2, - }, - { - action: GameLogAction.ADD_BUYS, - count: 1, - }, - { - action: GameLogAction.ADD_COINS, - count: 2, - }, - ], - [GroupedActionDest.SelectedPlayerIndex]: [], - [GroupedActionDest.AllPlayers]: [], - [GroupedActionDest.AllPlayersExceptCurrent]: [], - [GroupedActionDest.AllPlayersExceptSelected]: [], - }, - }, - Laboratory: { - name: 'Laboratory', - actions: { - [GroupedActionDest.CurrentPlayerIndex]: [ - { - action: GameLogAction.REMOVE_ACTIONS, - count: 1, - }, - { - action: GameLogAction.ADD_CARDS, - count: 2, - }, - { - action: GameLogAction.ADD_ACTIONS, - count: 1, - }, - ], - [GroupedActionDest.SelectedPlayerIndex]: [], - [GroupedActionDest.AllPlayers]: [], - [GroupedActionDest.AllPlayersExceptCurrent]: [], - [GroupedActionDest.AllPlayersExceptSelected]: [], - }, - }, - Smithy: { - name: 'Smithy', - actions: { - [GroupedActionDest.CurrentPlayerIndex]: [ - { - action: GameLogAction.REMOVE_ACTIONS, - count: 1, - }, - { - action: GameLogAction.ADD_CARDS, - count: 3, - }, - ], - [GroupedActionDest.SelectedPlayerIndex]: [], - [GroupedActionDest.AllPlayers]: [], - [GroupedActionDest.AllPlayersExceptCurrent]: [], - [GroupedActionDest.AllPlayersExceptSelected]: [], - }, - }, - CouncilRoom: { - name: 'Council Room', - actions: { - [GroupedActionDest.CurrentPlayerIndex]: [ - { - action: GameLogAction.REMOVE_ACTIONS, - count: 1, - }, - { - action: GameLogAction.ADD_CARDS, - count: 4, - }, - { - action: GameLogAction.ADD_BUYS, - count: 1, - }, - ], - [GroupedActionDest.SelectedPlayerIndex]: [], - [GroupedActionDest.AllPlayers]: [], - [GroupedActionDest.AllPlayersExceptCurrent]: [ - { - action: GameLogAction.ADD_NEXT_TURN_CARDS, - count: 1, - }, - ], - [GroupedActionDest.AllPlayersExceptSelected]: [], - }, - triggers: { - [GroupedActionTrigger.AfterNextTurnBegins]: { - [GroupedActionDest.AllPlayersExceptCurrent]: [ - { - action: GameLogAction.REMOVE_NEXT_TURN_CARDS, - count: 1, - }, - ], - [GroupedActionDest.AllPlayersExceptSelected]: [], - [GroupedActionDest.CurrentPlayerIndex]: [], - [GroupedActionDest.SelectedPlayerIndex]: [], - [GroupedActionDest.AllPlayers]: [], - }, - }, - }, - Market: { - name: 'Market', - actions: { - [GroupedActionDest.CurrentPlayerIndex]: [ - { - action: GameLogAction.REMOVE_ACTIONS, - count: 1, - }, - { - action: GameLogAction.ADD_CARDS, - count: 1, - }, - { - action: GameLogAction.ADD_ACTIONS, - count: 1, - }, - { - action: GameLogAction.ADD_BUYS, - count: 1, - }, - { - action: GameLogAction.ADD_COINS, - count: 1, - }, - ], - [GroupedActionDest.SelectedPlayerIndex]: [], - [GroupedActionDest.AllPlayers]: [], - [GroupedActionDest.AllPlayersExceptCurrent]: [], - [GroupedActionDest.AllPlayersExceptSelected]: [], - }, - }, - GrandMarket: { - name: 'Grand Market', - actions: { - [GroupedActionDest.CurrentPlayerIndex]: [ - { - action: GameLogAction.REMOVE_ACTIONS, - count: 1, - }, - { - action: GameLogAction.ADD_CARDS, - count: 1, - }, - { - action: GameLogAction.ADD_ACTIONS, - count: 1, - }, - { - action: GameLogAction.ADD_BUYS, - count: 1, - }, - { - action: GameLogAction.ADD_COINS, - count: 2, - }, - ], - [GroupedActionDest.SelectedPlayerIndex]: [], - [GroupedActionDest.AllPlayers]: [], - [GroupedActionDest.AllPlayersExceptCurrent]: [], - [GroupedActionDest.AllPlayersExceptSelected]: [], - }, - }, - Dominate: { - name: 'Dominate', - actions: { - [GroupedActionDest.CurrentPlayerIndex]: [ - { - action: GameLogAction.REMOVE_BUYS, - count: 1, - }, - { - action: GameLogAction.ADD_PROVINCES, - count: 1, - }, - { - action: GameLogAction.ADD_VP_TOKENS, - count: 9, - }, - ], - [GroupedActionDest.SelectedPlayerIndex]: [], - [GroupedActionDest.AllPlayers]: [], - [GroupedActionDest.AllPlayersExceptCurrent]: [], - [GroupedActionDest.AllPlayersExceptSelected]: [], - }, - }, - Alliance: { - name: 'Alliance', - actions: { - [GroupedActionDest.CurrentPlayerIndex]: [ - { - action: GameLogAction.REMOVE_BUYS, - count: 1, - }, - { - action: GameLogAction.ADD_PROVINCES, - count: 1, - }, - { - action: GameLogAction.ADD_DUCHIES, - count: 1, - }, - { - action: GameLogAction.ADD_ESTATES, - count: 1, - }, - ], - [GroupedActionDest.SelectedPlayerIndex]: [], - [GroupedActionDest.AllPlayers]: [], - [GroupedActionDest.AllPlayersExceptCurrent]: [], - [GroupedActionDest.AllPlayersExceptSelected]: [], - }, - }, - Demesne: { - name: 'Demesne', - actions: { - [GroupedActionDest.CurrentPlayerIndex]: [ - { - action: GameLogAction.REMOVE_ACTIONS, - count: 1, - }, - { - action: GameLogAction.ADD_ACTIONS, - count: 2, - }, - { - action: GameLogAction.ADD_BUYS, - count: 2, - }, - ], - [GroupedActionDest.SelectedPlayerIndex]: [], - [GroupedActionDest.AllPlayers]: [], - [GroupedActionDest.AllPlayersExceptCurrent]: [], - [GroupedActionDest.AllPlayersExceptSelected]: [], - }, - }, - BishopEstate: { - name: 'Bishop an Estate', - actions: { - [GroupedActionDest.CurrentPlayerIndex]: [ - { - action: GameLogAction.REMOVE_ACTIONS, - count: 1, - }, - { - action: GameLogAction.REMOVE_ESTATES, - count: 1, - trash: true, - }, - { - action: GameLogAction.ADD_VP_TOKENS, - count: 2, - }, - ], - [GroupedActionDest.SelectedPlayerIndex]: [], - [GroupedActionDest.AllPlayers]: [], - [GroupedActionDest.AllPlayersExceptCurrent]: [], - [GroupedActionDest.AllPlayersExceptSelected]: [], - }, - }, - Monument: { - name: 'Monument', - actions: { - [GroupedActionDest.CurrentPlayerIndex]: [ - { - action: GameLogAction.REMOVE_ACTIONS, - count: 1, - }, - { - action: GameLogAction.ADD_COINS, - count: 2, - }, - { - action: GameLogAction.ADD_VP_TOKENS, - count: 1, - }, - ], - [GroupedActionDest.SelectedPlayerIndex]: [], - [GroupedActionDest.AllPlayers]: [], - [GroupedActionDest.AllPlayersExceptCurrent]: [], - [GroupedActionDest.AllPlayersExceptSelected]: [], - }, - }, - Ducat: { - name: 'Ducat', - actions: { - [GroupedActionDest.CurrentPlayerIndex]: [ - { - action: GameLogAction.ADD_COFFERS, - count: 1, - }, - { - action: GameLogAction.ADD_BUYS, - count: 1, - }, - ], - [GroupedActionDest.SelectedPlayerIndex]: [], - [GroupedActionDest.AllPlayers]: [], - [GroupedActionDest.AllPlayersExceptCurrent]: [], - [GroupedActionDest.AllPlayersExceptSelected]: [], - }, - }, -}; diff --git a/src/index.html b/src/index.html index 1456668..88d5f19 100644 --- a/src/index.html +++ b/src/index.html @@ -2,9 +2,10 @@ - DominionAssistantReactWebpackJest + Dominion Assistant - + +
diff --git a/tmp.patch b/tmp.patch new file mode 100644 index 0000000..7d2decc --- /dev/null +++ b/tmp.patch @@ -0,0 +1,1450 @@ +diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json +index 03295ad..8475311 100644 +--- a/.devcontainer/devcontainer.json ++++ b/.devcontainer/devcontainer.json +@@ -38,7 +38,7 @@ + "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. +- "postCreateCommand": "./setup-nvm.sh && yarn install && ./install-globals.sh && ./install-copilot-cli.sh && ./setup-shell-config.sh && export $(grep -v '^#' ${DOTENV_CONFIG_PATH} | xargs)" ++ "postCreateCommand": "export $(grep -v '^#' /workspaces/dominion-assistant/.env | xargs) && ./fontawesome-npmrc.sh && ./setup-nvm.sh && yarn install && ./install-globals.sh && ./install-copilot-cli.sh && ./setup-shell-config.sh && export $(grep -v '^#' ${DOTENV_CONFIG_PATH} | xargs)" + + // Configure tool-specific properties. + // "customizations": {}, +diff --git a/.env.example b/.env.example +index 7e3f97f..2291094 100644 +--- a/.env.example ++++ b/.env.example +@@ -1 +1,2 @@ +-CODACY_PROJECT_TOKEN=your-codacy-project-token +\ No newline at end of file ++CODACY_PROJECT_TOKEN=your-codacy-project-token ++FONTAWESOME_KEY=your-fontawesome-key +\ No newline at end of file +diff --git a/.gitignore b/.gitignore +index f41e43d..b5ae9f0 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -1,6 +1,7 @@ + # See http://help.github.com/ignore-files/ for more about ignoring files. + + /.env ++/.npmrc + + # game storage + /game-storage/ +diff --git a/README.md b/README.md +index fcf3a30..97a8989 100644 +--- a/README.md ++++ b/README.md +@@ -106,6 +106,7 @@ A popup should offer to open the application in a browser. + + - Copy the .env.example in the project root to .env + - Fill in the CODACY_PROJECT_TOKEN from https://app.codacy.com/gh/Digital-Defiance/DominionAssistant/settings/coverage ++- Fill in the FONTAWESOME_KEY from https://fontawesome.com/kits/1111eb8cf6/package + - Save the .env file + + 4. When prompted, click "Reopen in Container" or use the command palette (F1) and select "Remote-Containers: Reopen in Container". +@@ -200,6 +201,19 @@ Join our community of developers. + + ## Changelog + ++### Thu Nov 07 02:06:00 2024 ++ ++- Version 0.10.3 ++ - Improve FontAwesome icons for goruped actions ++ - Add icon to the log for grouped actions ++ ++### Thu Nov 07 00:40:00 2024 ++ ++- Version 0.10.2 ++ - Add FontAwesome ++ - Add custom icon support to recipes/common actions ++ - Add icons to Common Actions/recipes tab ++ + ### Wed Nov 06 23:09:00 2024 + + - Version 0.10.1 +diff --git a/fontawesome-npmrc.sh b/fontawesome-npmrc.sh +new file mode 100755 +index 0000000..f876534 +--- /dev/null ++++ b/fontawesome-npmrc.sh +@@ -0,0 +1,5 @@ ++#!/bin/bash ++ ++echo "@fortawesome:registry=https://npm.fontawesome.com/ ++@awesome.me:registry=https://npm.fontawesome.com/ ++//npm.fontawesome.com/:_authToken=$FONTAWESOME_KEY" > .npmrc +\ No newline at end of file +diff --git a/package.json b/package.json +index 06557c6..1209904 100644 +--- a/package.json ++++ b/package.json +@@ -19,8 +19,13 @@ + }, + "private": true, + "dependencies": { ++ "@awesome.me/kit-1111eb8cf6": "^1.0.4", + "@emotion/react": "^11.13.3", + "@emotion/styled": "^11.13.0", ++ "@fortawesome/fontawesome-common-types": "^6.6.0", ++ "@fortawesome/fontawesome-svg-core": "^6.6.0", ++ "@fortawesome/pro-solid-svg-icons": "^6.6.0", ++ "@fortawesome/react-fontawesome": "^0.2.2", + "@mui/icons-material": "^6.1.3", + "@mui/material": "^6.1.3", + "chart.js": "^4.4.5", +diff --git a/src/assets/images/favicon.ico b/src/assets/images/favicon.ico +new file mode 100644 +index 0000000..e96938c +Binary files /dev/null and b/src/assets/images/favicon.ico differ +diff --git a/src/components/GameInterface.tsx b/src/components/GameInterface.tsx +index b6159f9..a933344 100644 +--- a/src/components/GameInterface.tsx ++++ b/src/components/GameInterface.tsx +@@ -28,7 +28,7 @@ import { IGame } from '@/game/interfaces/game'; + import { deepClone } from '@/game/utils'; + import TurnAdjustmentsSummary from '@/components/TurnAdjustments'; + import FloatingCounter from '@/components/FloatingCounter'; +-import { RecipesComponent } from '@/components/Recipes'; ++import { RecipesList } from '@/components/RecipeList'; + import ForwardRefBox from './ForwardRefBox'; + + interface GameInterfaceProps { +@@ -168,7 +168,7 @@ const GameInterface: FC = ({ nextTurn, endGame, undoLastActi + )} + {tabValue === 1 && } + {tabValue === 2 && } +- {tabValue === 3 && } ++ {tabValue === 3 && } + + + +diff --git a/src/components/GameLogEntry.tsx b/src/components/GameLogEntry.tsx +index f236545..b34fb77 100644 +--- a/src/components/GameLogEntry.tsx ++++ b/src/components/GameLogEntry.tsx +@@ -28,6 +28,7 @@ import { AdjustmentActions } from '@/game/constants'; + import ColoredPlayerName from '@/components/ColoredPlayerName'; + import { getAdjustedDurationFromCacheByIndex } from '@/game/dominion-lib-time'; + import '@/styles.scss'; ++import { Recipes } from './Recipes'; + + interface GameLogEntryProps { + logIndex: number; +@@ -186,6 +187,16 @@ const GameLogEntry: FC = ({ logIndex, entry, onOpenTurnAdjust + {relevantPlayer !== undefined && !isNotTriggeredByPlayer && ( + + )} ++ {entry.action === GameLogAction.GROUPED_ACTION && ++ entry.actionKey && ++ Recipes[entry.actionKey] && ( ++ ++ {Recipes[entry.actionKey].icon} ++ ++ )} + + {actionText} + +diff --git a/src/components/RecipeCard.tsx b/src/components/RecipeCard.tsx +index 63e1c8e..3558e77 100644 +--- a/src/components/RecipeCard.tsx ++++ b/src/components/RecipeCard.tsx +@@ -5,13 +5,14 @@ import { + prepareGroupedActionTriggers, + } from '@/game/dominion-lib-log'; + import { IGroupedAction } from '@/game/interfaces/grouped-action'; +-import { Recipes } from '@/game/recipes'; +-import { Box, Link } from '@mui/material'; ++import { RecipeKey, Recipes } from '@/components/Recipes'; ++import { Box, Link, Typography } from '@mui/material'; ++import PlayArrowIcon from '@mui/icons-material/PlayArrow'; + import { useGameContext } from '@/components/GameContext'; + import { useAlert } from '@/components/AlertContext'; + + interface RecipeCardProps { +- recipeKey: string; ++ recipeKey: RecipeKey; + recipe: IGroupedAction; + } + +@@ -30,7 +31,8 @@ export const RecipeCard: FC = ({ recipeKey, recipe }) => { + groupedAction, + new Date(), + applyGroupedActionSubAction, +- prepareGroupedActionTriggers ++ prepareGroupedActionTriggers, ++ recipeKey + ); + setGameState(newGame); + } catch (error) { +@@ -43,9 +45,21 @@ export const RecipeCard: FC = ({ recipeKey, recipe }) => { + }; + + return ( +- +- handleRecipe(event, recipeKey)}> +- {recipe.name} ++ ++ handleRecipe(event, recipeKey)} ++ sx={{ display: 'flex', alignItems: 'center' }} ++ > ++ ++ {recipe.icon ?? } ++ ++ ++ {recipe.name} ++ + + + ); +diff --git a/src/components/RecipeList.tsx b/src/components/RecipeList.tsx +new file mode 100644 +index 0000000..a90ff96 +--- /dev/null ++++ b/src/components/RecipeList.tsx +@@ -0,0 +1,59 @@ ++import React, { CSSProperties, FC, memo, RefObject, useEffect, useState } from 'react'; ++import TabTitle from '@/components/TabTitle'; ++import { Recipes } from '@/components/Recipes'; ++import { FixedSizeList } from 'react-window'; ++import { RecipeCard } from '@/components/RecipeCard'; ++ ++interface RecipesProps { ++ viewBoxRef: RefObject; ++} ++ ++export const RecipesList: FC = ({ viewBoxRef }) => { ++ const [listHeight, setListHeight] = useState( ++ viewBoxRef.current?.getBoundingClientRect().height ?? 0 ++ ); ++ const [listWidth, setListWidth] = useState( ++ viewBoxRef.current?.getBoundingClientRect().width ?? 0 ++ ); ++ const MemoizedRecipeCard = memo(RecipeCard); ++ ++ useEffect(() => { ++ const handleResize = () => { ++ const viewBoxBound = viewBoxRef.current?.getBoundingClientRect(); ++ const viewBoxHeight = viewBoxBound?.height ?? 0; ++ const viewBoxWidth = viewBoxBound?.width ?? 0; ++ setListHeight(viewBoxHeight); ++ setListWidth(viewBoxWidth); ++ }; ++ ++ window.addEventListener('resize', handleResize); ++ handleResize(); // Set initial height ++ ++ return () => { ++ window.removeEventListener('resize', handleResize); ++ }; ++ }, [viewBoxRef]); ++ ++ const recipesArray = Object.entries(Recipes); ++ ++ const Row = ({ index, style }: { index: number; style: CSSProperties }) => ( ++
++ ++
++ ); ++ ++ return ( ++ <> ++ Common Actions ++ ++ {Row} ++ ++ ++ ); ++}; +diff --git a/src/components/Recipes.tsx b/src/components/Recipes.tsx +index 6edd1d6..b9e5a59 100644 +--- a/src/components/Recipes.tsx ++++ b/src/components/Recipes.tsx +@@ -1,59 +1,451 @@ +-import React, { CSSProperties, FC, memo, RefObject, useEffect, useState } from 'react'; +-import TabTitle from '@/components/TabTitle'; +-import { Recipes } from '@/game/recipes'; +-import { FixedSizeList } from 'react-window'; +-import { RecipeCard } from '@/components/RecipeCard'; ++import React from 'react'; ++import { GameLogAction } from '@/game/enumerations/game-log-action'; ++import { IGroupedAction } from '@/game/interfaces/grouped-action'; ++import { GroupedActionDest } from '@/game/enumerations/grouped-action-dest'; ++import { GroupedActionTrigger } from '@/game/enumerations/grouped-action-trigger'; ++import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; ++import { ++ faStore, ++ faFlask, ++ faHome, ++ faHammer, ++ faGlassCheers, ++ faShoppingCart, ++ faMonument, ++ faCoins, ++ faUserFriends, ++ faShoppingBag, ++ faTasks, ++ faCrown, ++ faHandshake, ++ faLandmark, ++ faChessBishop, ++} from '@fortawesome/pro-solid-svg-icons'; + +-interface RecipesProps { +- viewBoxRef: RefObject; +-} ++export type RecipeKey = keyof typeof Recipes; + +-export const RecipesComponent: FC = ({ viewBoxRef }) => { +- const [listHeight, setListHeight] = useState( +- viewBoxRef.current?.getBoundingClientRect().height ?? 0 +- ); +- const [listWidth, setListWidth] = useState( +- viewBoxRef.current?.getBoundingClientRect().width ?? 0 +- ); +- const MemoizedRecipeCard = memo(RecipeCard); +- +- useEffect(() => { +- const handleResize = () => { +- const viewBoxBound = viewBoxRef.current?.getBoundingClientRect(); +- const viewBoxHeight = viewBoxBound?.height ?? 0; +- const viewBoxWidth = viewBoxBound?.width ?? 0; +- setListHeight(viewBoxHeight); +- setListWidth(viewBoxWidth); +- }; +- +- window.addEventListener('resize', handleResize); +- handleResize(); // Set initial height +- +- return () => { +- window.removeEventListener('resize', handleResize); +- }; +- }, [viewBoxRef]); +- +- const recipesArray = Object.entries(Recipes); +- +- const Row = ({ index, style }: { index: number; style: CSSProperties }) => ( +-
+- +-
+- ); +- +- return ( +- <> +- Common Actions +- +- {Row} +- +- +- ); ++export const Recipes: Record = { ++ OneCardOneAction: { ++ name: 'One Card, One Action', ++ actions: { ++ [GroupedActionDest.CurrentPlayerIndex]: [ ++ { ++ action: GameLogAction.REMOVE_ACTIONS, ++ count: 1, ++ }, ++ { ++ action: GameLogAction.ADD_CARDS, ++ count: 1, ++ }, ++ { ++ action: GameLogAction.ADD_ACTIONS, ++ count: 1, ++ }, ++ ], ++ [GroupedActionDest.SelectedPlayerIndex]: [], ++ [GroupedActionDest.AllPlayers]: [], ++ [GroupedActionDest.AllPlayersExceptCurrent]: [], ++ [GroupedActionDest.AllPlayersExceptSelected]: [], ++ }, ++ }, ++ OneCardTwoActions: { ++ name: 'One Card, Two Actions', ++ icon: , ++ actions: { ++ [GroupedActionDest.CurrentPlayerIndex]: [ ++ { ++ action: GameLogAction.REMOVE_ACTIONS, ++ count: 1, ++ }, ++ { ++ action: GameLogAction.ADD_CARDS, ++ count: 1, ++ }, ++ { ++ action: GameLogAction.ADD_ACTIONS, ++ count: 2, ++ }, ++ ], ++ [GroupedActionDest.SelectedPlayerIndex]: [], ++ [GroupedActionDest.AllPlayers]: [], ++ [GroupedActionDest.AllPlayersExceptCurrent]: [], ++ [GroupedActionDest.AllPlayersExceptSelected]: [], ++ }, ++ }, ++ OneCardTwoActionsOneBuy: { ++ name: 'One Card, Two Actions, One Buy', ++ icon: , ++ actions: { ++ [GroupedActionDest.CurrentPlayerIndex]: [ ++ { ++ action: GameLogAction.REMOVE_ACTIONS, ++ count: 1, ++ }, ++ { ++ action: GameLogAction.ADD_CARDS, ++ count: 1, ++ }, ++ { ++ action: GameLogAction.ADD_ACTIONS, ++ count: 2, ++ }, ++ { ++ action: GameLogAction.ADD_BUYS, ++ count: 1, ++ }, ++ ], ++ [GroupedActionDest.SelectedPlayerIndex]: [], ++ [GroupedActionDest.AllPlayers]: [], ++ [GroupedActionDest.AllPlayersExceptCurrent]: [], ++ [GroupedActionDest.AllPlayersExceptSelected]: [], ++ }, ++ }, ++ OneCardThreeActions: { ++ name: 'One Card, Three Actions', ++ icon: , ++ actions: { ++ [GroupedActionDest.CurrentPlayerIndex]: [ ++ { ++ action: GameLogAction.REMOVE_ACTIONS, ++ count: 1, ++ }, ++ { ++ action: GameLogAction.ADD_CARDS, ++ count: 1, ++ }, ++ { ++ action: GameLogAction.ADD_ACTIONS, ++ count: 3, ++ }, ++ ], ++ [GroupedActionDest.SelectedPlayerIndex]: [], ++ [GroupedActionDest.AllPlayers]: [], ++ [GroupedActionDest.AllPlayersExceptCurrent]: [], ++ [GroupedActionDest.AllPlayersExceptSelected]: [], ++ }, ++ }, ++ Festival: { ++ name: 'Festival', ++ icon: , ++ actions: { ++ [GroupedActionDest.CurrentPlayerIndex]: [ ++ { ++ action: GameLogAction.REMOVE_ACTIONS, ++ count: 1, ++ }, ++ { ++ action: GameLogAction.ADD_ACTIONS, ++ count: 2, ++ }, ++ { ++ action: GameLogAction.ADD_BUYS, ++ count: 1, ++ }, ++ { ++ action: GameLogAction.ADD_COINS, ++ count: 2, ++ }, ++ ], ++ [GroupedActionDest.SelectedPlayerIndex]: [], ++ [GroupedActionDest.AllPlayers]: [], ++ [GroupedActionDest.AllPlayersExceptCurrent]: [], ++ [GroupedActionDest.AllPlayersExceptSelected]: [], ++ }, ++ }, ++ Laboratory: { ++ name: 'Laboratory', ++ icon: , ++ actions: { ++ [GroupedActionDest.CurrentPlayerIndex]: [ ++ { ++ action: GameLogAction.REMOVE_ACTIONS, ++ count: 1, ++ }, ++ { ++ action: GameLogAction.ADD_CARDS, ++ count: 2, ++ }, ++ { ++ action: GameLogAction.ADD_ACTIONS, ++ count: 1, ++ }, ++ ], ++ [GroupedActionDest.SelectedPlayerIndex]: [], ++ [GroupedActionDest.AllPlayers]: [], ++ [GroupedActionDest.AllPlayersExceptCurrent]: [], ++ [GroupedActionDest.AllPlayersExceptSelected]: [], ++ }, ++ }, ++ Smithy: { ++ name: 'Smithy', ++ icon: , ++ actions: { ++ [GroupedActionDest.CurrentPlayerIndex]: [ ++ { ++ action: GameLogAction.REMOVE_ACTIONS, ++ count: 1, ++ }, ++ { ++ action: GameLogAction.ADD_CARDS, ++ count: 3, ++ }, ++ ], ++ [GroupedActionDest.SelectedPlayerIndex]: [], ++ [GroupedActionDest.AllPlayers]: [], ++ [GroupedActionDest.AllPlayersExceptCurrent]: [], ++ [GroupedActionDest.AllPlayersExceptSelected]: [], ++ }, ++ }, ++ CouncilRoom: { ++ name: 'Council Room', ++ icon: , ++ actions: { ++ [GroupedActionDest.CurrentPlayerIndex]: [ ++ { ++ action: GameLogAction.REMOVE_ACTIONS, ++ count: 1, ++ }, ++ { ++ action: GameLogAction.ADD_CARDS, ++ count: 4, ++ }, ++ { ++ action: GameLogAction.ADD_BUYS, ++ count: 1, ++ }, ++ ], ++ [GroupedActionDest.SelectedPlayerIndex]: [], ++ [GroupedActionDest.AllPlayers]: [], ++ [GroupedActionDest.AllPlayersExceptCurrent]: [ ++ { ++ action: GameLogAction.ADD_NEXT_TURN_CARDS, ++ count: 1, ++ }, ++ ], ++ [GroupedActionDest.AllPlayersExceptSelected]: [], ++ }, ++ triggers: { ++ [GroupedActionTrigger.AfterNextTurnBegins]: { ++ [GroupedActionDest.AllPlayersExceptCurrent]: [ ++ { ++ action: GameLogAction.REMOVE_NEXT_TURN_CARDS, ++ count: 1, ++ }, ++ ], ++ [GroupedActionDest.AllPlayersExceptSelected]: [], ++ [GroupedActionDest.CurrentPlayerIndex]: [], ++ [GroupedActionDest.SelectedPlayerIndex]: [], ++ [GroupedActionDest.AllPlayers]: [], ++ }, ++ }, ++ }, ++ Market: { ++ name: 'Market', ++ icon: , ++ actions: { ++ [GroupedActionDest.CurrentPlayerIndex]: [ ++ { ++ action: GameLogAction.REMOVE_ACTIONS, ++ count: 1, ++ }, ++ { ++ action: GameLogAction.ADD_CARDS, ++ count: 1, ++ }, ++ { ++ action: GameLogAction.ADD_ACTIONS, ++ count: 1, ++ }, ++ { ++ action: GameLogAction.ADD_BUYS, ++ count: 1, ++ }, ++ { ++ action: GameLogAction.ADD_COINS, ++ count: 1, ++ }, ++ ], ++ [GroupedActionDest.SelectedPlayerIndex]: [], ++ [GroupedActionDest.AllPlayers]: [], ++ [GroupedActionDest.AllPlayersExceptCurrent]: [], ++ [GroupedActionDest.AllPlayersExceptSelected]: [], ++ }, ++ }, ++ GrandMarket: { ++ name: 'Grand Market', ++ icon: , ++ actions: { ++ [GroupedActionDest.CurrentPlayerIndex]: [ ++ { ++ action: GameLogAction.REMOVE_ACTIONS, ++ count: 1, ++ }, ++ { ++ action: GameLogAction.ADD_CARDS, ++ count: 1, ++ }, ++ { ++ action: GameLogAction.ADD_ACTIONS, ++ count: 1, ++ }, ++ { ++ action: GameLogAction.ADD_BUYS, ++ count: 1, ++ }, ++ { ++ action: GameLogAction.ADD_COINS, ++ count: 2, ++ }, ++ ], ++ [GroupedActionDest.SelectedPlayerIndex]: [], ++ [GroupedActionDest.AllPlayers]: [], ++ [GroupedActionDest.AllPlayersExceptCurrent]: [], ++ [GroupedActionDest.AllPlayersExceptSelected]: [], ++ }, ++ }, ++ Dominate: { ++ name: 'Dominate', ++ icon: , ++ actions: { ++ [GroupedActionDest.CurrentPlayerIndex]: [ ++ { ++ action: GameLogAction.REMOVE_BUYS, ++ count: 1, ++ }, ++ { ++ action: GameLogAction.ADD_PROVINCES, ++ count: 1, ++ }, ++ { ++ action: GameLogAction.ADD_VP_TOKENS, ++ count: 9, ++ }, ++ ], ++ [GroupedActionDest.SelectedPlayerIndex]: [], ++ [GroupedActionDest.AllPlayers]: [], ++ [GroupedActionDest.AllPlayersExceptCurrent]: [], ++ [GroupedActionDest.AllPlayersExceptSelected]: [], ++ }, ++ }, ++ Alliance: { ++ name: 'Alliance', ++ icon: , ++ actions: { ++ [GroupedActionDest.CurrentPlayerIndex]: [ ++ { ++ action: GameLogAction.REMOVE_BUYS, ++ count: 1, ++ }, ++ { ++ action: GameLogAction.ADD_PROVINCES, ++ count: 1, ++ }, ++ { ++ action: GameLogAction.ADD_DUCHIES, ++ count: 1, ++ }, ++ { ++ action: GameLogAction.ADD_ESTATES, ++ count: 1, ++ }, ++ ], ++ [GroupedActionDest.SelectedPlayerIndex]: [], ++ [GroupedActionDest.AllPlayers]: [], ++ [GroupedActionDest.AllPlayersExceptCurrent]: [], ++ [GroupedActionDest.AllPlayersExceptSelected]: [], ++ }, ++ }, ++ Demesne: { ++ name: 'Demesne', ++ icon: , ++ actions: { ++ [GroupedActionDest.CurrentPlayerIndex]: [ ++ { ++ action: GameLogAction.REMOVE_ACTIONS, ++ count: 1, ++ }, ++ { ++ action: GameLogAction.ADD_ACTIONS, ++ count: 2, ++ }, ++ { ++ action: GameLogAction.ADD_BUYS, ++ count: 2, ++ }, ++ ], ++ [GroupedActionDest.SelectedPlayerIndex]: [], ++ [GroupedActionDest.AllPlayers]: [], ++ [GroupedActionDest.AllPlayersExceptCurrent]: [], ++ [GroupedActionDest.AllPlayersExceptSelected]: [], ++ }, ++ }, ++ BishopEstate: { ++ name: 'Bishop an Estate', ++ icon: , ++ actions: { ++ [GroupedActionDest.CurrentPlayerIndex]: [ ++ { ++ action: GameLogAction.REMOVE_ACTIONS, ++ count: 1, ++ }, ++ { ++ action: GameLogAction.REMOVE_ESTATES, ++ count: 1, ++ trash: true, ++ }, ++ { ++ action: GameLogAction.ADD_VP_TOKENS, ++ count: 2, ++ }, ++ ], ++ [GroupedActionDest.SelectedPlayerIndex]: [], ++ [GroupedActionDest.AllPlayers]: [], ++ [GroupedActionDest.AllPlayersExceptCurrent]: [], ++ [GroupedActionDest.AllPlayersExceptSelected]: [], ++ }, ++ }, ++ Monument: { ++ name: 'Monument', ++ icon: , ++ actions: { ++ [GroupedActionDest.CurrentPlayerIndex]: [ ++ { ++ action: GameLogAction.REMOVE_ACTIONS, ++ count: 1, ++ }, ++ { ++ action: GameLogAction.ADD_COINS, ++ count: 2, ++ }, ++ { ++ action: GameLogAction.ADD_VP_TOKENS, ++ count: 1, ++ }, ++ ], ++ [GroupedActionDest.SelectedPlayerIndex]: [], ++ [GroupedActionDest.AllPlayers]: [], ++ [GroupedActionDest.AllPlayersExceptCurrent]: [], ++ [GroupedActionDest.AllPlayersExceptSelected]: [], ++ }, ++ }, ++ Ducat: { ++ name: 'Ducat', ++ icon: , ++ actions: { ++ [GroupedActionDest.CurrentPlayerIndex]: [ ++ { ++ action: GameLogAction.ADD_COFFERS, ++ count: 1, ++ }, ++ { ++ action: GameLogAction.ADD_BUYS, ++ count: 1, ++ }, ++ ], ++ [GroupedActionDest.SelectedPlayerIndex]: [], ++ [GroupedActionDest.AllPlayers]: [], ++ [GroupedActionDest.AllPlayersExceptCurrent]: [], ++ [GroupedActionDest.AllPlayersExceptSelected]: [], ++ }, ++ }, + }; +diff --git a/src/game/__tests__/dominion-lib-log-applyGroupedAction.spec.ts b/src/game/__tests__/dominion-lib-log-applyGroupedAction.spec.ts +index 1b9bc55..eb85a27 100644 +--- a/src/game/__tests__/dominion-lib-log-applyGroupedAction.spec.ts ++++ b/src/game/__tests__/dominion-lib-log-applyGroupedAction.spec.ts +@@ -10,6 +10,7 @@ import { + getGameStartTime, + prepareGroupedActionTriggers, + } from '@/game/dominion-lib-log'; ++import { RecipeKey } from '@/components/Recipes'; + + function createGroupedActionBase(): IGroupedAction { + return { +@@ -608,6 +609,37 @@ describe('applyGroupedAction', () => { + expect(prepareGroupedActionTriggersMock).toHaveBeenCalledTimes(1); + expect(updatedGame.pendingGroupedActions).toStrictEqual([]); + }); ++ ++ it('should throw an error for an invalid grouped action key', () => { ++ const invalidGroupedActionKey = 'InvalidKey' as RecipeKey; ++ expect(() => { ++ applyGroupedAction( ++ mockGame, ++ groupedAction, ++ actionDate, ++ applyGroupedActionSubActionMock, ++ prepareGroupedActionTriggersMock, ++ invalidGroupedActionKey ++ ); ++ }).toThrow(`Invalid recipe key: ${invalidGroupedActionKey}`); ++ }); ++ ++ it('should create a log with the actionKey if provided and valid', () => { ++ groupedAction.actions[GroupedActionDest.CurrentPlayerIndex] = [ ++ { action: GameLogAction.ADD_ACTIONS, count: 1 }, ++ ]; ++ const updatedGame = applyGroupedAction( ++ mockGame, ++ groupedAction, ++ actionDate, ++ applyGroupedActionSubActionMock, ++ prepareGroupedActionTriggersMock, ++ 'OneCardOneAction' as RecipeKey ++ ); ++ expect(updatedGame.log.length).toBe(3); ++ expect(updatedGame.log[1].action).toBe(GameLogAction.GROUPED_ACTION); ++ expect(updatedGame.log[1].actionKey).toBe('OneCardOneAction'); ++ }); + }); + + describe('applyGroupedAction with different current and selected players', () => { +diff --git a/src/game/__tests__/dominion-lib-undo-reconstructGameState.spec.ts b/src/game/__tests__/dominion-lib-undo-reconstructGameState.spec.ts +index 75c5e48..d28c3e0 100644 +--- a/src/game/__tests__/dominion-lib-undo-reconstructGameState.spec.ts ++++ b/src/game/__tests__/dominion-lib-undo-reconstructGameState.spec.ts +@@ -21,6 +21,7 @@ import { + } from '@/game/dominion-lib-log'; + import { createMockGame } from '@/__fixtures__/dominion-lib-fixtures'; + import { GroupedActionDest } from '@/game/enumerations/grouped-action-dest'; ++import { RecipeKey } from '@/components/Recipes'; + + describe('reconstructGameState', () => { + let baseGame: IGame; +diff --git a/src/game/constants.ts b/src/game/constants.ts +index 3307546..c8697f5 100644 +--- a/src/game/constants.ts ++++ b/src/game/constants.ts +@@ -14,7 +14,7 @@ import { IRisingSunFeatures } from '@/game/interfaces/set-features/rising-sun'; + import { IExpansionsEnabled } from '@/game/interfaces/expansions-enabled'; + import { calculateInitialSunTokens } from '@/game/interfaces/set-mats/prophecy'; + +-export const VERSION_NUMBER = '0.10.1'; ++export const VERSION_NUMBER = '0.10.3'; + export const LAST_COMPATIBLE_SAVE_VERSION = '0.10.0'; + + export const MIN_PLAYERS = 2; +diff --git a/src/game/dominion-lib-log.ts b/src/game/dominion-lib-log.ts +index 5cf9d0a..7cac84b 100644 +--- a/src/game/dominion-lib-log.ts ++++ b/src/game/dominion-lib-log.ts +@@ -37,6 +37,7 @@ import { GroupedActionDest } from '@/game/enumerations/grouped-action-dest'; + import { InvalidPlayerIndexError } from '@/game/errors/invalid-player-index'; + import { InvalidActionError } from '@/game/errors/invalid-action'; + import { GroupedActionTrigger } from '@/game/enumerations/grouped-action-trigger'; ++import { RecipeKey, Recipes } from '@/components/Recipes'; + + /** + * Map a victory field and subfield to a game log action. +@@ -826,8 +827,11 @@ export function getGroupedActionTargetPlayers(game: IGame, dest: GroupedActionDe + /** + * Apply a grouped action to the game. + * @param game - The game state +- * @param playerIndex - The index of the player performing the action ++ * @param groupedActionKey - The key of the grouped action to apply + * @param groupedAction - The grouped action to apply ++ * @param actionDate - The date of the action ++ * @param applyGroupedActionSubAction - A function to apply sub-actions to the game ++ * @param prepareGroupedActionTriggers - A function to prepare triggers for the grouped action + * @returns The updated game state + */ + export function applyGroupedAction( +@@ -845,9 +849,13 @@ export function applyGroupedAction( + game: IGame, + groupedAction: IGroupedAction, + groupedActionId: string +- ) => IGame ++ ) => IGame, ++ groupedActionKey?: RecipeKey + ): IGame { + try { ++ if (groupedActionKey && Recipes[groupedActionKey] === undefined) { ++ throw new Error(`Invalid recipe key: ${groupedActionKey}`); ++ } + let updatedGame = deepClone(game); + const groupedActionId = uuidv4(); + // Create a log entry for the grouped action +@@ -859,6 +867,7 @@ export function applyGroupedAction( + currentPlayerIndex: updatedGame.currentPlayerIndex, + turn: updatedGame.currentTurn, + actionName: groupedAction.name, ++ actionKey: groupedActionKey, + }; + updatedGame.log.push(groupedActionLog); + const { timeCache, turnStatisticsCache } = updateCachesForEntry(updatedGame, groupedActionLog); +diff --git a/src/game/interfaces/grouped-action.ts b/src/game/interfaces/grouped-action.ts +index 0cd1981..933a62d 100644 +--- a/src/game/interfaces/grouped-action.ts ++++ b/src/game/interfaces/grouped-action.ts +@@ -1,9 +1,11 @@ + import { ILogEntry } from '@/game/interfaces/log-entry'; + import { GroupedActionDest } from '@/game/enumerations/grouped-action-dest'; + import { GroupedActionTrigger } from '@/game/enumerations/grouped-action-trigger'; ++import { ReactElement } from 'react'; + + export interface IGroupedAction { + name: string; ++ icon?: ReactElement; + actions: Record>>; + triggers?: Record>>>; + } +diff --git a/src/game/interfaces/log-entry-raw.ts b/src/game/interfaces/log-entry-raw.ts +index 4e672f8..6186a84 100644 +--- a/src/game/interfaces/log-entry-raw.ts ++++ b/src/game/interfaces/log-entry-raw.ts +@@ -1,3 +1,4 @@ ++import { RecipeKey } from '@/components/Recipes'; + import { IPlayerGameTurnDetails } from '@/game/interfaces/player-game-turn-details'; + + export interface ILogEntryRaw { +@@ -56,4 +57,6 @@ export interface ILogEntryRaw { + * Name of the action taken, for grouped actions + */ + actionName?: string; ++ /** Key for the grouped action */ ++ actionKey?: RecipeKey; + } +diff --git a/src/game/interfaces/log-entry.ts b/src/game/interfaces/log-entry.ts +index 40493bf..8b83539 100644 +--- a/src/game/interfaces/log-entry.ts ++++ b/src/game/interfaces/log-entry.ts +@@ -1,3 +1,4 @@ ++import { RecipeKey } from '@/components/Recipes'; + import { GameLogAction } from '@/game/enumerations/game-log-action'; + import { IPlayerGameTurnDetails } from '@/game/interfaces/player-game-turn-details'; + +@@ -53,7 +54,9 @@ export interface ILogEntry { + */ + playerTurnDetails?: IPlayerGameTurnDetails[]; + /** +- * Name of the action taken, for grouped actions ++ * Name of the grouped action taken, for grouped actions + */ + actionName?: string; ++ /** Key for the grouped action */ ++ actionKey?: RecipeKey; + } +diff --git a/src/game/recipes.ts b/src/game/recipes.ts +deleted file mode 100644 +index 3353de7..0000000 +--- a/src/game/recipes.ts ++++ /dev/null +@@ -1,415 +0,0 @@ +-import { GameLogAction } from '@/game/enumerations/game-log-action'; +-import { IGroupedAction } from '@/game/interfaces/grouped-action'; +-import { GroupedActionDest } from '@/game/enumerations/grouped-action-dest'; +-import { GroupedActionTrigger } from '@/game/enumerations/grouped-action-trigger'; +- +-export const Recipes: Record = { +- OneCardOneAction: { +- name: 'One Card, One Action', +- actions: { +- [GroupedActionDest.CurrentPlayerIndex]: [ +- { +- action: GameLogAction.REMOVE_ACTIONS, +- count: 1, +- }, +- { +- action: GameLogAction.ADD_CARDS, +- count: 1, +- }, +- { +- action: GameLogAction.ADD_ACTIONS, +- count: 1, +- }, +- ], +- [GroupedActionDest.SelectedPlayerIndex]: [], +- [GroupedActionDest.AllPlayers]: [], +- [GroupedActionDest.AllPlayersExceptCurrent]: [], +- [GroupedActionDest.AllPlayersExceptSelected]: [], +- }, +- }, +- OneCardTwoActions: { +- name: 'One Card, Two Actions', +- actions: { +- [GroupedActionDest.CurrentPlayerIndex]: [ +- { +- action: GameLogAction.REMOVE_ACTIONS, +- count: 1, +- }, +- { +- action: GameLogAction.ADD_CARDS, +- count: 1, +- }, +- { +- action: GameLogAction.ADD_ACTIONS, +- count: 2, +- }, +- ], +- [GroupedActionDest.SelectedPlayerIndex]: [], +- [GroupedActionDest.AllPlayers]: [], +- [GroupedActionDest.AllPlayersExceptCurrent]: [], +- [GroupedActionDest.AllPlayersExceptSelected]: [], +- }, +- }, +- OneCardTwoActionsOneBuy: { +- name: 'One Card, Two Actions, One Buy', +- actions: { +- [GroupedActionDest.CurrentPlayerIndex]: [ +- { +- action: GameLogAction.REMOVE_ACTIONS, +- count: 1, +- }, +- { +- action: GameLogAction.ADD_CARDS, +- count: 1, +- }, +- { +- action: GameLogAction.ADD_ACTIONS, +- count: 2, +- }, +- { +- action: GameLogAction.ADD_BUYS, +- count: 1, +- }, +- ], +- [GroupedActionDest.SelectedPlayerIndex]: [], +- [GroupedActionDest.AllPlayers]: [], +- [GroupedActionDest.AllPlayersExceptCurrent]: [], +- [GroupedActionDest.AllPlayersExceptSelected]: [], +- }, +- }, +- OneCardThreeActions: { +- name: 'One Card, Three Actions', +- actions: { +- [GroupedActionDest.CurrentPlayerIndex]: [ +- { +- action: GameLogAction.REMOVE_ACTIONS, +- count: 1, +- }, +- { +- action: GameLogAction.ADD_CARDS, +- count: 1, +- }, +- { +- action: GameLogAction.ADD_ACTIONS, +- count: 3, +- }, +- ], +- [GroupedActionDest.SelectedPlayerIndex]: [], +- [GroupedActionDest.AllPlayers]: [], +- [GroupedActionDest.AllPlayersExceptCurrent]: [], +- [GroupedActionDest.AllPlayersExceptSelected]: [], +- }, +- }, +- Festival: { +- name: 'Festival', +- actions: { +- [GroupedActionDest.CurrentPlayerIndex]: [ +- { +- action: GameLogAction.REMOVE_ACTIONS, +- count: 1, +- }, +- { +- action: GameLogAction.ADD_ACTIONS, +- count: 2, +- }, +- { +- action: GameLogAction.ADD_BUYS, +- count: 1, +- }, +- { +- action: GameLogAction.ADD_COINS, +- count: 2, +- }, +- ], +- [GroupedActionDest.SelectedPlayerIndex]: [], +- [GroupedActionDest.AllPlayers]: [], +- [GroupedActionDest.AllPlayersExceptCurrent]: [], +- [GroupedActionDest.AllPlayersExceptSelected]: [], +- }, +- }, +- Laboratory: { +- name: 'Laboratory', +- actions: { +- [GroupedActionDest.CurrentPlayerIndex]: [ +- { +- action: GameLogAction.REMOVE_ACTIONS, +- count: 1, +- }, +- { +- action: GameLogAction.ADD_CARDS, +- count: 2, +- }, +- { +- action: GameLogAction.ADD_ACTIONS, +- count: 1, +- }, +- ], +- [GroupedActionDest.SelectedPlayerIndex]: [], +- [GroupedActionDest.AllPlayers]: [], +- [GroupedActionDest.AllPlayersExceptCurrent]: [], +- [GroupedActionDest.AllPlayersExceptSelected]: [], +- }, +- }, +- Smithy: { +- name: 'Smithy', +- actions: { +- [GroupedActionDest.CurrentPlayerIndex]: [ +- { +- action: GameLogAction.REMOVE_ACTIONS, +- count: 1, +- }, +- { +- action: GameLogAction.ADD_CARDS, +- count: 3, +- }, +- ], +- [GroupedActionDest.SelectedPlayerIndex]: [], +- [GroupedActionDest.AllPlayers]: [], +- [GroupedActionDest.AllPlayersExceptCurrent]: [], +- [GroupedActionDest.AllPlayersExceptSelected]: [], +- }, +- }, +- CouncilRoom: { +- name: 'Council Room', +- actions: { +- [GroupedActionDest.CurrentPlayerIndex]: [ +- { +- action: GameLogAction.REMOVE_ACTIONS, +- count: 1, +- }, +- { +- action: GameLogAction.ADD_CARDS, +- count: 4, +- }, +- { +- action: GameLogAction.ADD_BUYS, +- count: 1, +- }, +- ], +- [GroupedActionDest.SelectedPlayerIndex]: [], +- [GroupedActionDest.AllPlayers]: [], +- [GroupedActionDest.AllPlayersExceptCurrent]: [ +- { +- action: GameLogAction.ADD_NEXT_TURN_CARDS, +- count: 1, +- }, +- ], +- [GroupedActionDest.AllPlayersExceptSelected]: [], +- }, +- triggers: { +- [GroupedActionTrigger.AfterNextTurnBegins]: { +- [GroupedActionDest.AllPlayersExceptCurrent]: [ +- { +- action: GameLogAction.REMOVE_NEXT_TURN_CARDS, +- count: 1, +- }, +- ], +- [GroupedActionDest.AllPlayersExceptSelected]: [], +- [GroupedActionDest.CurrentPlayerIndex]: [], +- [GroupedActionDest.SelectedPlayerIndex]: [], +- [GroupedActionDest.AllPlayers]: [], +- }, +- }, +- }, +- Market: { +- name: 'Market', +- actions: { +- [GroupedActionDest.CurrentPlayerIndex]: [ +- { +- action: GameLogAction.REMOVE_ACTIONS, +- count: 1, +- }, +- { +- action: GameLogAction.ADD_CARDS, +- count: 1, +- }, +- { +- action: GameLogAction.ADD_ACTIONS, +- count: 1, +- }, +- { +- action: GameLogAction.ADD_BUYS, +- count: 1, +- }, +- { +- action: GameLogAction.ADD_COINS, +- count: 1, +- }, +- ], +- [GroupedActionDest.SelectedPlayerIndex]: [], +- [GroupedActionDest.AllPlayers]: [], +- [GroupedActionDest.AllPlayersExceptCurrent]: [], +- [GroupedActionDest.AllPlayersExceptSelected]: [], +- }, +- }, +- GrandMarket: { +- name: 'Grand Market', +- actions: { +- [GroupedActionDest.CurrentPlayerIndex]: [ +- { +- action: GameLogAction.REMOVE_ACTIONS, +- count: 1, +- }, +- { +- action: GameLogAction.ADD_CARDS, +- count: 1, +- }, +- { +- action: GameLogAction.ADD_ACTIONS, +- count: 1, +- }, +- { +- action: GameLogAction.ADD_BUYS, +- count: 1, +- }, +- { +- action: GameLogAction.ADD_COINS, +- count: 2, +- }, +- ], +- [GroupedActionDest.SelectedPlayerIndex]: [], +- [GroupedActionDest.AllPlayers]: [], +- [GroupedActionDest.AllPlayersExceptCurrent]: [], +- [GroupedActionDest.AllPlayersExceptSelected]: [], +- }, +- }, +- Dominate: { +- name: 'Dominate', +- actions: { +- [GroupedActionDest.CurrentPlayerIndex]: [ +- { +- action: GameLogAction.REMOVE_BUYS, +- count: 1, +- }, +- { +- action: GameLogAction.ADD_PROVINCES, +- count: 1, +- }, +- { +- action: GameLogAction.ADD_VP_TOKENS, +- count: 9, +- }, +- ], +- [GroupedActionDest.SelectedPlayerIndex]: [], +- [GroupedActionDest.AllPlayers]: [], +- [GroupedActionDest.AllPlayersExceptCurrent]: [], +- [GroupedActionDest.AllPlayersExceptSelected]: [], +- }, +- }, +- Alliance: { +- name: 'Alliance', +- actions: { +- [GroupedActionDest.CurrentPlayerIndex]: [ +- { +- action: GameLogAction.REMOVE_BUYS, +- count: 1, +- }, +- { +- action: GameLogAction.ADD_PROVINCES, +- count: 1, +- }, +- { +- action: GameLogAction.ADD_DUCHIES, +- count: 1, +- }, +- { +- action: GameLogAction.ADD_ESTATES, +- count: 1, +- }, +- ], +- [GroupedActionDest.SelectedPlayerIndex]: [], +- [GroupedActionDest.AllPlayers]: [], +- [GroupedActionDest.AllPlayersExceptCurrent]: [], +- [GroupedActionDest.AllPlayersExceptSelected]: [], +- }, +- }, +- Demesne: { +- name: 'Demesne', +- actions: { +- [GroupedActionDest.CurrentPlayerIndex]: [ +- { +- action: GameLogAction.REMOVE_ACTIONS, +- count: 1, +- }, +- { +- action: GameLogAction.ADD_ACTIONS, +- count: 2, +- }, +- { +- action: GameLogAction.ADD_BUYS, +- count: 2, +- }, +- ], +- [GroupedActionDest.SelectedPlayerIndex]: [], +- [GroupedActionDest.AllPlayers]: [], +- [GroupedActionDest.AllPlayersExceptCurrent]: [], +- [GroupedActionDest.AllPlayersExceptSelected]: [], +- }, +- }, +- BishopEstate: { +- name: 'Bishop an Estate', +- actions: { +- [GroupedActionDest.CurrentPlayerIndex]: [ +- { +- action: GameLogAction.REMOVE_ACTIONS, +- count: 1, +- }, +- { +- action: GameLogAction.REMOVE_ESTATES, +- count: 1, +- trash: true, +- }, +- { +- action: GameLogAction.ADD_VP_TOKENS, +- count: 2, +- }, +- ], +- [GroupedActionDest.SelectedPlayerIndex]: [], +- [GroupedActionDest.AllPlayers]: [], +- [GroupedActionDest.AllPlayersExceptCurrent]: [], +- [GroupedActionDest.AllPlayersExceptSelected]: [], +- }, +- }, +- Monument: { +- name: 'Monument', +- actions: { +- [GroupedActionDest.CurrentPlayerIndex]: [ +- { +- action: GameLogAction.REMOVE_ACTIONS, +- count: 1, +- }, +- { +- action: GameLogAction.ADD_COINS, +- count: 2, +- }, +- { +- action: GameLogAction.ADD_VP_TOKENS, +- count: 1, +- }, +- ], +- [GroupedActionDest.SelectedPlayerIndex]: [], +- [GroupedActionDest.AllPlayers]: [], +- [GroupedActionDest.AllPlayersExceptCurrent]: [], +- [GroupedActionDest.AllPlayersExceptSelected]: [], +- }, +- }, +- Ducat: { +- name: 'Ducat', +- actions: { +- [GroupedActionDest.CurrentPlayerIndex]: [ +- { +- action: GameLogAction.ADD_COFFERS, +- count: 1, +- }, +- { +- action: GameLogAction.ADD_BUYS, +- count: 1, +- }, +- ], +- [GroupedActionDest.SelectedPlayerIndex]: [], +- [GroupedActionDest.AllPlayers]: [], +- [GroupedActionDest.AllPlayersExceptCurrent]: [], +- [GroupedActionDest.AllPlayersExceptSelected]: [], +- }, +- }, +-}; +diff --git a/src/index.html b/src/index.html +index 1456668..88d5f19 100644 +--- a/src/index.html ++++ b/src/index.html +@@ -2,9 +2,10 @@ + + + +- DominionAssistantReactWebpackJest ++ Dominion Assistant + +- ++ ++ + + +
+diff --git a/yarn.lock b/yarn.lock +index 24a44b8..b890b4a 100644 +--- a/yarn.lock ++++ b/yarn.lock +@@ -15,6 +15,13 @@ + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + ++"@awesome.me/kit-1111eb8cf6@^1.0.4": ++ version "1.0.4" ++ resolved "https://npm.fontawesome.com/@awesome.me/kit-1111eb8cf6/-/1.0.4/kit-1111eb8cf6-1.0.4.tgz#f66dff09cdf43e3dd0b64dc66f8effccc20d15bf" ++ integrity sha512-Tr+cXWNrfvFSIe2BRLrCLUFRgVaXR43KcU9vPslmyG4RtklzRqOXdpT5wUCMv1L0cZXhzmc0se7Z+p0Q9+7YdA== ++ dependencies: ++ "@fortawesome/fontawesome-common-types" "^6.6.0" ++ + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.25.7.tgz#438f2c524071531d643c6f0188e1e28f130cebc7" +@@ -1256,6 +1263,32 @@ + resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-9.0.3.tgz#be817db896b07d1716bc65d9aad1ba587b499826" + integrity sha512-lWrrK4QNlFSU+13PL9jMbMKLJYXDFu3tQfayBsMXX7KL/GiQeqfB1CzHkqD5UHBUtPAuPo6XwGbMFNdVMZObRA== + ++"@fortawesome/fontawesome-common-types@6.6.0", "@fortawesome/fontawesome-common-types@^6.6.0": ++ version "6.6.0" ++ resolved "https://npm.fontawesome.com/@fortawesome/fontawesome-common-types/-/6.6.0/fontawesome-common-types-6.6.0.tgz#31ab07ca6a06358c5de4d295d4711b675006163f" ++ integrity sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw== ++ ++"@fortawesome/fontawesome-svg-core@^6.6.0": ++ version "6.6.0" ++ resolved "https://npm.fontawesome.com/@fortawesome/fontawesome-svg-core/-/6.6.0/fontawesome-svg-core-6.6.0.tgz#2a24c32ef92136e98eae2ff334a27145188295ff" ++ integrity sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg== ++ dependencies: ++ "@fortawesome/fontawesome-common-types" "6.6.0" ++ ++"@fortawesome/pro-solid-svg-icons@^6.6.0": ++ version "6.6.0" ++ resolved "https://npm.fontawesome.com/@fortawesome/pro-solid-svg-icons/-/6.6.0/pro-solid-svg-icons-6.6.0.tgz#e4f99d9b674288c3d1ccbec27b37e008bd190ca1" ++ integrity sha512-IwYuyO+i681arIxOjPUoqRwoHeTqeg6i2s5rY2WbZ1R7PnwavOd1+aPHCJlfU2k8l5LZ+u3hAKlVqu+emQFlZg== ++ dependencies: ++ "@fortawesome/fontawesome-common-types" "6.6.0" ++ ++"@fortawesome/react-fontawesome@^0.2.2": ++ version "0.2.2" ++ resolved "https://npm.fontawesome.com/@fortawesome/react-fontawesome/-/0.2.2/react-fontawesome-0.2.2.tgz#68b058f9132b46c8599875f6a636dad231af78d4" ++ integrity sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g== ++ dependencies: ++ prop-types "^15.8.1" ++ + "@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0": + version "9.3.0" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" diff --git a/yarn.lock b/yarn.lock index 24a44b8..b890b4a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15,6 +15,13 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" +"@awesome.me/kit-1111eb8cf6@^1.0.4": + version "1.0.4" + resolved "https://npm.fontawesome.com/@awesome.me/kit-1111eb8cf6/-/1.0.4/kit-1111eb8cf6-1.0.4.tgz#f66dff09cdf43e3dd0b64dc66f8effccc20d15bf" + integrity sha512-Tr+cXWNrfvFSIe2BRLrCLUFRgVaXR43KcU9vPslmyG4RtklzRqOXdpT5wUCMv1L0cZXhzmc0se7Z+p0Q9+7YdA== + dependencies: + "@fortawesome/fontawesome-common-types" "^6.6.0" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.25.7": version "7.25.7" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.25.7.tgz#438f2c524071531d643c6f0188e1e28f130cebc7" @@ -1256,6 +1263,32 @@ resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-9.0.3.tgz#be817db896b07d1716bc65d9aad1ba587b499826" integrity sha512-lWrrK4QNlFSU+13PL9jMbMKLJYXDFu3tQfayBsMXX7KL/GiQeqfB1CzHkqD5UHBUtPAuPo6XwGbMFNdVMZObRA== +"@fortawesome/fontawesome-common-types@6.6.0", "@fortawesome/fontawesome-common-types@^6.6.0": + version "6.6.0" + resolved "https://npm.fontawesome.com/@fortawesome/fontawesome-common-types/-/6.6.0/fontawesome-common-types-6.6.0.tgz#31ab07ca6a06358c5de4d295d4711b675006163f" + integrity sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw== + +"@fortawesome/fontawesome-svg-core@^6.6.0": + version "6.6.0" + resolved "https://npm.fontawesome.com/@fortawesome/fontawesome-svg-core/-/6.6.0/fontawesome-svg-core-6.6.0.tgz#2a24c32ef92136e98eae2ff334a27145188295ff" + integrity sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg== + dependencies: + "@fortawesome/fontawesome-common-types" "6.6.0" + +"@fortawesome/pro-solid-svg-icons@^6.6.0": + version "6.6.0" + resolved "https://npm.fontawesome.com/@fortawesome/pro-solid-svg-icons/-/6.6.0/pro-solid-svg-icons-6.6.0.tgz#e4f99d9b674288c3d1ccbec27b37e008bd190ca1" + integrity sha512-IwYuyO+i681arIxOjPUoqRwoHeTqeg6i2s5rY2WbZ1R7PnwavOd1+aPHCJlfU2k8l5LZ+u3hAKlVqu+emQFlZg== + dependencies: + "@fortawesome/fontawesome-common-types" "6.6.0" + +"@fortawesome/react-fontawesome@^0.2.2": + version "0.2.2" + resolved "https://npm.fontawesome.com/@fortawesome/react-fontawesome/-/0.2.2/react-fontawesome-0.2.2.tgz#68b058f9132b46c8599875f6a636dad231af78d4" + integrity sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g== + dependencies: + prop-types "^15.8.1" + "@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0": version "9.3.0" resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb"