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>
-    );
-}