diff --git a/src/ConfigHandler.ts b/src/ConfigHandler.ts index fbfede5..81d4fc8 100644 --- a/src/ConfigHandler.ts +++ b/src/ConfigHandler.ts @@ -2,10 +2,13 @@ import path from "path"; import process from "process"; import * as fs from "fs"; import {Config, GameRules} from "./RawConstants"; - +import {app} from "electron"; export class ConfigHandler { + private static dataPath = path.resolve(app.getPath('userData'), "config"); + private static confPath = path.resolve(this.dataPath, "config.json"); + private static defaultConfig = { rules: { switchValues: {}, @@ -13,21 +16,19 @@ export class ConfigHandler { } } - private static confPath = path.resolve(process.cwd(), "data", "config.json"); static generateDefault() { fs.writeFileSync(this.confPath, JSON.stringify(this.defaultConfig, null, 1)); } static get() { - if(!fs.existsSync(this.confPath)) - this.generateDefault(); + this.existsOrGenerate(); + let confRaw = fs.readFileSync(this.confPath).toString('utf8'); try { JSON.parse(confRaw); - }catch(e) - { + } catch (e) { this.generateDefault(); } @@ -35,22 +36,29 @@ export class ConfigHandler { return JSON.parse(confRaw) as Config; } - static set(conf: Config) { - if(!fs.existsSync(this.confPath)) + static existsOrGenerate() { + if (!fs.existsSync(this.dataPath)) + fs.mkdirSync(this.dataPath); + + if (!fs.existsSync(this.confPath)) this.generateDefault(); + } + + static set(conf: Config) { + this.existsOrGenerate(); + let confRaw = fs.readFileSync(this.confPath).toString('utf8'); try { JSON.parse(confRaw); - }catch(e) - { + } catch (e) { this.generateDefault(); } let current = JSON.parse(confRaw) as Config; current = {...current, ...conf}; - console.log("Config saved!") + console.log("Config saved to " + this.confPath); fs.writeFileSync(this.confPath, JSON.stringify(current, null, 1)); } diff --git a/src/RawConstants.ts b/src/RawConstants.ts index 2fc14ea..27ed0c2 100644 --- a/src/RawConstants.ts +++ b/src/RawConstants.ts @@ -8,8 +8,8 @@ export type IPCChannel = | 'PREPARING' export enum IPCListenChannels { - 'KEYPAD_INPUT'= 'KEYPAD_INPUT', - 'NFC_CARD'= 'NFC_CARD', + 'KEYPAD_INPUT' = 'KEYPAD_INPUT', + 'NFC_CARD' = 'NFC_CARD', 'NFC_RAW' = 'NFC_RAW' } @@ -45,7 +45,7 @@ export type GameSwitch = { export type GameInput = { name: GameInputNames, - type: "number" | "string", + type: "number" | "text", label: string, depends?: GameSwitchNames, value: string @@ -68,7 +68,8 @@ export type GameSwitchNames = export type GameInputNames = "PASSING_GO_CASH" | "STARTING_CASH" - | "PRISON_RELEASE_FEE"; + | "PRISON_RELEASE_FEE" + | "CURRENCY"; export interface GameRules { switchValues: { [key in GameSwitchNames]?: boolean }, @@ -80,27 +81,72 @@ export interface Config { } export enum NFCCardType { - ACCOUNT, - PROPERTY, - TASK, + ACCOUNT = "ACCOUNT", + PROPERTY = "PROPERTY", + TASK = "TASK", } export interface NFCCard { cardType: NFCCardType, - uid: string, - raw: string + uid?: string, + raw?: string } export interface NFCAccountCard extends NFCCard { symbol: string, - friendlyName: string, + nickname: string, pin: number } -export interface NFCPropertyCard extends NFCCard { +export enum PropertyColor { + YELLOW= "YELLOW", + GOLD="GOLD", + SILVER="SILVER", + ORANGE = "ORANGE", + PINK = "PINK", + BEIGE="BEIGE", + BROWN = "BROWN", + RED ="RED", + GREEN="GREEN", + BLACK="BLACK", + WHITE="WHITE", + LIGHT_BLUE ="LIGHT_BLUE", + DARK_BLUE = "DARK_BLUE", + TRAINSTATION = "TRAINSTATION", + UTILITY = "UTILITY", + + CUSTOM1 ="CUSTOM1", + CUSTOM2 = "CUSTOM2", + CUSTOM3 = "CUSTOM3" } -export interface NFCTaskCard extends NFCCard { +export type NFCPropertyCardSpecialProperties = + "TRAINSTATION" | "UTILITY" +export interface NFCPropertyCard extends NFCCard { + name: string, + color: PropertyColor, + fullSetAmount: number, + buyValue: number, + mortgageValue: number, + rent: number, + rent1?: number, + rent2?: number, + rent3?: number, + rent4?: number, + rentHotel?: number, + specialProperties: NFCPropertyCardSpecialProperties|null, +} + +export type TaskType = + "GET_FROM_BANK" + | "GET_FROM_ALL_PLAYERS" + | "PAY_TO_BANK" + | "PAY_TO_ALL_PLAYERS" + | "PAY_FOR_PROPS" + | "ESCAPE_JAIl"; + +export interface NFCTaskCard extends NFCCard { + type: TaskType } \ No newline at end of file diff --git a/src/web/App.tsx b/src/web/App.tsx index 3b25af2..dfeefc6 100644 --- a/src/web/App.tsx +++ b/src/web/App.tsx @@ -32,7 +32,7 @@ export class App extends Component<{}, AppState> { constructor(props: {}) { super(props); this.state = { - currentPage: PAGE.GAME_SETUP, + currentPage: PAGE.SETUP, showWiFi: false, } } diff --git a/src/web/CardSetup.tsx b/src/web/CardSetup.tsx new file mode 100644 index 0000000..09a494f --- /dev/null +++ b/src/web/CardSetup.tsx @@ -0,0 +1,406 @@ +import { + NFCAccountCard, + NFCCard, + NFCCardType, + NFCPropertyCard, NFCPropertyCardSpecialProperties, + PropertyColor, + TaskType, + WiFiNetwork +} from "../RawConstants"; +import React, {Component} from "react"; +import { + Backdrop, + Box, + Button, + Fade, FormControl, FormControlLabel, FormGroup, FormLabel, Grid, + InputLabel, + MenuItem, + Modal, Radio, RadioGroup, + Select, SelectChangeEvent, Switch, + TextField, + Typography +} from "@mui/material"; +import WifiPasswordIcon from "@mui/icons-material/WifiPassword"; +import WifiIcon from "@mui/icons-material/Wifi"; + +type AccountValues = keyof NFCAccountCard; + +type PropertyValues = keyof NFCPropertyCard; + +type TaskValues = + "TYPE" | "AMOUNT1" | "AMOUNT2"; + +interface CardSetupState { + validNFCCard: boolean, + NFCCardType: NFCCardType, + + accountValues: { [key in AccountValues]?: string }, + propertyValues: { [key in PropertyValues]?: string | number | boolean }, + taskValues: { [key in TaskValues]?: TaskType | number } +} + +interface CardSetupProps { + closeCallback: () => void; + card?: NFCCard; +} + +export default class CardSetup extends Component { + constructor(props: CardSetupProps) { + super(props); + + let defaultType = NFCCardType.ACCOUNT; + let accountValues: { [key in AccountValues]?: string } = {}; + let propertyValues: { [key in PropertyValues]?: string } = {}; + let taskValues: { [key in TaskValues]?: string } = {}; + + if (props.card) { + defaultType = props.card.cardType; + switch (defaultType) { + case NFCCardType.ACCOUNT: + let card = props.card as NFCAccountCard; + accountValues = { + symbol: card.symbol, + nickname: card.nickname, + pin: card.pin, + } + } + } + + this.state = { + validNFCCard: false, + NFCCardType: NFCCardType.ACCOUNT, + + accountValues: accountValues, + propertyValues: {}, + taskValues: {}, + } + + } + + componentDidMount() { + } + + componentWillUnmount() { + } + + modalStyle = { + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: 700, + maxHeight: '80%', + overflowY: 'scroll', + height: 'auto', + display: 'block', + bgcolor: 'background.paper', + border: '2px solid #0000', + boxShadow: 24, + p: 4, + }; + + changeAccountState(prop: AccountValues, ev: React.ChangeEvent, useUpperCase = false) { + this.setState(prevState => ({ + ...prevState, + accountValues: { + ...prevState.accountValues, + [prop]: useUpperCase ? ev.target.value.toUpperCase() : ev.target.value + } + })) + } + + changePropertyState(prop: PropertyValues, ev: any) { + if(prop == "fullSetAmount") { + if (ev.target.value > 4) + ev.target.value = 4; + if(ev.target.value < 1) + ev.target.value = 1; + } + if(prop == "buyValue" || prop == "mortgageValue" || prop.includes("rent")) + { + if(ev.target.value < 0) + ev.target.value = 0; + } + + this.setState(prevState => ({ + ...prevState, + propertyValues: { + ...prevState.propertyValues, + [prop]: ev.target.value + } + })) + } + + changeTaskState(prop: TaskValues, ev: React.ChangeEvent) { + this.setState(prevState => ({ + ...prevState, + taskValues: { + ...prevState.taskValues, + prop: ev.target.value + } + })) + } + + insertInputsPerType = () => { + if (this.state.NFCCardType == NFCCardType.ACCOUNT) { + return ( + this.changeAccountState("symbol", ev)} + /> + this.changeAccountState("nickname", ev)} + /> + this.changeAccountState("pin", ev)} + /> + + ) + } + + if (this.state.NFCCardType == NFCCardType.PROPERTY) { + return ( + + + Name + this.changePropertyState("name", ev)} + fullWidth + /> + + + Farbcode / Typ + + + + Voller Satz Anzahl + this.changePropertyState("fullSetAmount", ev)} + fullWidth + aria-valuemin={0} + aria-valuemax={4} + /> + + + Kauf-Wert + this.changePropertyState("buyValue", ev)} + fullWidth + /> + + + Hypothek-Wert + this.changePropertyState("mortgageValue", ev)} + fullWidth + /> + + + + this.changePropertyState("rent", ev)} + helperText={"Grundmiete ohne Haus und Set"} + /> + + + this.changePropertyState("rent1", ev)} + helperText={"Miete bei einem Haus"} + /> + + + this.changePropertyState("rent2", ev)} + helperText={"Miete bei 2 Häusern"} + /> + + + + this.changePropertyState("rent3", ev)} + helperText={"Miete bei 3 Häusern"} + /> + + + + this.changePropertyState("rent4", ev)} + helperText={"Miete bei 4 Häusern"} + /> + + + + this.changePropertyState("rentHotel", ev)} + helperText={"Miete bei einem Hotel"} + /> + + + + Besondere Eigenschaften + this.changePropertyState("specialProperties", ev)} + > + } label="Keine" /> + } label="Bahnhof" /> + } label="Versorgungswerk" /> + } label="Gefängnis" /> + + + + + + + ) + + } + + if (this.state.NFCCardType == NFCCardType.TASK) { + return ( + this.changeAccountState("symbol", ev)} + /> + this.changeAccountState("nickname", ev)} + /> + this.changeAccountState("pin", ev)} + /> + + ) + } + + return (
Typ selektieren um fortzufahren.
) + } + + render() { + return ( { + this.props.closeCallback() + }} + closeAfterTransition + slots={{backdrop: Backdrop}} + slotProps={{ + backdrop: { + timeout: 500, + }, + }} + > + + + + NFC-Karte konfigurieren + + + {this.state.validNFCCard ? "Es wurde eine gültige Spielkarte erkannt." : "Es wurde eine ungültige NFC-Karte erkannt."} +

+
+ Karten-Typ + +
+ {this.insertInputsPerType()} +
+ + + +
+
+
+
); + } +} \ No newline at end of file diff --git a/src/web/GameSetup.tsx b/src/web/GameSetup.tsx index 58d28c6..da88814 100644 --- a/src/web/GameSetup.tsx +++ b/src/web/GameSetup.tsx @@ -1,18 +1,23 @@ -import {Component} from "react"; +import React, {Component} from "react"; import { Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, - Grid, + Grid, ListItemAvatar, Snackbar, - Typography + Typography, + Avatar, ListItemText, ListItem, List } from "@mui/material"; import NfcIcon from '@mui/icons-material/Nfc'; import AddCardIcon from '@mui/icons-material/AddCard'; import PersonAddIcon from '@mui/icons-material/PersonAdd'; import PersonRemoveIcon from '@mui/icons-material/PersonRemove'; +import ImageIcon from '@mui/icons-material/Image'; +import WorkIcon from '@mui/icons-material/Work'; +import BeachAccessIcon from '@mui/icons-material/BeachAccess'; + import GameHandler from "./GameHandler"; import {NFCCard} from "../RawConstants"; @@ -74,7 +79,7 @@ export default class GameSetup extends Component<{}, GameState> { aria-describedby="alert-dialog-description" > - Karte anhalten, um Spieler hinzuzufügen! + Spielerkarte auf Gerät legen! @@ -94,8 +99,36 @@ export default class GameSetup extends Component<{}, GameState> { Wer spielt mit? - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + } diff --git a/src/web/Setup.tsx b/src/web/Setup.tsx index cdce4a3..5b9249e 100644 --- a/src/web/Setup.tsx +++ b/src/web/Setup.tsx @@ -1,11 +1,14 @@ import React, {Component} from "react"; import { + Backdrop, Box, Button, + Chip, Fade, FormControl, FormControlLabel, FormGroup, FormLabel, - Grid, Snackbar, + Grid, InputLabel, MenuItem, Modal, Select, + Snackbar, Switch, TextField, Typography @@ -14,18 +17,32 @@ import RestartAltIcon from '@mui/icons-material/RestartAlt'; import CloseIcon from '@mui/icons-material/Close'; import SaveIcon from '@mui/icons-material/Save'; import {PAGE} from "./App"; -import {GameInput, GameSwitch, GameRules, GameSwitchNames, GameInputNames, Config} from "../RawConstants"; +import { + Config, + GameInput, + GameRules, + GameSwitch, + GameSwitchNames, + IPCListenChannels, NFCCard, + NFCCardType, TaskType +} from "../RawConstants"; +import NfcIcon from "@mui/icons-material/Nfc"; +import IPCListener from "./IPCListener"; +import CardSetup from "./CardSetup"; + interface InitialSetupState { open: boolean, switchValues: GameSwitch[], inputValues: GameInput[], - savedMsg: boolean + savedMsg: boolean, + openNFCModal: boolean } const defaultConf: InitialSetupState = { open: true, savedMsg: false, + openNFCModal: true, switchValues: [ { name: "GET_BONUS_PASSING_START", @@ -110,6 +127,12 @@ const defaultConf: InitialSetupState = { type: "number", label: "Gefängnis Gebühren", value: "50" + }, + { + name: "CURRENCY", + type: "text", + label: "Währung", + value: "€" } ] } @@ -212,8 +235,7 @@ export default class Setup extends Component<{}, InitialSetupState> { })); } resolve(); - } catch(e) - { + } catch (e) { reject(e); } }) @@ -230,7 +252,7 @@ export default class Setup extends Component<{}, InitialSetupState> { for (let ele of this.state.switchValues) { gameSettings.switchValues[ele.name] = ele.value; - if(!!ele.depends && ele.value) + if (!!ele.depends && ele.value) gameSettings.switchValues[ele.name] = this.checkDependsValue(ele.depends); } @@ -247,12 +269,26 @@ export default class Setup extends Component<{}, InitialSetupState> { })); } + private nfcCard: NFCCard|null; + onNFCRawEvent = (card: NFCCard|null) => { + this.nfcCard = card; + this.setState(prevState => ({ + ...prevState, + openNFCModal: true, + })); + } + + private rawNFCListener: number = 0; + componentDidMount() { this.getSettings().then(); + this.rawNFCListener = IPCListener.attach(IPCListenChannels.NFC_RAW, (message) => { + this.onNFCRawEvent(message.data); + }) } componentWillUnmount() { - + IPCListener.detach(this.rawNFCListener); } handleClose = () => { @@ -279,6 +315,13 @@ export default class Setup extends Component<{}, InitialSetupState> { }} message={"Einstellungen gespeichert!"} /> + + + {this.state.openNFCModal && + this.setState(prevState => ({ + ...prevState, + openNFCModal: false + }))}/>} Einstellungen @@ -318,6 +361,15 @@ export default class Setup extends Component<{}, InitialSetupState> { })} + + Spielkarten + NFC} color="secondary" + variant="outlined"/>
Bitte die Karte an das Lesegerät halten, um sie zu + konfigurieren.
Anschließend können die Einstellungen der Karte im Dialog angepasst + werden.
+
@@ -327,7 +379,8 @@ export default class Setup extends Component<{}, InitialSetupState> {
diff --git a/src/web/Startup.tsx b/src/web/Startup.tsx index feb205d..bb97b25 100644 --- a/src/web/Startup.tsx +++ b/src/web/Startup.tsx @@ -163,6 +163,7 @@ export default class Startup extends Component { if (connected) this.counterInterval = setInterval(() => { if (!this.props.visible) return; + if (this.state.startCounter == 0) { clearInterval(this.counterInterval); this.startupBtnClick(); diff --git a/src/web/components.css b/src/web/components.css index bea3bf4..652cfb7 100644 --- a/src/web/components.css +++ b/src/web/components.css @@ -9,4 +9,36 @@ .hidden { display: none; +} + +.animationBreathe { + animation: breathing 3s ease-out infinite normal; + + -webkit-font-smoothing: antialiased; +} + +@keyframes breathing { + 0% { + -webkit-transform: scale(0.9); + -ms-transform: scale(0.9); + transform: scale(0.9); + } + + 25% { + -webkit-transform: scale(1.1); + -ms-transform: scale(1.1); + transform: scale(1.1); + } + + 80% { + -webkit-transform: scale(0.9); + -ms-transform: scale(0.9); + transform: scale(0.9); + } + + 100% { + -webkit-transform: scale(0.9); + -ms-transform: scale(0.9); + transform: scale(0.9); + } } \ No newline at end of file