This commit is contained in:
Tobias Hopp 2024-04-03 03:35:09 +02:00
parent bb1d9f71a8
commit 1e858021be
8 changed files with 617 additions and 38 deletions

View File

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

View File

@ -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 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
}

View File

@ -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,
}
}

406
src/web/CardSetup.tsx Normal file
View File

@ -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<CardSetupProps, CardSetupState> {
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<HTMLTextAreaElement | HTMLInputElement>, 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<HTMLTextAreaElement | HTMLInputElement>) {
this.setState(prevState => ({
...prevState,
taskValues: {
...prevState.taskValues,
prop: ev.target.value
}
}))
}
insertInputsPerType = () => {
if (this.state.NFCCardType == NFCCardType.ACCOUNT) {
return (<FormGroup sx={{m: 1}}>
<TextField sx={{mb: 1}}
label="Symbol"
type="text"
value={this.state.accountValues["symbol"]}
onChange={(ev) => this.changeAccountState("symbol", ev)}
/>
<TextField sx={{mb: 1}}
label="Nickname"
type="text"
value={this.state.accountValues["nickname"]}
onChange={(ev) => this.changeAccountState("nickname", ev)}
/>
<TextField sx={{mb: 1}}
label="PIN"
type="number"
value={this.state.accountValues["pin"]}
onChange={(ev) => this.changeAccountState("pin", ev)}
/>
</FormGroup>)
}
if (this.state.NFCCardType == NFCCardType.PROPERTY) {
return (<FormGroup sx={{m: 1}}>
<Grid container spacing={2} sx={{width: '100%'}}>
<Grid item xs={12} sm={12}>
<InputLabel>Name</InputLabel>
<TextField
sx={{mb: 1}}
type="text"
value={this.state.propertyValues["name"]}
onChange={(ev) => this.changePropertyState("name", ev)}
fullWidth
/>
</Grid>
<Grid item xs={12} sm={6}>
<InputLabel id="colorLabel">Farbcode / Typ</InputLabel>
<Select
labelId={"colorLabel"}
sx={{mb: 1}}
onChange={(ev) => this.changePropertyState("color", ev)}
label="Farbcode / Typ "
value={this.state.propertyValues["color"]}
fullWidth
>
<MenuItem disabled={true} value="pleaseSelect">Bitte wählen...</MenuItem>
{Object.keys(PropertyColor).map((color: string) => (
<MenuItem key={color} value={color}>{color}</MenuItem>
))}
</Select>
</Grid>
<Grid item xs={12} sm={6}>
<InputLabel>Voller Satz Anzahl</InputLabel>
<TextField
sx={{mb: 1}}
type="number"
value={this.state.propertyValues["fullSetAmount"]}
onChange={(ev) => this.changePropertyState("fullSetAmount", ev)}
fullWidth
aria-valuemin={0}
aria-valuemax={4}
/>
</Grid>
<Grid item xs={12} sm={6}>
<InputLabel>Kauf-Wert</InputLabel>
<TextField
sx={{mb: 1}}
inputProps={{"step": 5}}
type="number"
value={this.state.propertyValues["buyValue"]}
onChange={(ev) => this.changePropertyState("buyValue", ev)}
fullWidth
/>
</Grid>
<Grid item xs={12} sm={6}>
<InputLabel>Hypothek-Wert</InputLabel>
<TextField
sx={{mb: 1}}
inputProps={{"step": 5}}
type="number"
value={this.state.propertyValues["mortgageValue"]}
onChange={(ev) => this.changePropertyState("mortgageValue", ev)}
fullWidth
/>
</Grid>
<Grid item xs={4} sm={4}>
<TextField
type="number"
label={"Grundmiete"}
inputProps={{"step": 5}}
value={this.state.propertyValues["rent"]}
onChange={(ev) => this.changePropertyState("rent", ev)}
helperText={"Grundmiete ohne Haus und Set"}
/>
</Grid>
<Grid item xs={4} sm={4}>
<TextField
type="number"
label={"Miete 1"}
inputProps={{"step": 5}}
value={this.state.propertyValues["rent1"]}
onChange={(ev) => this.changePropertyState("rent1", ev)}
helperText={"Miete bei einem Haus"}
/>
</Grid>
<Grid item xs={4} sm={4}>
<TextField
type="number"
label={"Miete 2"}
inputProps={{"step": 5}}
value={this.state.propertyValues["rent2"]}
onChange={(ev) => this.changePropertyState("rent2", ev)}
helperText={"Miete bei 2 Häusern"}
/>
</Grid>
<Grid item xs={4} sm={4}>
<TextField
type="number"
label={"Miete 3"}
inputProps={{"step": 5}}
value={this.state.propertyValues["rent3"]}
onChange={(ev) => this.changePropertyState("rent3", ev)}
helperText={"Miete bei 3 Häusern"}
/>
</Grid>
<Grid item xs={4} sm={4}>
<TextField
type="number"
label={"Miete 4"}
inputProps={{"step": 5}}
value={this.state.propertyValues["rent4"]}
onChange={(ev) => this.changePropertyState("rent4", ev)}
helperText={"Miete bei 4 Häusern"}
/>
</Grid>
<Grid item xs={4} sm={4}>
<TextField
type="number"
label={"Miete Hotel"}
inputProps={{"step": 5}}
value={this.state.propertyValues["rentHotel"]}
onChange={(ev) => this.changePropertyState("rentHotel", ev)}
helperText={"Miete bei einem Hotel"}
/>
</Grid>
<Grid item xs={12} sm={12}>
<FormControl fullWidth>
<FormLabel id="cardProperties">Besondere Eigenschaften</FormLabel>
<RadioGroup
row
aria-labelledby="cardProperties"
defaultValue={""}
value={this.state.propertyValues["specialProperties"]}
onChange={(ev) => this.changePropertyState("specialProperties", ev)}
>
<FormControlLabel value={""} control={<Radio />} label="Keine" />
<FormControlLabel value="UTILITY" control={<Radio />} label="Bahnhof" />
<FormControlLabel value="TRAINSTATION" control={<Radio />} label="Versorgungswerk" />
<FormControlLabel value="JAIL" control={<Radio />} label="Gefängnis" />
</RadioGroup>
</FormControl>
</Grid>
</Grid>
</FormGroup>)
}
if (this.state.NFCCardType == NFCCardType.TASK) {
return (<FormGroup sx={{m: 1}}>
<TextField sx={{mb: 1}}
label="Symbol"
type="text"
value={this.state.accountValues["symbol"]}
onChange={(ev) => this.changeAccountState("symbol", ev)}
/>
<TextField sx={{mb: 1}}
label="Nickname"
type="text"
value={this.state.accountValues["nickname"]}
onChange={(ev) => this.changeAccountState("nickname", ev)}
/>
<TextField sx={{mb: 1}}
label="PIN"
type="number"
value={this.state.accountValues["pin"]}
onChange={(ev) => this.changeAccountState("pin", ev)}
/>
</FormGroup>)
}
return (<div>Typ selektieren um fortzufahren.</div>)
}
render() {
return (<Modal
aria-labelledby="transition-modal-title"
aria-describedby="transition-modal-description"
open={true}
onClose={() => {
this.props.closeCallback()
}}
closeAfterTransition
slots={{backdrop: Backdrop}}
slotProps={{
backdrop: {
timeout: 500,
},
}}
>
<Fade in={true}>
<Box sx={this.modalStyle}>
<Typography id="transition-modal-title" variant="h6" component="h2">
NFC-Karte konfigurieren
</Typography>
<Typography id="transition-modal-description" sx={{mt: 2}}>
{this.state.validNFCCard ? "Es wurde eine gültige Spielkarte erkannt." : "Es wurde eine ungültige NFC-Karte erkannt."}
<br/><br/>
<div>
<InputLabel id="nfcCardTypeLabel">Karten-Typ</InputLabel>
<Select
labelId="nfcCardTypeLabel"
value={this.state.NFCCardType}
onChange={(ev) =>
this.setState(prevState => ({
...prevState,
NFCCardType: ev.target.value as NFCCardType
}))
}
>
{Object.values(NFCCardType).map(e => {
return (
<MenuItem value={(e)}>{(e)}</MenuItem>
)
})}
</Select>
</div>
{this.insertInputsPerType()}
<br/>
<Button variant="contained" onClick={() => {
this.props.closeCallback();
}}
color="secondary">Schließen</Button>
<Button variant="contained" color="primary" sx={{ml: 1}}
onClick={() => {
}}>Karte beschreiben</Button>
</Typography>
</Box>
</Fade>
</Modal>);
}
}

