This commit is contained in:
Daniel Ledda
2020-12-24 12:37:26 +01:00
parent 602f9fd3f5
commit 4b409083fd
16 changed files with 760 additions and 357 deletions

View File

@@ -1,138 +1,228 @@
import React, {useContext, useState} from "react";
import {CellDef, RulesetSchemaDto} from "../Services/RulesetSchemaDto";
import {
Button,
Checkbox,
Form,
Header,
Segment,
Table
} from "semantic-ui-react";
BlockDef,
BonusBlockDef,
CellDef,
NoBonusBlockDef,
RulesetSchemaDto,
} from "../Services/RulesetSchemaDto";
import {Button, Container, Grid, Header, Segment} from "semantic-ui-react";
import UserContext from "../Contexts/UserContext";
import RulesetBlockTable from "./RulesetBlockTable";
import RulesetDisplayPanel from "./RulesetDisplayPanel";
import NewBlockForm from "./NewBlockForm";
import axios from "axios";
import {SERVER_BASE_NAME} from "../index";
import EditableHeader from "./EditableHeader";
interface RulesetDisplayPanelProps {
onSubmitRuleset: (ruleset: RulesetSchemaDto) => any;
onRulesetSave: () => any;
onRulesetChange: () => any;
}
const CreateRulesetPanel: React.FunctionComponent<RulesetDisplayPanelProps> = (props) => {
const {onSubmitRuleset} = props;
const {strings: Locale} = useContext(UserContext);
const [currentRulesetBuild, updateCurrentRulesetBuild] = useState<RulesetSchemaDto>({
id: "",
label: Locale.rulesetsPage.newRuleset,
blocks: {},
});
const [newBlockInput, updateNewBlockInput] = useState({
label: "",
hasBonus: false,
bonusScore: 35,
bonusFor: 63,
cells: {},
});
interface RulesetDisplayPanelState {
saving: boolean;
currentRulesetBuild: RulesetSchemaDto;
newBlockInput: {
label: string;
hasBonus: boolean;
bonusFor: number;
bonusScore: number;
};
}
const handleAddBlock = () => {
if (newBlockInput.label) {
updateCurrentRulesetBuild({
...currentRulesetBuild,
blocks: {
...currentRulesetBuild.blocks,
[newBlockInput.label]: {
...newBlockInput,
bonusFor: newBlockInput.hasBonus ? newBlockInput.bonusFor : undefined,
bonusScore: newBlockInput.hasBonus ? newBlockInput.bonusScore : undefined,
},
}
class CreateRulesetPanel extends React.Component<
RulesetDisplayPanelProps,
RulesetDisplayPanelState
> {
constructor(props: RulesetDisplayPanelProps) {
super(props);
this.state = {
saving: false,
currentRulesetBuild: {
id: "",
label: "",
blocks: {},
},
newBlockInput: {
label: "",
hasBonus: false,
bonusFor: 0,
bonusScore: 0,
},
};
}
componentDidMount(): void {
if (this.state.currentRulesetBuild.label === "") {
this.setState({
currentRulesetBuild: {
...this.state.currentRulesetBuild,
id: this.context.strings.rulesetsPage.newRuleset,
label: this.context.strings.rulesetsPage.newRuleset,
},
});
}
};
}
const handleAddCell = (cellDef: CellDef, blockId: string) => {
if (cellDef.label) {
updateCurrentRulesetBuild({
...currentRulesetBuild,
blocks: {
...currentRulesetBuild.blocks,
[blockId]: {
...currentRulesetBuild.blocks[blockId],
cells: {
...currentRulesetBuild.blocks[blockId].cells,
[cellDef.label]: cellDef
}
}
}
})
handleAddBlock() {
if (this.state.newBlockInput.label) {
const blockToSave = {
...this.state.newBlockInput,
cells: {},
};
if (!this.state.newBlockInput.hasBonus) {
delete blockToSave.bonusScore;
delete blockToSave.bonusFor;
}
this.state.currentRulesetBuild.blocks[blockToSave.label] = blockToSave;
this.forceUpdate();
}
};
this.props.onRulesetChange();
}
return (
<>
<Header size={"tiny"}>
{Locale.rulesetsPage.newRuleset}
</Header>
<RulesetDisplayPanel
ruleset={currentRulesetBuild}
editable={true}
loading={false}
onAddCell={handleAddCell}
/>
<Header size={"tiny"}>
{Locale.rulesetsPage.newBlock}
</Header>
<Segment>
<Form>
<Form.Field>
<label>{Locale.rulesetsPage.blockName + ": "}</label>
<input
placeholder={Locale.rulesetsPage.blockNamePlaceholder}
onChange={(e) => updateNewBlockInput({...newBlockInput, label: e.target.value})}
/>
</Form.Field>
<Form.Field>
<Checkbox
toggle={true}
onChange={(e, c) => updateNewBlockInput({...newBlockInput, hasBonus: c.checked ?? false})}
label={Locale.rulesetsPage.bonus}
/>
</Form.Field>
{newBlockInput.hasBonus && (
<>
<Form.Field width={4}>
<label>{Locale.rulesetsPage.bonusScore + ": "}</label>
<input
type={"number"}
value={newBlockInput.bonusScore}
onChange={(e) => updateNewBlockInput({...newBlockInput, bonusScore: Number(e.target.value)})}
/>
</Form.Field>
<Form.Field width={4}>
<label>{Locale.rulesetsPage.bonusThreshold + ": "}</label>
<input
type={"number"}
value={newBlockInput.bonusFor}
onChange={(e) => updateNewBlockInput({...newBlockInput, bonusFor: Number(e.target.value)})}
/>
</Form.Field>
</>
)}
<Form.Field>
<Button
onClick={handleAddBlock}
>
{Locale.rulesetsPage.addBlock}
</Button>
</Form.Field>
</Form>
</Segment>
<Button
fluid={true}
onClick={() => onSubmitRuleset(currentRulesetBuild)}
>
{Locale.rulesetsPage.submit}
</Button>
</>
);
};
handleAddCell(cellDef: CellDef, blockId: string) {
if (cellDef.label) {
this.state.currentRulesetBuild.blocks[blockId].cells[cellDef.label] = cellDef;
this.forceUpdate();
}
this.props.onRulesetChange();
}
export default CreateRulesetPanel;
handleRulesetNameChange(newLabel: string) {
this.setState({
currentRulesetBuild: {
...this.state.currentRulesetBuild,
label: newLabel,
id: newLabel,
},
});
}
handleBlockNameChange(newName: string) {
this.setState({
newBlockInput: {
...this.state.newBlockInput,
label: newName,
},
});
}
handleToggleBlockBonus(newHasBonus: boolean) {
this.setState({
newBlockInput: {
...this.state.newBlockInput,
hasBonus: newHasBonus,
},
});
}
handleBonusScoreChange(newBonusScore: number) {
this.setState({
newBlockInput: {
...this.state.newBlockInput,
bonusScore: newBonusScore,
},
});
}
handleBonusForChange(newBonusFor: number) {
this.setState({
newBlockInput: {
...this.state.newBlockInput,
bonusFor: newBonusFor,
},
});
}
blocksExistAndAllHaveCells(): boolean {
return (
Object.values(this.state.currentRulesetBuild.blocks).length > 0 &&
Object.values(this.state.currentRulesetBuild.blocks).reduce(
(prev: boolean, blockDef: BlockDef) =>
Object.values(blockDef.cells).length > 0 && prev,
true
)
);
}
removeBlock(blockId: string) {
this.props.onRulesetChange();
delete this.state.currentRulesetBuild.blocks[blockId];
this.forceUpdate();
}
removeCell(cellId: string, blockId: string) {
this.props.onRulesetChange();
delete this.state.currentRulesetBuild.blocks[blockId].cells[cellId];
this.forceUpdate();
}
async submitNewRuleset() {
this.setState({saving: true}, async () => {
const response = await axios.post(
SERVER_BASE_NAME + "/api/ruleset",
this.state.currentRulesetBuild
);
console.log(response);
this.setState({saving: false}, () => {
this.props.onRulesetSave();
});
});
}
render() {
const Locale = this.context.strings;
return (
<Grid>
<Grid.Column>
<Grid.Row>
<Segment basic={true}>
<EditableHeader
onTextEdit={(t) => this.handleRulesetNameChange(t)}
text={this.state.currentRulesetBuild.label}
headerProps={{size: "small"}}
/>
<RulesetDisplayPanel
ruleset={this.state.currentRulesetBuild}
editable={true}
loading={false}
removeBlock={(id) => this.removeBlock(id)}
removeCell={(cid, bid) => this.removeCell(cid, bid)}
onAddCell={(c, b) => this.handleAddCell(c, b)}
/>
</Segment>
</Grid.Row>
<Grid.Row>
<Segment basic={true}>
<Header size={"tiny"}>{Locale.rulesetsPage.newBlock}</Header>
<NewBlockForm
newBlockInput={this.state.newBlockInput}
onNameChange={(x) => this.handleBlockNameChange(x)}
onBonusToggle={(x) => this.handleToggleBlockBonus(x)}
onBonusScoreChange={(x) => this.handleBonusScoreChange(x)}
onBonusForChange={(x) => this.handleBonusForChange(x)}
onSubmitClick={() => this.handleAddBlock()}
/>
</Segment>
</Grid.Row>
<Grid.Column>
<Segment textAlign={"center"} basic={true}>
<Button
loading={this.state.saving}
primary={true}
disabled={!this.blocksExistAndAllHaveCells()}
onClick={() => this.submitNewRuleset()}
>
{Locale.rulesetsPage.save}
</Button>
</Segment>
</Grid.Column>
</Grid.Column>
</Grid>
);
}
}
CreateRulesetPanel.contextType = UserContext;
export default CreateRulesetPanel;

View File

@@ -0,0 +1,39 @@
import React, {useState, KeyboardEvent} from "react";
import {Header, Icon, Input, StrictHeaderProps} from "semantic-ui-react";
interface EditableHeaderProps {
onTextEdit: (newText: string) => any;
headerProps: StrictHeaderProps;
text: string;
}
const EditableHeader: React.FunctionComponent<EditableHeaderProps> = (props) => {
const {text, onTextEdit, headerProps} = props;
const [isEditing, updateIsEditing] = useState(false);
const handleKeyUp = (e: KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
onTextEdit(e.currentTarget.value);
updateIsEditing(false);
}
};
return isEditing ? (
<Header {...headerProps}>
<Input
onBlur={() => updateIsEditing(false)}
autoFocus={true}
onChange={(e) => onTextEdit(e.target.value)}
value={text}
onKeyUp={(e: KeyboardEvent<HTMLInputElement>) => handleKeyUp(e)}
/>
</Header>
) : (
<Header style={{cursor: "pointer"}} onClick={() => updateIsEditing(true)} {...headerProps}>
{text + " "}
<Icon name={"edit"} size={"tiny"} disabled={true}/>
</Header>
);
};
export default EditableHeader;

View File

@@ -0,0 +1,69 @@
import React, {useContext} from "react";
import UserContext from "../Contexts/UserContext";
import {Button, Checkbox, Form, Segment} from "semantic-ui-react";
function NewBlockForm(props: {
newBlockInput: {
bonusScore: number;
bonusFor: number;
label: string;
hasBonus: boolean;
};
onNameChange: (newName: string) => void;
onBonusToggle: (newHasBonus: boolean) => void;
onBonusScoreChange: (newBonusScore: number) => void;
onBonusForChange: (newBonusFor: number) => void;
onSubmitClick: () => void;
}) {
const {strings: Locale} = useContext(UserContext);
return (
<Segment>
<Form>
<Form.Field>
<label>{Locale.rulesetsPage.blockName + ": "}</label>
<input
placeholder={Locale.rulesetsPage.blockNamePlaceholder}
onChange={(e) => props.onNameChange(e.target.value)}
/>
</Form.Field>
<Form.Field>
<Checkbox
toggle={true}
onChange={(e, c) => props.onBonusToggle(c.checked ?? false)}
label={Locale.rulesetsPage.bonus}
/>
</Form.Field>
{props.newBlockInput.hasBonus && (
<>
<Form.Field width={4}>
<label>{Locale.rulesetsPage.bonusScore + ": "}</label>
<input
type={"number"}
value={props.newBlockInput.bonusScore}
onChange={(e) => props.onBonusScoreChange(Number(e.target.value))}
/>
</Form.Field>
<Form.Field width={4}>
<label>{Locale.rulesetsPage.bonusThreshold + ": "}</label>
<input
type={"number"}
value={props.newBlockInput.bonusFor}
onChange={(e) => props.onBonusForChange(Number(e.target.value))}
/>
</Form.Field>
</>
)}
<Form.Field>
<Button
disabled={props.newBlockInput.label === ""}
onClick={props.onSubmitClick}
>
{Locale.rulesetsPage.addBlock}
</Button>
</Form.Field>
</Form>
</Segment>
);
}
export default NewBlockForm;

View File

@@ -1,7 +1,7 @@
import {BlockDef, CellDef} from "../Services/RulesetSchemaDto";
import React, {useContext, useState} from "react";
import RulesetCellTableRow from "./RulesetCellTableRow";
import {Button, Dropdown, DropdownItemProps, Grid, Input, Table} from "semantic-ui-react";
import {Button, Dropdown, DropdownItemProps, Grid, Input, List, Table} from "semantic-ui-react";
import UserContext from "../Contexts/UserContext";
import {FieldType} from "../enums";
@@ -10,32 +10,28 @@ interface RulesetBlockTableProps {
blockDef: BlockDef | null;
editable?: boolean;
onAddCell?: (cellDef: CellDef, blockId: string) => any;
removeBlock?: (blockId: string) => any;
removeCell?: (cellId: string, blockId: string) => any;
}
const RulesetBlockTable: React.FunctionComponent<RulesetBlockTableProps> = (props) => {
const {id, blockDef, editable, onAddCell} = props;
const {id, blockDef, editable, onAddCell, removeCell} = props;
const [currentCellInput, updateCurrentCellInput] = useState({
label: "",
maxMultiples: 0,
multiplier: 0,
fieldType: FieldType.number,
score: 0,
maxMultiples: 1,
multiplier: 1,
fieldType: undefined,
score: 25,
maxSuperkadis: 5,
});
const {strings: Locale} = useContext(UserContext);
if (!blockDef) {
return (
<Table
attached={true}
fixed={true}
celled={true}
>
<Table attached={true} fixed={true} celled={true}>
<Table.Header>
<Table.Row>
<Table.HeaderCell
colSpan={"3"}
textAlign={"center"}
>
<Table.HeaderCell colSpan={"3"} textAlign={"center"}>
{Locale.rulesetsPage.noBlocks}
</Table.HeaderCell>
</Table.Row>
@@ -51,151 +47,200 @@ const RulesetBlockTable: React.FunctionComponent<RulesetBlockTableProps> = (prop
subText += `${Locale.general.yes},
${Locale.rulesetsPage.bonusScore}: ${blockDef.bonusScore},
${Locale.rulesetsPage.bonusThreshold}: ${blockDef.bonusFor}`;
}
else {
} else {
subText += Locale.general.no;
}
const fieldTypeOptions: DropdownItemProps[] = [
{value: FieldType.bool, key: FieldType.bool, text: Locale.rulesetsPage[FieldType.bool]},
{value: FieldType.multiplier, key: FieldType.multiplier, text: Locale.rulesetsPage[FieldType.multiplier]},
{value: FieldType.number, key: FieldType.number, text: Locale.rulesetsPage[FieldType.number]},
{value: FieldType.superkadi, key: FieldType.superkadi, text: Locale.rulesetsPage[FieldType.superkadi]},
{
value: FieldType.multiplier,
key: FieldType.multiplier,
text: Locale.rulesetsPage[FieldType.multiplier],
},
{
value: FieldType.number,
key: FieldType.number,
text: Locale.rulesetsPage[FieldType.number],
},
{
value: FieldType.superkadi,
key: FieldType.superkadi,
text: Locale.rulesetsPage[FieldType.superkadi],
},
];
const onChangeDropdown = (value: FieldType) => {
const newCell = {
...currentCellInput,
fieldType: value,
};
if (value === FieldType.bool) {
newCell.score = 0;
}
else if (value === FieldType.multiplier) {
newCell.multiplier = 0;
newCell.maxMultiples = 5;
}
else if (value === FieldType.superkadi) {
newCell.maxMultiples = 5;
newCell.score = 50;
}
const onChangeFieldTypeDropdown = (newFieldType: FieldType) => {
const newCell: any = Object.assign({}, currentCellInput);
newCell.fieldType = newFieldType;
updateCurrentCellInput(newCell);
};
const handleAddCell = () => {
if (onAddCell) {
const newCell: any = Object.assign({}, currentCellInput);
if (newCell.fieldType === FieldType.bool) {
delete newCell.maxMultiples;
delete newCell.maxSuperkadis;
delete newCell.multiplier;
} else if (newCell.fieldType === FieldType.multiplier) {
delete newCell.score;
delete newCell.maxSuperkadis;
} else if (newCell.fieldType === FieldType.superkadi) {
delete newCell.multiplier;
delete newCell.maxMultiples;
} else if (newCell.fieldType === FieldType.number) {
delete newCell.multiplier;
delete newCell.maxMultiples;
delete newCell.maxSuperkadis;
delete newCell.score;
}
onAddCell(newCell, id);
}
};
return (
<Table
attached={true}
fixed={true}
celled={true}
>
<Table attached={true} fixed={true} celled={true}>
<Table.Header>
<Table.Row>
<Table.HeaderCell
colSpan={"3"}
textAlign={"center"}
>
<Table.HeaderCell verticalAlign={"middle"} colSpan={"3"} textAlign={"center"}>
{blockDef.label}
{editable && (
<Button
floated={"right"}
color={"red"}
icon={"minus"}
size={"mini"}
onClick={() => props.removeBlock?.(id)}
/>
)}
</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{idAndCellList.map(idAndCell => (
{idAndCellList.map((idAndCell) => (
<RulesetCellTableRow
key={idAndCell[0]}
id={idAndCell[0]}
blockId={id}
cellDef={idAndCell[1]}
editable={editable}
removeCell={removeCell}
/>
))}
{editable && (
<Table.Row>
<Table.Cell
style={{overflow: "visible"}}
colSpan={"3"}
>
<Grid>
<Grid.Row
columns={3}
verticalAlign={"middle"}
>
<Grid.Column width={4}>
<Input
fluid={true}
value={currentCellInput.label}
label={Locale.rulesetsPage.cellName}
placeholder={Locale.rulesetsPage.cellNamePlaceholder}
onChange={(e) => updateCurrentCellInput({...currentCellInput, label: e.target.value})}
/>
</Grid.Column>
<Grid.Column width={4}>
<Dropdown
selectedLabel={currentCellInput.fieldType}
placeholder={Locale.rulesetsPage.fieldTypePlaceholder}
onChange={(e, c) => onChangeDropdown(c.value as FieldType)}
options={fieldTypeOptions}
button={true}
/>
</Grid.Column>
<Grid.Column width={4}>
{currentCellInput.fieldType === FieldType.multiplier && (
<Input
label={Locale.rulesetsPage.multiplierPlaceholder}
fluid={true}
type={"number"}
value={currentCellInput.multiplier}
onChange={(e) => updateCurrentCellInput(
{...currentCellInput, multiplier: Number(e.target.value)})}
/>
)}
{(currentCellInput.fieldType === FieldType.superkadi
|| currentCellInput.fieldType === FieldType.multiplier) && (
<Input
label={Locale.rulesetsPage.maxMultiplesPlaceholder}
fluid={true}
type={"number"}
value={currentCellInput.maxMultiples}
onChange={(e) => updateCurrentCellInput({
...currentCellInput,
maxMultiples: Number(e.target.value),
})}
/>
)}
{(currentCellInput.fieldType === FieldType.bool
|| currentCellInput.fieldType === FieldType.superkadi
|| currentCellInput.fieldType === FieldType.number) && (
<Input
label={Locale.rulesetsPage.valuePlaceholder}
fluid={true}
type={"number"}
value={currentCellInput.score}
onChange={(e) => updateCurrentCellInput(
{...currentCellInput, score: Number(e.target.value)})}
/>
)}
</Grid.Column>
<Grid.Column
width={4}
textAlign={"right"}
>
<Button
onClick={() => {
if (onAddCell) {
onAddCell(currentCellInput, id)
}
}}
>
{Locale.rulesetsPage.addCell}
</Button>
</Grid.Column>
</Grid.Row>
<Table.Cell>
<Input
fluid={true}
value={currentCellInput.label}
placeholder={Locale.rulesetsPage.cellNamePlaceholder}
onChange={(e) =>
updateCurrentCellInput({
...currentCellInput,
label: e.target.value,
})
}
/>
</Table.Cell>
<Table.Cell style={{overflow: "visible"}}>
<Dropdown
basic={true}
placeholder={Locale.rulesetsPage.fieldTypePlaceholder}
onChange={(e, c) => onChangeFieldTypeDropdown(c.value as FieldType)}
options={fieldTypeOptions}
button={true}
/>
</Table.Cell>
<Table.Cell>
<Grid divided={true}>
<Grid.Column width={12}>
{currentCellInput.fieldType === undefined ||
currentCellInput.fieldType === FieldType.number ? (
Locale.rulesetsPage.extraCellSettingsInfo
) : (
<List>
{(currentCellInput.fieldType === FieldType.bool ||
currentCellInput.fieldType ===
FieldType.superkadi) && (
<List.Item>
<Input
label={Locale.rulesetsPage.valuePlaceholder}
fluid={true}
type={"number"}
value={currentCellInput.score}
onChange={(e) =>
updateCurrentCellInput({
...currentCellInput,
score: Number(e.target.value),
})
}
/>
</List.Item>
)}
{currentCellInput.fieldType ===
FieldType.multiplier && (
<List.Item>
<Input
label={
Locale.rulesetsPage
.multiplierPlaceholder
}
fluid={true}
type={"number"}
value={currentCellInput.multiplier}
onChange={(e) =>
updateCurrentCellInput({
...currentCellInput,
multiplier: Number(e.target.value),
})
}
/>
</List.Item>
)}
{(currentCellInput.fieldType === FieldType.superkadi ||
currentCellInput.fieldType ===
FieldType.multiplier) && (
<List.Item>
<Input
label={
Locale.rulesetsPage
.maxMultiplesPlaceholder
}
fluid={true}
type={"number"}
value={currentCellInput.maxMultiples}
onChange={(e) =>
updateCurrentCellInput({
...currentCellInput,
maxMultiples: Number(
e.target.value
),
})
}
/>
</List.Item>
)}
</List>
)}
</Grid.Column>
<Grid.Column width={4}>
<Button
basic={true}
floated={"right"}
disabled={
currentCellInput.label === "" ||
!currentCellInput.fieldType
}
icon={"add"}
onClick={handleAddCell}
/>
</Grid.Column>
</Grid>
</Table.Cell>
</Table.Row>
)}
<Table.Row>
<Table.Cell
colSpan={"3"}
textAlign={"center"}
>
<Table.Cell colSpan={"3"} textAlign={"center"}>
{subText}
</Table.Cell>
</Table.Row>
@@ -204,4 +249,4 @@ const RulesetBlockTable: React.FunctionComponent<RulesetBlockTableProps> = (prop
);
};
export default RulesetBlockTable;
export default RulesetBlockTable;

View File

@@ -1,31 +1,82 @@
import {BoolCellDef, CellDef, MultiplierCellDef, SuperkadiCellDef} from "../Services/RulesetSchemaDto";
import {
BoolCellDef,
CellDef,
MultiplierCellDef,
NumberCellDef,
SuperkadiCellDef,
} from "../Services/RulesetSchemaDto";
import React, {useContext} from "react";
import UserContext from "../Contexts/UserContext";
import {Table} from "semantic-ui-react";
import {Button, Grid, List, Table} from "semantic-ui-react";
import {FieldType} from "../enums";
interface TableCellRowProps {
id: string;
blockId: string;
cellDef: CellDef;
editable?: boolean;
removeCell?: (cellId: string, blockId: string) => any;
}
const RulesetCellTableRow: React.FunctionComponent<TableCellRowProps> = (props) => {
const {id, cellDef} = props;
const {blockId, cellDef, editable} = props;
const {strings: Locale} = useContext(UserContext);
const displayValue = (cellDef as MultiplierCellDef).multiplier ??
(cellDef as SuperkadiCellDef | BoolCellDef).score ?? Locale.general.nA;
return (
<Table.Row>
<Table.Cell key={id}>
{cellDef.label}
</Table.Cell>
<Table.Cell key={cellDef.label}>{cellDef.label}</Table.Cell>
<Table.Cell>{Locale.rulesetsPage[cellDef.fieldType]}</Table.Cell>
<Table.Cell>
{Locale.rulesetsPage[cellDef.fieldType]}
</Table.Cell>
<Table.Cell disabled={displayValue === Locale.general.nA}>
{displayValue}
<Grid>
<Grid.Column width={12}>
<List>
{(cellDef as MultiplierCellDef).multiplier && (
<List.Item>
{Locale.rulesetsPage.multiplierPlaceholder +
": " +
(cellDef as MultiplierCellDef).multiplier}
</List.Item>
)}
{(cellDef as BoolCellDef | SuperkadiCellDef).score && (
<List.Item>
{Locale.rulesetsPage.valuePlaceholder +
": " +
(cellDef as BoolCellDef | SuperkadiCellDef).score}
</List.Item>
)}
{(cellDef as MultiplierCellDef).maxMultiples && (
<List.Item>
{Locale.rulesetsPage.maxMultiplesPlaceholder +
": " +
(cellDef as MultiplierCellDef).maxMultiples}
</List.Item>
)}
{(cellDef as SuperkadiCellDef).maxSuperkadis && (
<List.Item>
{Locale.rulesetsPage.maxSuperkadisPlaceholder +
": " +
(cellDef as SuperkadiCellDef).maxSuperkadis}
</List.Item>
)}
<List.Item disabled={true}>
{cellDef.fieldType === FieldType.number && (
<p>{Locale.general.nA}</p>
)}
</List.Item>
</List>
</Grid.Column>
{editable && (
<Grid.Column textAlign={"right"}>
<Button
size={"mini"}
color={"red"}
icon={"minus"}
onClick={() => props.removeCell?.(cellDef.label, blockId)}
/>
</Grid.Column>
)}
</Grid>
</Table.Cell>
</Table.Row>
);
};
export default RulesetCellTableRow;
export default RulesetCellTableRow;

View File

@@ -1,13 +1,6 @@
import React, {useContext} from "react";
import {
BlockDef,
BoolCellDef,
CellDef,
MultiplierCellDef,
RulesetSchemaDto,
SuperkadiCellDef
} from "../Services/RulesetSchemaDto";
import {Header, List, Table, TableBody} from "semantic-ui-react";
import {CellDef, RulesetSchemaDto} from "../Services/RulesetSchemaDto";
import {Table} from "semantic-ui-react";
import UserContext from "../Contexts/UserContext";
import Loading from "./Loading";
import RulesetBlockTable from "./RulesetBlockTable";
@@ -17,19 +10,20 @@ interface RulesetDisplayPanelProps {
loading: boolean;
editable?: boolean;
onAddCell?: (cellDef: CellDef, blockId: string) => any;
removeBlock?: (blockId: string) => any;
removeCell?: (cellId: string, blockId: string) => any;
}
const RulesetDisplayPanel: React.FunctionComponent<RulesetDisplayPanelProps> = (props) => {
const {ruleset, loading, editable, onAddCell} = props;
const {ruleset, loading, editable, onAddCell, removeBlock, removeCell} = props;
const {strings: Locale} = useContext(UserContext);
if (loading) {
return <Loading/>;
}
else {
return <Loading />;
} else {
return (
<>
<Table attached={true} celled={true}>
<Table fixed={true} attached={true} celled={true}>
<Table.Header>
<Table.Row>
<Table.HeaderCell>
@@ -39,29 +33,29 @@ const RulesetDisplayPanel: React.FunctionComponent<RulesetDisplayPanelProps> = (
{Locale.rulesetsPage.fieldTypeHeader}
</Table.HeaderCell>
<Table.HeaderCell>
{Locale.rulesetsPage.fieldValueHeader}
{Locale.rulesetsPage.fieldAttributesHeader}
</Table.HeaderCell>
</Table.Row>
</Table.Header>
</Table>
{Object.entries(ruleset.blocks).length > 0 ?
Object.entries(ruleset.blocks).map(idAndBlock => (
{Object.entries(ruleset.blocks).length > 0 ? (
Object.entries(ruleset.blocks).map((idAndBlock) => (
<RulesetBlockTable
key={idAndBlock[0]}
id={idAndBlock[0]}
blockDef={idAndBlock[1]}
editable={editable}
onAddCell={onAddCell}
removeBlock={removeBlock}
removeCell={removeCell}
/>
)) : (
<RulesetBlockTable
id={"noBlocks"}
blockDef={null}
/>
))
) : (
<RulesetBlockTable id={"noBlocks"} blockDef={null} />
)}
</>
);
}
};
export default RulesetDisplayPanel;
export default RulesetDisplayPanel;

View File

@@ -1,5 +1,5 @@
import React, {ReactElement} from "react";
import {Container, Grid, Header, List, Segment} from "semantic-ui-react";
import {Button, Grid, Header, Modal} from "semantic-ui-react";
import UserContext from "../Contexts/UserContext";
import KadiStatsService from "../Services/KadiStatsService";
import RulesetList from "./RulesetList";
@@ -10,10 +10,13 @@ import CreateRulesetPanel from "./CreateRulesetPanel";
interface RulesetsPageProps {}
interface RulesetsPageState {
unsavedChanges: boolean;
loading: boolean;
addingNewRuleset: boolean;
rulesets: RulesetSchemaDto[];
selectedRulesetIndex: number;
modalContinueCallback: () => any;
showWarningModal: boolean;
}
class RulesetsPage extends React.Component<RulesetsPageProps, RulesetsPageState> {
@@ -21,6 +24,9 @@ class RulesetsPage extends React.Component<RulesetsPageProps, RulesetsPageState>
super(props);
this.state = {
modalContinueCallback: () => {},
showWarningModal: false,
unsavedChanges: false,
loading: true,
addingNewRuleset: false,
rulesets: [],
@@ -38,28 +44,48 @@ class RulesetsPage extends React.Component<RulesetsPageProps, RulesetsPageState>
}
onRulesetSelect(newRulesetId: string | "addNewRuleset") {
if (newRulesetId === "addNewRuleset" || !this.state.unsavedChanges) {
this.selectRuleset(newRulesetId);
}
else if (this.state.unsavedChanges) {
this.warn(() => this.selectRuleset(newRulesetId));
}
}
warn(callback: () => any) {
this.setState({showWarningModal: true, modalContinueCallback: callback});
}
selectRuleset(newRulesetId: string | "addNewRuleset") {
if (newRulesetId === "addNewRuleset") {
this.setState({addingNewRuleset: true});
}
else {
} else {
this.setState({
unsavedChanges: false,
addingNewRuleset: false,
selectedRulesetIndex: this.state.rulesets.findIndex(item => item.label === newRulesetId)
selectedRulesetIndex: this.state.rulesets.findIndex(
(item) => item.label === newRulesetId
),
});
}
}
submitNewRuleset(ruleset: RulesetSchemaDto) {
onWarningModalActionClick() {
const oldContinueCallback = this.state.modalContinueCallback;
this.setState({showWarningModal: false, modalContinueCallback: () => {}}, () =>
oldContinueCallback()
);
}
onWarningModalClose() {
this.setState({showWarningModal: false, modalContinueCallback: () => {}});
}
render(): ReactElement {
const Locale = this.context.strings;
return (
<>
<Header size={"huge"}>
{Locale.rulesetsPage.title}
</Header>
<Header size={"huge"}>{Locale.rulesetsPage.title}</Header>
<Grid>
<Grid.Row>
<Grid.Column width={4}>
@@ -67,13 +93,14 @@ class RulesetsPage extends React.Component<RulesetsPageProps, RulesetsPageState>
onItemChange={(r) => this.onRulesetSelect(r)}
selectedItemIndex={this.state.selectedRulesetIndex}
creatingRuleset={this.state.addingNewRuleset}
rulesetNames={this.state.rulesets.map(ruleset => ruleset.label)}
rulesetNames={this.state.rulesets.map((ruleset) => ruleset.label)}
/>
</Grid.Column>
<Grid.Column width={12}>
{this.state.addingNewRuleset ? (
<CreateRulesetPanel
onSubmitRuleset={(r) => this.submitNewRuleset(r)}
onRulesetSave={() => this.setState({unsavedChanges: false})}
onRulesetChange={() => this.setState({unsavedChanges: true})}
/>
) : (
<RulesetDisplayPanel
@@ -84,10 +111,30 @@ class RulesetsPage extends React.Component<RulesetsPageProps, RulesetsPageState>
</Grid.Column>
</Grid.Row>
</Grid>
<Modal
open={this.state.showWarningModal}
dimmer={"inverted"}
size={"tiny"}
onClose={() => this.onWarningModalClose()}
>
<Modal.Header>Discard ruleset?</Modal.Header>
<Modal.Content>Are you sure you want to discard the current ruleset? Any changes you've made will be lost.</Modal.Content>
<Modal.Actions>
<Button color="black" onClick={() => this.onWarningModalClose()}>
Return to editing
</Button>
<Button
onClick={() => this.onWarningModalActionClick()}
negative={true}
>
Discard ruleset
</Button>
</Modal.Actions>
</Modal>
</>
);
}
}
RulesetsPage.contextType = UserContext;
export default RulesetsPage;
export default RulesetsPage;

View File

@@ -1,20 +1,18 @@
import React, {ReactNode} from "react";
import {
Container,
Dropdown, DropdownItemProps, DropdownMenuProps,
Dropdown,
DropdownItemProps,
Grid,
GridColumn,
GridRow,
Header, Segment,
Header,
} from "semantic-ui-react";
import UserContext from "../Contexts/UserContext";
import {StatsTable} from "./StatsTable";
import KadiStatsService from "../Services/KadiStatsService";
import {ServiceError} from "../errors";
import Loading from "./Loading";
import KadiStatsServiceSingleton from "../Services/KadiStatsService";
import {RulesetSchemaDto} from "../Services/RulesetSchemaDto";
import {StatsDto} from "../Services/StatsDto";
interface StatsPageProps {}
@@ -49,17 +47,14 @@ class StatsPage extends React.Component<StatsPageProps, StatsPageState> {
this.setState({loadingStats: true}, async () => {
try {
await this.loadStatsAndRulesetChoices();
}
catch (e) {
} catch (e) {
if (e instanceof ServiceError) {
this.setState({error: true});
console.log(e);
}
else {
} else {
throw e;
}
}
finally {
} finally {
this.setState({loadingStats: false});
}
});
@@ -70,11 +65,21 @@ class StatsPage extends React.Component<StatsPageProps, StatsPageState> {
const rulesetIds = Object.keys(stats.pStats[0].stats.statsByRuleset);
const schemas: Record<string, RulesetSchemaDto> = {};
for (const rulesetId of rulesetIds) {
schemas[rulesetId] = await KadiStatsService.getRulesetById(rulesetId);
schemas[rulesetId] = await KadiStatsService.getRulesetById(
rulesetId
);
}
const rulesetChoices = rulesetIds.map(rulesetId =>
({key: rulesetId, value: rulesetId, text: schemas[rulesetId].label}));
this.setState({stats, rulesetChoices, rulesetSchemas: schemas, selectedRulesetId: rulesetIds[0]});
const rulesetChoices = rulesetIds.map((rulesetId) => ({
key: rulesetId,
value: rulesetId,
text: schemas[rulesetId].label,
}));
this.setState({
stats,
rulesetChoices,
rulesetSchemas: schemas,
selectedRulesetId: rulesetIds[0],
});
}
handleError(error: any): void {
@@ -102,23 +107,33 @@ class StatsPage extends React.Component<StatsPageProps, StatsPageState> {
loading={this.state.loadingStats}
selection={true}
options={this.state.rulesetChoices}
defaultValue={this.state.rulesetChoices[0]?.value}
onChange={(e, choice) => this.changeRulesetSelection(choice.value as string)}
defaultValue={
this.state.rulesetChoices[0]?.value
}
onChange={(e, choice) =>
this.changeRulesetSelection(
choice.value as string
)
}
/>
</GridColumn>
</GridRow>
<GridRow columns={1} style={{overflow: "auto"}}>
<GridColumn>
{this.state.error ?
<p>{Locale.general.databaseError}</p> :
this.state.loadingStats ?
<Loading/> : (
<StatsTable
data={this.state.stats}
displayedRulesetSchema={this.state.rulesetSchemas[this.state.selectedRulesetId]}
/>
)
}
{this.state.error ? (
<p>{Locale.general.databaseError}</p>
) : this.state.loadingStats ? (
<Loading />
) : (
<StatsTable
data={this.state.stats}
displayedRulesetSchema={
this.state.rulesetSchemas[
this.state.selectedRulesetId
]
}
/>
)}
</GridColumn>
</GridRow>
</Grid>
@@ -128,4 +143,4 @@ class StatsPage extends React.Component<StatsPageProps, StatsPageState> {
}
StatsPage.contextType = UserContext;
export default StatsPage;
export default StatsPage;

View File

@@ -0,0 +1,28 @@
import {RulesetSchemaDto} from "./RulesetSchemaDto";
class KadiPlayerService {
private readonly players: PlayerDto[] = [];
constructor() {}
async getPlayerById(id: string): Promise<PlayerDto> {
if (!this.rulesets[id]) {
await this.loadRulesetById(id);
}
return this.rulesets[id];
}
async refreshStats(): Promise<void> {
await this.loadStats();
}
async getAllRulesets(): Promise<RulesetSchemaDto[]> {
if (!this.allRulesetsLoaded) {
await this.loadAllRulesets();
}
return Object.values(this.rulesets);
}
}
const KadiStatsServiceSingleton = new KadiStatsService();
export default KadiStatsServiceSingleton;

View File

@@ -3,10 +3,6 @@ import {SERVER_BASE_NAME} from "../index";
import {StatsDto} from "./StatsDto";
import {RulesetSchemaDto} from "./RulesetSchemaDto";
const dummyRulesets = [
{"id":"DEFAULT_RULESET","label":"Standard Kadi Rules (en)","blocks":{"top":{"label":"Upper","hasBonus":true,"bonusScore":35,"bonusFor":63,"cells":{"aces":{"fieldType":"multiplierField","label":"Aces","multiplier":1,"maxMultiples":5},"twos":{"fieldType":"multiplierField","label":"Twos","multiplier":2,"maxMultiples":5},"threes":{"fieldType":"multiplierField","label":"Threes","multiplier":3,"maxMultiples":5},"fours":{"fieldType":"multiplierField","label":"Fours","multiplier":4,"maxMultiples":5},"fives":{"fieldType":"multiplierField","label":"Fives","multiplier":5,"maxMultiples":5},"sixes":{"fieldType":"multiplierField","label":"Sixes","multiplier":6,"maxMultiples":5}}},"bottom":{"label":"Lower","hasBonus":false,"cells":{"threeKind":{"fieldType":"numberField","label":"Three of a Kind"},"fourKind":{"fieldType":"numberField","label":"Four of a Kind"},"fullHouse":{"fieldType":"boolField","label":"Full House","score":25},"smlStraight":{"fieldType":"boolField","label":"Small Straight","score":30},"lgSraight":{"fieldType":"boolField","label":"Large Straight","score":40},"superkadi":{"fieldType":"superkadiField","label":"Super Kadis","score":50,"maxSuperkadis":5},"chance":{"fieldType":"numberField","label":"Chance"}}}}}
];
class KadiStatsService {
private userStats: StatsDto | null = null;
private rulesets: Record<string, RulesetSchemaDto> = {};
@@ -19,6 +15,18 @@ class KadiStatsService {
return this.userStats;
}
private async loadAllRulesets(): Promise<void> {
const rulesetSchemas = (await axios.get(SERVER_BASE_NAME + "/api/rulesets/")).data.rulesets as RulesetSchemaDto[];
console.log(rulesetSchemas);
rulesetSchemas.forEach(schema => this.rulesets[schema.id] = schema);
this.allRulesetsLoaded = true;
}
private async loadRulesetById(id: string): Promise<void> {
const rulesetSchema = (await axios.get(SERVER_BASE_NAME + "/api/ruleset/" + id)).data as RulesetSchemaDto;
this.rulesets[rulesetSchema.id] = rulesetSchema;
}
async getStats(): Promise<StatsDto> {
if (this.userStats) {
return this.userStats;
@@ -29,14 +37,10 @@ class KadiStatsService {
}
async getRulesetById(id: string): Promise<RulesetSchemaDto> {
if (this.rulesets[id]) {
return this.rulesets[id];
}
else {
const rulesetSchema = (await axios.get(SERVER_BASE_NAME + "/api/ruleset/" + id)).data as RulesetSchemaDto;
this.rulesets[rulesetSchema.id] = rulesetSchema;
return this.rulesets[rulesetSchema.id];
if (!this.rulesets[id]) {
await this.loadRulesetById(id);
}
return this.rulesets[id];
}
async refreshStats(): Promise<void> {
@@ -44,16 +48,10 @@ class KadiStatsService {
}
async getAllRulesets(): Promise<RulesetSchemaDto[]> {
return dummyRulesets as RulesetSchemaDto[];
if (this.allRulesetsLoaded) {
return Object.values(this.rulesets);
}
else {
const rulesetSchemas = (await axios.get(SERVER_BASE_NAME + "/api/rulesets/")).data as RulesetSchemaDto[];
rulesetSchemas.forEach(schema => this.rulesets[schema.id] = schema);
this.allRulesetsLoaded = true;
return rulesetSchemas;
if (!this.allRulesetsLoaded) {
await this.loadAllRulesets();
}
return Object.values(this.rulesets);
}
}

View File

@@ -0,0 +1,6 @@
interface PlayerMongoData {
id: string;
nick: string;
}
export default PlayerDto;

View File

@@ -1,3 +1,5 @@
import {FieldType} from "../enums";
export interface RulesetSchemaDto {
id: string;
label: string;

View File

@@ -66,7 +66,7 @@ export const IntlStrings = {
blocksHeader: "Blocks",
fieldLabelHeader: "Label",
fieldTypeHeader: "Type",
fieldValueHeader: "Value",
fieldAttributesHeader: "Attributes",
superkadiField: "Superkadi",
multiplierField: "Multiplier Cell",
boolField: "Boolean Cell",
@@ -84,9 +84,12 @@ export const IntlStrings = {
cellNamePlaceholder: "My New Cell",
fieldTypePlaceholder: "Select a field type",
multiplierPlaceholder: "Multiplier",
maxMultiplesPlaceholder: "Max of kind",
maxMultiplesPlaceholder: "Max Multiples",
maxSuperkadisPlaceholder: "Max Superkadis",
valuePlaceholder: "Value",
submit: "Submit",
save: "Save",
extraCellSettingsInfo: "No attributes available",
newCell: "New Cell",
},
friendsPage: {
title: "Friends",
@@ -135,7 +138,7 @@ export const IntlStrings = {
blocksHeader: "Blöcke",
fieldLabelHeader: "Name",
fieldTypeHeader: "Typ",
fieldValueHeader: "Wert",
fieldAttributesHeader: "Eigenschaften",
superkadiField: "Superkadi",
multiplierField: "Multiplikator-Feld",
boolField: "Bool'sches Feld",
@@ -155,8 +158,11 @@ export const IntlStrings = {
fieldTypePlaceholder: "Feldtyp auswählen",
multiplierPlaceholder: "Multiplikator",
maxMultiplesPlaceholder: "Max. Anzahl",
maxSuperkadisPlaceholder: "Max. Anzahl",
valuePlaceholder: "Wert",
submit: "Fertig",
save: "Speichern",
extraCellSettingsInfo: "Keine Eigenschaften vorhanden",
newCell: "Neues Feld",
},
friendsPage: {
title: "Freunde",
@@ -205,7 +211,7 @@ export const IntlStrings = {
blocksHeader: "===TRANSLATE ME===",
fieldLabelHeader: "===TRANSLATE ME===",
fieldTypeHeader: "===TRANSLATE ME===",
fieldValueHeader: "===TRANSLATE ME===",
fieldAttributesHeader: "===TRANSLATE ME===",
superkadiField: "===TRANSLATE ME===",
multiplierField: "===TRANSLATE ME===",
boolField: "===TRANSLATE ME===",