diff --git a/src/App.tsx b/src/App.tsx index 5ea2849..00a03be 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,9 +1,11 @@ import './App.scss'; +import CookieMessage from './components/CookieMessage/CookieMessage'; import Routes from './Routes'; function App() { return ( <> + > ); diff --git a/src/Routes.tsx b/src/Routes.tsx index 5fbd822..690c816 100644 --- a/src/Routes.tsx +++ b/src/Routes.tsx @@ -4,11 +4,16 @@ import HarmoniesView from './games/harmonies/View'; import SevenWondersDuelView from './games/seven-wonders-duel/View'; import DorfromatikDuelView from './games/dorfromatik-duel/View'; import EverdellView from './games/everdell/View'; +import ExpandableTable from './views/ExpandableTable'; function Routes() { return ( <> + void; } /** @@ -21,17 +22,29 @@ interface BoardScoreTableProps { export default function BoardScorePage({ definition, children, + onCellChange, }: BoardScoreTableProps): JSX.Element { const navigate = useNavigate(); + const [playerSize, setPlayerSize] = useState( GameStorage.getPlayerSize(definition.title, definition.playerSizes[0]) ); + const [settings, setSettings] = useState( + GameStorage.getGameSettings(definition.title, { + showHelp: false, + }) + ); const onPlayerSizeChange = (size: number) => { setPlayerSize(size); GameStorage.setPlayerSize(definition.title, size); }; + const applySettings = (newSettings: any) => { + setSettings(newSettings); + GameStorage.setGameSettings(definition.title, newSettings); + }; + useEffect(() => { setInitialAttributes(definition); }, [definition]); @@ -49,8 +62,21 @@ export default function BoardScorePage({ initPlayerSize={playerSize} onPlayerSizeChange={onPlayerSizeChange}> + { + const newSettings = { + ...settings, + showHelp: !settings.showHelp, + }; + applySettings(newSettings); + }}> + {settings.showHelp ? 'Hide Help' : 'Help'} + Export @@ -88,17 +114,20 @@ function print() { const element = showElements[i] as HTMLElement; element.style.display = 'block'; } + setTimeout(() => { + window.print(); + setTimeout(() => { + for (let i = 0; i < hideElements.length; i++) { + const element = hideElements[i] as HTMLElement; + element.style.display = 'block'; + } - window.print(); - for (let i = 0; i < hideElements.length; i++) { - const element = hideElements[i] as HTMLElement; - element.style.display = 'block'; - } - - for (let i = 0; i < showElements.length; i++) { - const element = showElements[i] as HTMLElement; - element.style.display = 'none'; - } + for (let i = 0; i < showElements.length; i++) { + const element = showElements[i] as HTMLElement; + element.style.display = 'none'; + } + }, 100); + }, 100); } function setInitialAttributes(definition: any) { diff --git a/src/api/BoardScoreTable/BoardScoreTable.scss b/src/api/BoardScoreTable/BoardScoreTable.scss index d7a5271..e14065b 100644 --- a/src/api/BoardScoreTable/BoardScoreTable.scss +++ b/src/api/BoardScoreTable/BoardScoreTable.scss @@ -13,6 +13,7 @@ color: var(--bg-color); position: sticky; top: -3px; + z-index: 10000; } th, @@ -40,6 +41,10 @@ background-color: var(--primary-color); color: var(--bg-color); } + .row-help { + font-size: 16px; + opacity: 0.7; + } } @media (max-width: 600px) { @@ -52,16 +57,15 @@ font-size: 16px; } .row-icon { - height: 50px; + height: 60px; } input { font-size: 16px; max-width: 30px; } - th { - input { - font-size: 16px; - } + .row-help { + font-size: 11px; + opacity: 0.7; } } } diff --git a/src/api/BoardScoreTable/BoardScoreTable.tsx b/src/api/BoardScoreTable/BoardScoreTable.tsx index 7ce9809..54504e0 100644 --- a/src/api/BoardScoreTable/BoardScoreTable.tsx +++ b/src/api/BoardScoreTable/BoardScoreTable.tsx @@ -2,10 +2,13 @@ import { useEffect, useState } from 'react'; import './BoardScoreTable.scss'; import GameStorage from '../utils/GameStorage'; +import { getFunctionForWinMode, WinMode } from '../types/WinMode'; interface BoardScoreTableProps { definition: any; playerSize: number; + gameSettings: any; + onCellChange?: (rowIndex: number, playerIndex: number, value: any) => void; } /** @@ -14,7 +17,12 @@ interface BoardScoreTableProps { * @version 1.0.0 * @created 2024-7-21 */ -function BoardScoreTable({ definition, playerSize }: BoardScoreTableProps) { +function BoardScoreTable({ + definition, + playerSize, + gameSettings, + onCellChange, +}: BoardScoreTableProps) { const playerSizes = Array.from(Array(playerSize).keys()); const rows = definition.rows || []; @@ -40,7 +48,11 @@ function BoardScoreTable({ definition, playerSize }: BoardScoreTableProps) { }, [playerSize, tableMatrix]); const getTableValue = (rowIndex: number, playerIndex: number) => { - return tableMatrix[rowIndex][playerIndex]; + const row = tableMatrix[rowIndex]; + if (!row) { + return 0; + } + return tableMatrix[rowIndex][playerIndex] || 0; }; const setTableValue = ( @@ -52,16 +64,31 @@ function BoardScoreTable({ definition, playerSize }: BoardScoreTableProps) { if (isNaN(value)) { return; } + + let couldBeAdded = false; const newTableMatrix = tableMatrix.map( (row: number[], index: number) => { if (index === rowIndex) { row[playerIndex] = Number(value); + couldBeAdded = true; } return row; } ); + + if (!couldBeAdded) { + newTableMatrix.push( + Array.from(Array(playerSize).keys()).map(() => 0) + ); + newTableMatrix[newTableMatrix.length - 1][playerIndex] = + Number(value); + } + setTableMatrix(newTableMatrix); GameStorage.setGameMatrix(definition.title, newTableMatrix); + if (onCellChange) { + onCellChange(rowIndex, playerIndex, value); + } }; const getColumnTotal = (playerIndex: number) => { @@ -74,8 +101,8 @@ function BoardScoreTable({ definition, playerSize }: BoardScoreTableProps) { }; const getWinningScore = () => { - const winMode = definition.winMode || 'most'; - const winFn = winMode === 'most' ? Math.max : Math.min; + const winMode = definition.winMode || WinMode.MOST; + const winFn = getFunctionForWinMode(winMode); const value = winFn(...totalRow); // check if all values are the same if (totalRow.every((val) => val === value)) { @@ -111,8 +138,11 @@ function BoardScoreTable({ definition, playerSize }: BoardScoreTableProps) { {rows.map((row: any, index: number) => ( - - + + {playerSizes.map((playerIndex) => ( ))} - + {totalRow.map((value, playerIndex) => ( ; + if (helpOn) { + inner = ( + <> + {inner} + {row.name} + > + ); + } } return {inner}; } +function getStyleFromRow(row: any) { + const style: any = {}; + if (row.bgColor) { + style.backgroundColor = row.bgColor; + } + if (row.fontColor) { + style.color = row.fontColor; + } + return style; +} + function getEmptyTbaleMatrix(rows: number, cols: number) { return Array.from(Array(rows).keys()).map(() => Array.from(Array(cols).keys()).map(() => 0) diff --git a/src/api/types/WinMode.ts b/src/api/types/WinMode.ts new file mode 100644 index 0000000..115f5f3 --- /dev/null +++ b/src/api/types/WinMode.ts @@ -0,0 +1,16 @@ +export enum WinMode { + MOST = 'most', + LEAST = 'least', + NONE = 'none', +} + +export function getFunctionForWinMode(mode: WinMode) { + switch (mode) { + case WinMode.MOST: + return Math.max; + case WinMode.LEAST: + return Math.min; + case WinMode.NONE: + return () => undefined; + } +} diff --git a/src/api/utils/GameStorage.ts b/src/api/utils/GameStorage.ts index 14f6de1..eda43fa 100644 --- a/src/api/utils/GameStorage.ts +++ b/src/api/utils/GameStorage.ts @@ -68,4 +68,21 @@ export default class GameStorage { JSON.stringify(matrix) ); } + + static setGameSettings(gameTitle: string, settings: unknown) { + localStorage.setItem( + GameStorage.getStorageKeyFromTitle(gameTitle, 'settings'), + JSON.stringify(settings) + ); + } + + static getGameSettings(gameTitle: string, fallback: unknown = {}) { + const settings = localStorage.getItem( + GameStorage.getStorageKeyFromTitle(gameTitle, 'settings') + ); + if (settings === null) { + return fallback; + } + return JSON.parse(settings); + } } diff --git a/src/components/CookieMessage/CookieMessage.scss b/src/components/CookieMessage/CookieMessage.scss new file mode 100644 index 0000000..4c4869c --- /dev/null +++ b/src/components/CookieMessage/CookieMessage.scss @@ -0,0 +1,12 @@ +.cookie-message { + position: fixed; + bottom: 0; + left: 0; + width: 100%; + background-color: var(--primary-color); + color: var(--bg-color); + padding-top: 10px; + padding-bottom: 20px; + box-shadow: 0 0 15px 10px rgba(0, 0, 0, 0.4); + z-index: 1000000; +} diff --git a/src/components/CookieMessage/CookieMessage.tsx b/src/components/CookieMessage/CookieMessage.tsx new file mode 100644 index 0000000..5de80a8 --- /dev/null +++ b/src/components/CookieMessage/CookieMessage.tsx @@ -0,0 +1,36 @@ +import { useState } from 'react'; +import './CookieMessage.scss'; + +/** + * This is a CookieMessage component + * @author cophilot + * @version 1.0.0 + * @created 2024-7-22 + */ +function CookieMessage() { + const lsKey = 'cookie-accepted'; + const [visible, setVisible] = useState( + localStorage.getItem(lsKey) !== 'true' + ); + return ( + <> + {visible && ( + + + This website uses cookies to ensure you get the best + experience on our website. + + { + setVisible(false); + localStorage.setItem(lsKey, 'true'); + }}> + Okay + + + )} + > + ); +} +export default CookieMessage; diff --git a/src/games/dorfromatik-duel/definition.ts b/src/games/dorfromatik-duel/definition.ts index 57aa682..7162862 100644 --- a/src/games/dorfromatik-duel/definition.ts +++ b/src/games/dorfromatik-duel/definition.ts @@ -10,6 +10,7 @@ import flagYellow from './assets/flag-yellow.png'; import flagRed from './assets/flag-red.png'; import longRail from './assets/longest-rail.png'; import longRiver from './assets/longest-river.png'; +import { WinMode } from '../../api/types/WinMode'; export default function getDefinition() { return { @@ -19,7 +20,7 @@ export default function getDefinition() { primaryColor: '#7ba1d4', secondaryColor: '#ee776c', playerSizes: [2], - winMode: 'most', + winMode: WinMode.MOST, rows: [ { name: 'Tree', diff --git a/src/games/everdell/assets/.gitkeep b/src/games/everdell/assets/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/games/everdell/assets/basic-events.png b/src/games/everdell/assets/basic-events.png new file mode 100644 index 0000000..8263ef4 Binary files /dev/null and b/src/games/everdell/assets/basic-events.png differ diff --git a/src/games/everdell/assets/journey.png b/src/games/everdell/assets/journey.png new file mode 100644 index 0000000..7798bd2 Binary files /dev/null and b/src/games/everdell/assets/journey.png differ diff --git a/src/games/everdell/assets/point-token.png b/src/games/everdell/assets/point-token.png new file mode 100644 index 0000000..8460ad8 Binary files /dev/null and b/src/games/everdell/assets/point-token.png differ diff --git a/src/games/everdell/assets/prosperity.png b/src/games/everdell/assets/prosperity.png new file mode 100644 index 0000000..ec06559 Binary files /dev/null and b/src/games/everdell/assets/prosperity.png differ diff --git a/src/games/everdell/assets/special-events.png b/src/games/everdell/assets/special-events.png new file mode 100644 index 0000000..95d5df7 Binary files /dev/null and b/src/games/everdell/assets/special-events.png differ diff --git a/src/games/everdell/assets/village.png b/src/games/everdell/assets/village.png new file mode 100644 index 0000000..b849d30 Binary files /dev/null and b/src/games/everdell/assets/village.png differ diff --git a/src/games/everdell/definition.ts b/src/games/everdell/definition.ts index f3fc053..b2ac58a 100644 --- a/src/games/everdell/definition.ts +++ b/src/games/everdell/definition.ts @@ -1,20 +1,44 @@ +import basicEvents from './assets/basic-events.png'; +import journey from './assets/journey.png'; +import pointToken from './assets/point-token.png'; +import specialEvents from './assets/special-events.png'; +import village from './assets/village.png'; +import prosperity from './assets/prosperity.png'; +import { WinMode } from '../../api/types/WinMode'; + export default function getDefinition() { return { title: 'Everdell', - //bgColor: '#fff', - //fontColor: '#000', - //primaryColor: '#fff', - //secondaryColor: '#000', + bgColor: '#5c652e', + fontColor: '#342a28', + primaryColor: '#714839', + secondaryColor: '#50446a', playerSizes: [1, 2, 3, 4], - winMode: 'most', + winMode: WinMode.MOST, rows: [ { - name: 'Row1', + name: 'Cards in Village', + icon: village, + }, + { + name: 'Prosperity', + icon: prosperity, + }, + { + name: 'Point Tokens', + icon: pointToken, + }, + { + name: 'Basic Events', + icon: basicEvents, + }, + { + name: 'Special Events', + icon: specialEvents, }, - { - name: 'Row2', - icon: 'src/games/everdell/assets/test.png', + name: 'Journey', + icon: journey, }, ], }; diff --git a/src/games/harmonies/definition.ts b/src/games/harmonies/definition.ts index fc0fe75..9319ad6 100644 --- a/src/games/harmonies/definition.ts +++ b/src/games/harmonies/definition.ts @@ -4,6 +4,7 @@ import flower from './assets/flower.png'; import house from './assets/house.png'; import water from './assets/water.png'; import card from './assets/card.png'; +import { WinMode } from '../../api/types/WinMode'; export default function getDefinition() { return { @@ -13,7 +14,7 @@ export default function getDefinition() { primaryColor: '#0094b8', secondaryColor: '#f7ac1f', playerSizes: [1, 2, 3, 4], - winMode: 'most', + winMode: WinMode.MOST, rows: [ { name: 'Leaf', diff --git a/src/games/seven-wonders-duel/assets/.gitkeep b/src/games/seven-wonders-duel/assets/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/games/seven-wonders-duel/assets/blue-card.png b/src/games/seven-wonders-duel/assets/blue-card.png new file mode 100644 index 0000000..eb6df66 Binary files /dev/null and b/src/games/seven-wonders-duel/assets/blue-card.png differ diff --git a/src/games/seven-wonders-duel/assets/coins.png b/src/games/seven-wonders-duel/assets/coins.png new file mode 100644 index 0000000..3c19b2b Binary files /dev/null and b/src/games/seven-wonders-duel/assets/coins.png differ diff --git a/src/games/seven-wonders-duel/assets/green-card.png b/src/games/seven-wonders-duel/assets/green-card.png new file mode 100644 index 0000000..fb252cf Binary files /dev/null and b/src/games/seven-wonders-duel/assets/green-card.png differ diff --git a/src/games/seven-wonders-duel/assets/green-chip.png b/src/games/seven-wonders-duel/assets/green-chip.png new file mode 100644 index 0000000..6a2fcd7 Binary files /dev/null and b/src/games/seven-wonders-duel/assets/green-chip.png differ diff --git a/src/games/seven-wonders-duel/assets/military.png b/src/games/seven-wonders-duel/assets/military.png new file mode 100644 index 0000000..ba0ca6e Binary files /dev/null and b/src/games/seven-wonders-duel/assets/military.png differ diff --git a/src/games/seven-wonders-duel/assets/purple-card.png b/src/games/seven-wonders-duel/assets/purple-card.png new file mode 100644 index 0000000..2467ed1 Binary files /dev/null and b/src/games/seven-wonders-duel/assets/purple-card.png differ diff --git a/src/games/seven-wonders-duel/assets/world-wonder.png b/src/games/seven-wonders-duel/assets/world-wonder.png new file mode 100644 index 0000000..34d6497 Binary files /dev/null and b/src/games/seven-wonders-duel/assets/world-wonder.png differ diff --git a/src/games/seven-wonders-duel/assets/yellow-card.png b/src/games/seven-wonders-duel/assets/yellow-card.png new file mode 100644 index 0000000..9957e81 Binary files /dev/null and b/src/games/seven-wonders-duel/assets/yellow-card.png differ diff --git a/src/games/seven-wonders-duel/definition.ts b/src/games/seven-wonders-duel/definition.ts index 0bc11ce..a26305b 100644 --- a/src/games/seven-wonders-duel/definition.ts +++ b/src/games/seven-wonders-duel/definition.ts @@ -1,20 +1,62 @@ +import blueCard from './assets/blue-card.png'; +import greenCard from './assets/green-card.png'; +import yellowCard from './assets/yellow-card.png'; +import purpleCard from './assets/purple-card.png'; +import worldWonder from './assets/world-wonder.png'; +import greenChip from './assets/green-chip.png'; +import coins from './assets/coins.png'; +import military from './assets/military.png'; +import { WinMode } from '../../api/types/WinMode'; + export default function getDefinition() { return { title: 'Seven Wonders: Duel', - //bgColor: '#fff', + bgColor: '#fbdbb3', //fontColor: '#000', - //primaryColor: '#fff', - //secondaryColor: '#000', + primaryColor: '#c59551', + secondaryColor: '#2a7c6d', playerSizes: [2], - winMode: 'most', + winMode: WinMode.MOST, rows: [ { - name: 'Row1', + name: 'Blue cards', + icon: blueCard, + bgColor: '#c7f0fc', + }, + { + name: 'Green cards', + icon: greenCard, + bgColor: '#d8edd6', + }, + { + name: 'Yellow cards', + icon: yellowCard, + bgColor: '#fffad9', + }, + { + name: 'Purple cards', + icon: purpleCard, + bgColor: '#d2cce0', + }, + { + name: 'World wonders', + icon: worldWonder, + bgColor: '#ececed', + }, + { + name: 'Progress tokens', + icon: greenChip, + bgColor: '#d8edd6', + }, + { + name: 'Coins', + icon: coins, + bgColor: '#fffad9', }, - { - name: 'Row2', - icon: 'src/games/seven-wonders-duel/assets/test.png', + name: 'Military', + icon: military, + bgColor: '#fad2c5', }, ], }; diff --git a/src/utils/StyleUtils.ts b/src/utils/StyleUtils.ts index 2a56ebf..88ce9c3 100644 --- a/src/utils/StyleUtils.ts +++ b/src/utils/StyleUtils.ts @@ -11,7 +11,7 @@ export default class StyleUtils { document.documentElement.style.setProperty('--primary-color', color); } - static setSecondaryColor(color: string = '#f7eb83') { + static setSecondaryColor(color: string = '#E9BD43') { document.documentElement.style.setProperty('--secondary-color', color); } diff --git a/src/views/ExpandableTable.tsx b/src/views/ExpandableTable.tsx new file mode 100644 index 0000000..f5b7f47 --- /dev/null +++ b/src/views/ExpandableTable.tsx @@ -0,0 +1,56 @@ +import { useEffect, useMemo, useState } from 'react'; +import BoardScorePage from '../api/BoardScorePage/BoardScorePage'; +import { WinMode } from '../api/types/WinMode'; +import By from '../components/By/By'; + +export default function ExpandableTable() { + const rowsLSKey = 'expandable table-rows'; + + const [rows, setRows] = useState([ + { + name: 'Row 1', + bgColor: 'white', + }, + ]); + + useEffect(() => { + const storedRows = localStorage.getItem(rowsLSKey); + if (storedRows) { + setRows(JSON.parse(storedRows)); + } + }, []); + + const definition = useMemo(() => { + return { + title: 'Expandable Table', + playerSizes: [1, 2, 3, 4, 5, 6], + winMode: WinMode.NONE, + rows: rows, + }; + }, [rows]); + + const onCellChange = (row: number) => { + if (row < rows.length - 1) { + return; + } + const newRowNumber = rows.length + 1; + let bgColor = 'white'; + if (newRowNumber % 2 === 0) { + bgColor = '#d8d8d8'; + } + const newRows = [ + ...rows, + { + name: `Row ${newRowNumber}`, + bgColor: bgColor, + }, + ]; + setRows(newRows); + localStorage.setItem(rowsLSKey, JSON.stringify(newRows)); + }; + return ( + + + + ); +} diff --git a/src/views/HomeView.tsx b/src/views/HomeView.tsx index 7d5b00c..a5889f4 100644 --- a/src/views/HomeView.tsx +++ b/src/views/HomeView.tsx @@ -3,6 +3,7 @@ import StyleUtils from '../utils/StyleUtils'; import { useNavigate } from 'react-router-dom'; import StringUtils from '../utils/StringUtils'; import By from '../components/By/By'; +import getAllGames from '../allGames'; export default function HomeView() { const navigate = useNavigate(); @@ -11,18 +12,28 @@ export default function HomeView() { document.title = 'BoardScoreHub'; }, []); - const games = [ - 'Harmonies', - 'Seven Wonders: Duel', - 'Dorfromatik: Duel', - 'Everdell', - ]; + const games = getAllGames(); games.sort((a, b) => a.localeCompare(b)); return ( BoardScoreHub - + General + + { + navigate('/general-table/expandable'); + }}> + Expandable Table + + + Fix size Table + + + Games + {games.map((game) => (
{row.name}