View File

@ -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"
>
<DialogTitle id="alert-dialog-title">
Karte anhalten, um Spieler hinzuzufügen! <AddCardIcon/>
Spielerkarte auf Gerät legen! <AddCardIcon/>
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
@ -94,8 +99,36 @@ export default class GameSetup extends Component<{}, GameState> {
<Typography variant="h2" sx={{mb: 2}}>Wer spielt mit?</Typography>
<Grid container spacing={3}>
<Grid container spacing={1}>
<Grid item xs={4}></Grid>
<Grid justifyContent="center" alignItems="center" item xs={"auto"}>
<List sx={{ width: '100%', minWidth: '40%', bgcolor: 'background.paper', border: "1px solid black", borderRadius: "5px" }}>
<ListItem>
<ListItemAvatar>
<Avatar>
<ImageIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="Photos" secondary="Jan 9, 2014" />
</ListItem>
<ListItem>
<ListItemAvatar>
<Avatar>
<WorkIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="Work" secondary="Jan 7, 2014" />
</ListItem>
<ListItem>
<ListItemAvatar>
<Avatar>
<BeachAccessIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="Vacation" secondary="July 20, 2014" />
</ListItem>
</List>
</Grid>
</Grid>
</div>
}

View File

@ -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 &&
<CardSetup card={this.nfcCard} closeCallback={() => this.setState(prevState => ({
...prevState,
openNFCModal: false
}))}/>}
<Typography variant="h2" sx={{mb: 2}}>Einstellungen</Typography>
<Grid container spacing={3}>
<Grid item xs={6}>
@ -318,6 +361,15 @@ export default class Setup extends Component<{}, InitialSetupState> {
})}
</FormGroup>
</FormControl>
<FormControl sx={{mt: 5, mb: 1}} component="fieldset" variant="standard">
<FormLabel component="legend">Spielkarten</FormLabel>
<Typography sx={{ml: -2}} width={"95%"} variant="body1"><Chip
label={<div><NfcIcon sx={{marginTop: 0.9}} className={"animationBreathe"}
color="primary"/> NFC</div>} color="secondary"
variant="outlined"/><br/>Bitte die Karte an das Lesegerät halten, um sie zu
konfigurieren.<br/>Anschließend können die Einstellungen der Karte im Dialog angepasst
werden.</Typography>
</FormControl>
</Grid>
<Grid item xs={3.5}></Grid>
<Grid item xs={"auto"} alignItems="center">
@ -327,7 +379,8 @@ export default class Setup extends Component<{}, InitialSetupState> {
<Button sx={{ml: 2}} variant="outlined" color="error"
onClick={() => this.reset()}>Zurücksetzen <RestartAltIcon/></Button>
<Button sx={{ml: 2}} variant="contained" color="success" onClick={() => {
this.saveSettings(); window.app.setPage(PAGE.STARTUP);
this.saveSettings();
window.app.setPage(PAGE.STARTUP);
}}>Speichern <SaveIcon/></Button>
</Grid>
</Grid>

View File

@ -163,6 +163,7 @@ export default class Startup extends Component<StartupProps, StartupState> {
if (connected)
this.counterInterval = setInterval(() => {
if (!this.props.visible) return;
if (this.state.startCounter == 0) {
clearInterval(this.counterInterval);
this.startupBtnClick();

View File

@ -10,3 +10,35 @@
.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);
}
}