From 0bda5111b8c8b65f224073284c44e347d20c0dbf Mon Sep 17 00:00:00 2001 From: cophilot <philxsb@gmail.com> Date: Wed, 7 Aug 2024 21:58:43 +0200 Subject: [PATCH] added gamebutton and favorite section --- src/Routes.tsx | 2 +- src/allGames.ts | 13 +++- .../FavoriteGameSection.scss | 3 + .../FavoriteGameSection.tsx | 77 +++++++++++++++++++ src/components/GameButton/GameButton.scss | 0 src/components/GameButton/GameButton.tsx | 43 +++++++++++ src/index.scss | 6 ++ src/utils/LocalStorageService.ts | 20 +++++ src/views/HomeView.tsx | 46 +++-------- 9 files changed, 174 insertions(+), 36 deletions(-) create mode 100644 src/components/FavoriteGameSection/FavoriteGameSection.scss create mode 100644 src/components/FavoriteGameSection/FavoriteGameSection.tsx create mode 100644 src/components/GameButton/GameButton.scss create mode 100644 src/components/GameButton/GameButton.tsx create mode 100644 src/utils/LocalStorageService.ts diff --git a/src/Routes.tsx b/src/Routes.tsx index d33327c..0e100b3 100644 --- a/src/Routes.tsx +++ b/src/Routes.tsx @@ -1,7 +1,7 @@ import { Route, Routes as ReactRoutes, HashRouter } from 'react-router-dom'; import HomeView from './views/HomeView'; import ExpandableTable from './views/ExpandableTable'; -import getAllGames from './allGames'; +import { getAllGames } from './allGames'; import StringUtils from './utils/StringUtils'; import CreateCustomView from './views/CreateCustomView/CreateCustomView'; diff --git a/src/allGames.ts b/src/allGames.ts index 02b908d..24fb8eb 100644 --- a/src/allGames.ts +++ b/src/allGames.ts @@ -10,7 +10,7 @@ import Wizard from './games/wizard/main'; import Wingspan from './games/wingspan/main'; import ForestShuffle from './games/forest-shuffle/main'; -export default function getAllGames() { +export function getAllGames() { return [ Everdell, Calico, @@ -25,3 +25,14 @@ export default function getAllGames() { ForestShuffle, ]; } + +export function getSortedGames() { + const games = getAllGames(); + games.sort((a, b) => a.definition.title.localeCompare(b.definition.title)); + return games; +} + +export function getSortedGameNames(): string[] { + const games = getSortedGames(); + return games.map((game) => game.definition.title); +} diff --git a/src/components/FavoriteGameSection/FavoriteGameSection.scss b/src/components/FavoriteGameSection/FavoriteGameSection.scss new file mode 100644 index 0000000..f4e38cf --- /dev/null +++ b/src/components/FavoriteGameSection/FavoriteGameSection.scss @@ -0,0 +1,3 @@ +.btn-template { + border: 2px dashed var(--font-color); +} diff --git a/src/components/FavoriteGameSection/FavoriteGameSection.tsx b/src/components/FavoriteGameSection/FavoriteGameSection.tsx new file mode 100644 index 0000000..67758b7 --- /dev/null +++ b/src/components/FavoriteGameSection/FavoriteGameSection.tsx @@ -0,0 +1,77 @@ +import { useState } from 'react'; +import './FavoriteGameSection.scss'; +import { getSortedGameNames } from '../../allGames'; +import LocalStorageService from '../../utils/LocalStorageService'; +import GameButton from '../GameButton/GameButton'; + +/** + * This is a FavoriteGameSection component + * @author cophilot + * @version 1.0.0 + * @created 2024-8-7 + */ +function FavoriteGameSection() { + const [addingMode, setAddingMode] = useState(false); + const [favoriteGames, setFavoriteGamesInternal] = useState<string[]>( + LocalStorageService.getFavoriteGames() + ); + const games = getSortedGameNames(); + + const setFavoriteGames = (favoriteGames: string[]) => { + setFavoriteGamesInternal(favoriteGames); + LocalStorageService.setFavoriteGames(favoriteGames); + }; + + const templateButtonStyle = { border: '2px dashed var(--font-color)' }; + + const onGameClick = (game: string) => { + if (addingMode) { + if (favoriteGames.includes(game)) { + setFavoriteGames(favoriteGames.filter((g) => g !== game)); + } else { + setFavoriteGames([...favoriteGames, game]); + } + } + }; + + return ( + <div className="ver"> + {addingMode + ? games.map((game) => ( + <button + key={game} + className={ + 'btn wide ' + + (favoriteGames.includes(game) ? 'selected' : '') + } + style={ + favoriteGames.includes(game) + ? {} + : templateButtonStyle + } + onClick={() => onGameClick(game)}> + {game} + </button> + )) + : favoriteGames.map((game) => ( + <GameButton game={game} key={game} /> + ))} + <i + className={ + 'bi icon ' + getIconClassName(addingMode, favoriteGames) + } + onClick={() => setAddingMode(!addingMode)}></i> + </div> + ); +} +export default FavoriteGameSection; + +function getIconClassName(addingMode: boolean, favoriteGames: string[]) { + if (addingMode) { + return 'bi-check-lg'; + } + if (favoriteGames.length === 0) { + return 'bi-plus-circle'; + } + return 'bi-pencil-square'; +} diff --git a/src/components/GameButton/GameButton.scss b/src/components/GameButton/GameButton.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/components/GameButton/GameButton.tsx b/src/components/GameButton/GameButton.tsx new file mode 100644 index 0000000..9c7b019 --- /dev/null +++ b/src/components/GameButton/GameButton.tsx @@ -0,0 +1,43 @@ +import { useNavigate } from 'react-router-dom'; +import './GameButton.scss'; +import StringUtils from '../../utils/StringUtils'; + +interface GameButtonProps { + game: string; + asLink?: boolean; + link?: string; +} + +/** + * This is a GameButton component + * @author cophilot + * @version 1.0.0 + * @created 2024-8-7 + */ +function GameButton({ game, asLink = false, link = '' }: GameButtonProps) { + const navigate = useNavigate(); + + if (asLink) { + return ( + <div className="btn selected" style={{ width: '250px' }}> + <a + className="" + href={link} + target="_blank" + style={{ color: 'white' }}> + {game} <i className="bi bi-arrow-up-right-square"></i> + </a> + </div> + ); + } + return ( + <button + className="btn selected wide" + onClick={() => { + navigate(`/game/${StringUtils.gameNameToPath(game)}`); + }}> + {game}{' '} + </button> + ); +} +export default GameButton; diff --git a/src/index.scss b/src/index.scss index cb22e12..9c4fdd2 100644 --- a/src/index.scss +++ b/src/index.scss @@ -133,3 +133,9 @@ a { .imp { color: var(--primary-color); } + +.icon { + font-size: 30px; + margin: 10px; + cursor: pointer; +} diff --git a/src/utils/LocalStorageService.ts b/src/utils/LocalStorageService.ts new file mode 100644 index 0000000..fad442c --- /dev/null +++ b/src/utils/LocalStorageService.ts @@ -0,0 +1,20 @@ +export default class LocalStorageService { + private static FAVORITE_GAMES_KEY = 'bsh-favorite-games'; + + static getFavoriteGames() { + const favoriteGames = localStorage.getItem( + LocalStorageService.FAVORITE_GAMES_KEY + ); + if (favoriteGames === null) { + return []; + } + return JSON.parse(favoriteGames); + } + + static setFavoriteGames(favoriteGames: string[]) { + localStorage.setItem( + LocalStorageService.FAVORITE_GAMES_KEY, + JSON.stringify(favoriteGames) + ); + } +} diff --git a/src/views/HomeView.tsx b/src/views/HomeView.tsx index 9daac25..c58367b 100644 --- a/src/views/HomeView.tsx +++ b/src/views/HomeView.tsx @@ -1,11 +1,12 @@ import { useEffect } from 'react'; import StyleUtils from '../api/utils/StyleUtils'; import { useNavigate } from 'react-router-dom'; -import StringUtils from '../utils/StringUtils'; import By from '../components/By'; -import getAllGames from '../allGames'; +import { getSortedGameNames } from '../allGames'; import Logo from '../components/Logo'; import DevMessage from '../components/DevMessage'; +import GameButton from '../components/GameButton/GameButton'; +import FavoriteGameSection from '../components/FavoriteGameSection/FavoriteGameSection'; export default function HomeView() { const navigate = useNavigate(); @@ -14,8 +15,7 @@ export default function HomeView() { document.title = 'BoardScoreHub'; }, []); - const games = getAllGames().map((game) => game.definition.title); - games.sort((a, b) => a.localeCompare(b)); + const games = getSortedGameNames(); return ( <div> @@ -32,10 +32,12 @@ export default function HomeView() { </button> {/* <button className="btn selected wide">Fix size Table</button> */} </div> + <h2>Favorites</h2> + <FavoriteGameSection /> <h2>Games</h2> <div className="ver"> {games.map((game) => ( - <LinkGameButton key={game} game={game} /> + <GameButton key={game} game={game} /> ))} </div> <p> @@ -50,7 +52,11 @@ export default function HomeView() { </p> <h2>External</h2> <div className="ver"> - <LinkGameButton game="Cascadia$x$https://cascoria.philipp-bonin.com/#/" /> + <GameButton + game="Cascadia" + asLink + link="https://cascoria.philipp-bonin.com/#/" + /> </div> <h2>Custom</h2> <div className="msg"> @@ -67,31 +73,3 @@ export default function HomeView() { </div> ); } - -function LinkGameButton({ game }: { game: string }) { - const navigate = useNavigate(); - if (game.includes('$x$')) { - const [name, link] = game.split('$x$'); - return ( - <div className="btn selected" style={{ width: '250px' }}> - <a - className="" - href={link} - target="_blank" - style={{ color: 'white' }}> - {name} <i className="bi bi-arrow-up-right-square"></i> - </a> - </div> - ); - } - - return ( - <button - className="btn selected wide" - onClick={() => { - navigate(`/game/${StringUtils.gameNameToPath(game)}`); - }}> - {game} - </button> - ); -}