diff --git a/.env b/.env new file mode 100644 index 0000000000000000000000000000000000000000..191b674a09d3c64e304abb26cfc22b19a8f660d4 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +REACT_APP_API_URL = "http://localhost:5000" \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c47346eb24c76e3ebdf37ae12b226e67b04488da..cf95bf2d9457edec5a21ba6b736ac979bd6135a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -939,6 +939,11 @@ "@hapi/hoek": "6.x.x" } }, + "@icons/material": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz", + "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==" + }, "@jest/console": { "version": "24.7.1", "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.7.1.tgz", @@ -7950,6 +7955,11 @@ "object-visit": "^1.0.0" } }, + "material-colors": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz", + "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==" + }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -10078,6 +10088,19 @@ "whatwg-fetch": "3.0.0" } }, + "react-color": { + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/react-color/-/react-color-2.17.3.tgz", + "integrity": "sha512-1dtO8LqAVotPIChlmo6kLtFS1FP89ll8/OiA8EcFRDR+ntcK+0ukJgByuIQHRtzvigf26dV5HklnxDIvhON9VQ==", + "requires": { + "@icons/material": "^0.2.4", + "lodash": "^4.17.11", + "material-colors": "^1.2.1", + "prop-types": "^15.5.10", + "reactcss": "^1.2.0", + "tinycolor2": "^1.4.1" + } + }, "react-cookie": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-4.0.0.tgz", @@ -10311,6 +10334,14 @@ "workbox-webpack-plugin": "4.2.0" } }, + "reactcss": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz", + "integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==", + "requires": { + "lodash": "^4.0.1" + } + }, "read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -11757,6 +11788,11 @@ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, + "tinycolor2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz", + "integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g=" + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", diff --git a/package.json b/package.json index 6e710f05834f66edfcfe93fa11e6d53ad52e9239..cf145f8645b5f565e23c6d1bba0a60d1e6ff9062 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "leaflet": "^1.5.1", "leaflet-draw": "^1.0.4", "react": "^16.8.6", + "react-color": "^2.17.3", "react-cookie": "^4.0.0", "react-dom": "^16.8.6", "react-leaflet": "^2.3.0", diff --git a/public/index.html b/public/index.html index 8b2f9d1cb04b63987cab6a5b0645beb0aeafff57..421a898f120b5f1f39e1b60e6f6db3ef91e754f1 100644 --- a/public/index.html +++ b/public/index.html @@ -23,6 +23,7 @@ </head> <body> <noscript>You need to enable JavaScript to run this app.</noscript> + <div id="tasklist"></div> <div id="root"></div> <div id="form"></div> <!-- diff --git a/src/App.css b/src/App.css index d09ddd38e9af220ac7f8af30cf562952e241034e..8cf31418c5cc17db9852182200e9fc8dce43451c 100644 --- a/src/App.css +++ b/src/App.css @@ -59,6 +59,8 @@ div.fade-main { margin: auto; text-align: center; background-color: rgba(0, 0, 0, 0.85); + color: white; + overflow: scroll; } div.sticky { @@ -214,3 +216,19 @@ div.login button:hover { max-width: 500px; padding: 10px; } + +.tasklist { + position: absolute; + margin-top: 50px; + z-index: 2020; + background-color: #ffffff80; +} + +.tasklist-item { + margin: 20px; +} + +.task-form { + display: flex; + flex-direction: column; +} diff --git a/src/components/EditGameForm.js b/src/components/EditGameForm.js index fbc37f8995faaaec41f56c51a6b1b6cdd6ba5d4b..55247e638269ca8cb9d888d972cc9ec75aa08857 100644 --- a/src/components/EditGameForm.js +++ b/src/components/EditGameForm.js @@ -1,23 +1,36 @@ -import React from "react"; +import React, { Fragment } from "react"; import ReactDOM from "react-dom"; import { Map, TileLayer } from "react-leaflet"; +import { SketchPicker } from "react-color"; +import reactCSS from "reactcss"; export class EditGameForm extends React.Component { constructor(props) { super(props); this.state = { + zoom: 13, gamename: "", description: "", startDate: "", startTime: "", endDate: "", endTime: "", - zoom: 13, + map: "", mapCenter: { lat: 62.2416479, lng: 25.7597186 - } + }, + factionNameInput: "", // >= 2 chars + factionPasswordInput: "", // >= 3 chars + factionColorInput: "#852222", + factions: [], + objectivePointDescriptionInput: "", // >= 7 + objectivePointMultiplierInput: "", // number + objectivePoints: [], + capture_time: 300, + confirmation_time: 60, + displayColorPicker: false }; this.handleMapDrag = this.handleMapDrag.bind(this); @@ -32,6 +45,143 @@ export class EditGameForm extends React.Component { this.setState({ [name]: value }); }; + handleFactionAdd = e => { + e.preventDefault(); + + if ( + this.state.factionNameInput === "" || + this.state.factionPasswordInput === "" + ) { + return alert("Faction needs a name and password"); + } + + if ( + this.state.factions.findIndex( + faction => faction.factionName === this.state.factionNameInput + ) !== -1 + ) { + return alert( + "Faction " + this.state.factionNameInput + " already exists" + ); + } + + this.setState(state => { + let factions = state.factions; + factions.push({ + factionName: this.state.factionNameInput, + factionPassword: this.state.factionPasswordInput, + colour: this.state.factionColorInput, + multiplier: 1 + }); + return { + factions: factions, + factionNameInput: "", + factionPasswordInput: "" + }; + }); + }; + + removeFaction(factionName) { + this.setState(state => { + let factions = state.factions; + const index = factions.findIndex( + faction => faction.factionName === factionName + ); + + if (index !== -1) { + factions.splice(index, 1); + } else { + console.log("Faction is not in the faction list"); + } + + return factions; + }); + } + + handleObjectivePointAdd = e => { + e.preventDefault(); + + if ( + this.state.objectivePointDescriptionInput === "" || + this.state.objectivePointMultiplierInput === "" + ) { + return alert("Capture point needs an ID and multiplier"); + } + + if ( + this.state.objectivePoints.findIndex( + point => + point.objectivePointDescription === + this.state.objectivePointDescriptionInput + ) !== -1 + ) { + return alert( + "Capture point " + + this.state.objectivePointDescriptionInput + + " already exists" + ); + } + + this.setState(state => { + let objectivePoints = state.objectivePoints; + objectivePoints.push({ + objectivePointDescription: this.state.objectivePointDescriptionInput, + objectivePointMultiplier: parseFloat( + this.state.objectivePointMultiplierInput + ) + }); + + return { + objectivePoints: objectivePoints, + objectivePointDescriptionInput: "", + objectivePointMultiplierInput: "" + }; + }); + }; + + removeObjectivePoint(objectivePointId) { + this.setState(state => { + let objectivePoints = state.objectivePoints; + const index = objectivePoints.findIndex( + point => point.objectivePointDescription === objectivePointId + ); + + if (index !== -1) { + objectivePoints.splice(index, 1); + } else { + console.log("Objective point is not in the point list"); + } + + return objectivePoints; + }); + } + + handleGameDeletion = e => { + e.preventDefault(); + + let token = sessionStorage.getItem("token"); + + if (window.confirm("Are you sure you want to delete this game")) { + alert("Game deleted"); + + fetch( + `${process.env.REACT_APP_API_URL}/game/delete/${this.props.gameId}`, + { + method: "DELETE", + headers: { + Authorization: "Bearer " + token + } + } + ) + .then(result => result.json()) + .then(result => { + console.log(result); + this.handleView(); + }) + .catch(error => console.log(error)); + } + }; + // show/hide this form handleView = e => { this.props.toggleView(this.props.view); @@ -57,20 +207,44 @@ export class EditGameForm extends React.Component { }; handleGameSave = e => { + e.preventDefault(); + let startDate = this.state.startDate + "T" + this.state.startTime + ":00.000Z"; let endDate = this.state.endDate + "T" + this.state.endTime + ":00.000Z"; - const gameObject = { + let objectivePoints = this.state.objectivePoints; + + // objective points are not allowed if the game has no factions + if (this.state.factions.length === 0) { + objectivePoints = []; + } + + // Object the form sends to server + let gameObject = { name: this.state.gamename, desc: this.state.description, map: "", startdate: startDate, enddate: endDate, - center: this.state.mapCenter + center: this.state.mapCenter, + factions: this.state.factions, + objective_points: objectivePoints }; - e.preventDefault(); + // Add node settings to the game if the game has objective points + if (objectivePoints.length > 0) { + gameObject.nodesettings = { + node_settings: { + capture_time: this.state.capture_time, + confirmation_time: this.state.confirmation_time, + owner: 0, + capture: 0, + buttons_available: 16, + heartbeat_interval: 60 + } + }; + } let token = sessionStorage.getItem("token"); @@ -84,7 +258,13 @@ export class EditGameForm extends React.Component { }, body: JSON.stringify(gameObject) }) - .then(res => res.json()) + .then(res => { + if (!res.ok) { + throw Error(res.statusMessage); + } else { + return res.json(); + } + }) .then(result => { alert(result.message); this.handleView(); @@ -104,27 +284,144 @@ export class EditGameForm extends React.Component { getGameInfo(gameId) { fetch(`${process.env.REACT_APP_API_URL}/game/${gameId}`) .then(response => response.json()) - .then(json => this.handleGameInfo(json)) + .then(json => this.setGameInfoToState(json)) .catch(error => console.log(error)); } - handleGameInfo(json) { - this.setState({ - gamename: json.name, - description: json.desc, - startDate: json.startdate.substring(0, 10), - startTime: json.startdate.substring(11, 16), - endDate: json.enddate.substring(0, 10), - endTime: json.enddate.substring(11, 16), - zoom: 13, - mapCenter: { - lat: json.center.lat, - lng: json.center.lng + setGameInfoToState(json) { + let token = sessionStorage.getItem("token"); + + // Get factions and passwordds + fetch( + `${process.env.REACT_APP_API_URL}/game/get-factions/${this.props.gameId}`, + { + method: "GET", + headers: { + Authorization: "Bearer " + token + } } - }); + ) + .then(result => result.json()) + .then(result => { + let factions = result.map(faction => { + return { + factionName: faction.factionName, + factionPassword: faction.factionPassword, + multiplier: 1, + colour: faction.colour + }; + }); + + // Remove objective point's id from the object + let objectivePoints = json.objective_points.map(point => { + return { + objectivePointDescription: point.objectivePointDescription, + objectivePointMultiplier: point.objectivePointMultiplier + }; + }); + + // get node settings if the settings exists in the game + let nodesettings = + json.nodesettings !== null && + json.nodesettings.node_settings !== undefined + ? json.nodesettings.node_settings + : undefined; + + this.setState({ + gamename: json.name, + description: json.desc, + startDate: json.startdate.substring(0, 10), + startTime: json.startdate.substring(11, 16), + endDate: json.enddate.substring(0, 10), + endTime: json.enddate.substring(11, 16), + mapCenter: { + lat: json.center.lat, + lng: json.center.lng + }, + factions: factions, + objectivePoints: objectivePoints, + capture_time: + nodesettings !== undefined + ? json.nodesettings.node_settings.capture_time + : this.state.capture_time, + confirmation_time: + nodesettings !== undefined + ? json.nodesettings.node_settings.confirmation_time + : this.state.confirmation_time + }); + }) + .catch(error => console.log(error)); } render() { + let factions = []; + for (let i = 0; i < this.state.factions.length; i++) { + const faction = this.state.factions[i]; + factions.push( + <li key={faction.factionName}> + <div style={{ color: faction.colour }}> + {faction.factionName} : {faction.factionPassword} + </div> + <button + type="button" + onClick={() => this.removeFaction(faction.factionName)} + > + Remove + </button> + </li> + ); + } + + let objectivePoints = []; + for (let i = 0; i < this.state.objectivePoints.length; i++) { + const point = this.state.objectivePoints[i]; + objectivePoints.push( + <li key={point.objectivePointDescription}> + <div> + {point.objectivePointDescription} : {point.objectivePointMultiplier} + </div> + <button + type="button" + onClick={() => + this.removeObjectivePoint(point.objectivePointDescription) + } + > + Remove + </button> + </li> + ); + } + + const styles = reactCSS({ + default: { + color: { + width: "36px", + height: "14px", + borderRadius: "2px", + background: `${this.state.factionColorInput}` + }, + swatch: { + padding: "5px", + background: "#fff", + borderRadius: "1px", + boxShadow: "0 0 0 1px rgba(0,0,0,.1)", + display: "inline-block", + cursor: "pointer" + }, + popover: { + position: "absolute", + zIndex: "2" + }, + cover: { + position: "fixed", + top: "0px", + right: "0px", + bottom: "0px", + left: "0px" + } + } + }); + return ReactDOM.createPortal( <div className="fade-main"> <div className="sticky"> @@ -137,85 +434,208 @@ export class EditGameForm extends React.Component { </span> </div> <div className=""> - <form onSubmit={this.handleGameSave}> - <h1>Demo Game Creation</h1> - <br /> - <input - placeholder="Game name" - name="gamename" - value={this.state.gamename} - onChange={this.handleChange} - id="editGameNameInput" - /> - <br /> - <input - placeholder="Description" - type="text" - name="description" - value={this.state.description} - onChange={this.handleChange} - id="editGameDescriptionInput" - /> - <br /> - <label className="">Start:</label> - <input - className="formDate" - type="date" - name="startDate" - value={this.state.startDate} - onChange={this.handleChange} - id="editGameDateStartInput" - /> - <input - className="formTime" - type="time" - name="startTime" - value={this.state.startTime} - onChange={this.handleChange} - rid="editGameTimeStartInput" - /> - <br /> - <label className="">End:</label> - <input - className="formDate" - type="date" - name="endDate" - value={this.state.endDate} - onChange={this.handleChange} - min={this.state.startDate} - id="editGameDateEndInput" - /> - <input - className="formTime" - type="time" - name="endTime" - value={this.state.endTime} - onChange={this.handleChange} - id="editGameTimeEndInput" - /> - <br /> - <label>Map things</label> - <br /> - <Map - id="editGameCenterMap" - className="" - center={[this.state.mapCenter.lat, this.state.mapCenter.lng]} - zoom={this.state.zoom} - style={{ height: "400px", width: "400px" }} - onmoveend={this.handleMapDrag} - onzoomend={this.handleMapScroll} + <form id="gameEditForm" onSubmit={this.handleGameSave} /> + <form id="factionAddFrom" onSubmit={this.handleFactionAdd} /> + <form id="gameDeletionForm" onSubmit={this.handleGameDeletion} /> + <form + id="objectivePointAddFrom" + onSubmit={this.handleObjectivePointAdd} + /> + + <h1>Demo Game Editor</h1> + <br /> + <input + placeholder="Game name" + name="gamename" + value={this.state.gamename} + onChange={this.handleChange} + id="editGameNameInput" + form="gameEditForm" + required + /> + <br /> + <input + placeholder="Description" + type="text" + name="description" + value={this.state.description} + onChange={this.handleChange} + id="editGameDescriptionInput" + form="gameEditForm" + required + /> + <br /> + <label className="">Start:</label> + <input + className="formDate" + type="date" + name="startDate" + value={this.state.startDate} + onChange={this.handleChange} + id="editGameDateStartInput" + form="gameEditForm" + required + /> + <input + className="formTime" + type="time" + name="startTime" + value={this.state.startTime} + onChange={this.handleChange} + sid="editGameTimeStartInput" + form="gameEditForm" + required + /> + <br /> + <label className="">End:</label> + <input + className="formDate" + type="date" + name="endDate" + value={this.state.endDate} + onChange={this.handleChange} + min={this.state.startDate} + id="editGameDateEndInput" + form="gameEditForm" + required + /> + <input + className="formTime" + type="time" + name="endTime" + value={this.state.endTime} + onChange={this.handleChange} + id="editGameTimeEndInput" + form="gameEditForm" + required + /> + <br /> + <br /> + + <label>Factions</label> + <br /> + <input + name="factionNameInput" + value={this.state.factionNameInput} + minLength="2" + onChange={this.handleChange} + placeholder="Add new faction" + form="factionAddFrom" + /> + <input + name="factionPasswordInput" + value={this.state.factionPasswordInput} + minLength="3" + onChange={this.handleChange} + placeholder="Faction password" + form="factionAddFrom" + /> + <div + style={styles.swatch} + onClick={() => + this.setState({ + displayColorPicker: !this.state.displayColorPicker + }) + } + > + <div style={styles.color} /> + </div> + {this.state.displayColorPicker && ( + <div + style={styles.cover} + onClick={() => this.setState({ displayColorPicker: false })} > - <TileLayer - attribution="Maanmittauslaitoksen kartta" - url=" https://tiles.kartat.kapsi.fi/taustakartta/{z}/{x}/{y}.jpg" + <SketchPicker + color={this.state.factionColorInput} + onChangeComplete={color => + this.setState({ factionColorInput: color.hex }) + } /> - </Map> - <br /> - <button id="editGameSubmitButton" type="submit"> - Save changes - </button> - <h2>{this.state.errorMsg}</h2> - </form> + </div> + )} + <button type="submit" form="factionAddFrom"> + Add + </button> + <ul>{factions}</ul> + <br /> + <br /> + <label>Objective points</label> + <br /> + <input + name="objectivePointDescriptionInput" + type="number" + value={this.state.objectivePointDescriptionInput} + onChange={this.handleChange} + placeholder="Objective point id" + min="1000000" + form="objectivePointAddFrom" + /> + <input + name="objectivePointMultiplierInput" + type="number" + value={this.state.objectivePointMultiplierInput} + onChange={this.handleChange} + placeholder="Objective point multiplier" + form="objectivePointAddFrom" + /> + <button type="submit" form="objectivePointAddFrom"> + Add + </button> + <ul>{objectivePoints}</ul> + <br /> + <br /> + <label>Node things (set if objective points are in the game)</label> + <br /> + <br /> + <label className="" form="gameEditForm"> + Capture time: + </label> + <input + name="capture_time" + type="number" + value={this.state.capture_time} + form="gameEditForm" + onChange={this.handleChange} + /> + <label className="">Confimation time:</label> + <input + name="confirmation_time" + type="number" + value={this.state.confirmation_time} + form="gameEditForm" + onChange={this.handleChange} + /> + <br /> + <br /> + <label>Map things</label> + <br /> + <Map + id="editGameCenterMap" + className="" + center={[this.state.mapCenter.lat, this.state.mapCenter.lng]} + zoom={this.state.zoom} + maxZoom="13" + style={{ height: "400px", width: "400px" }} + onmoveend={this.handleMapDrag} + onzoomend={this.handleMapScroll} + > + <TileLayer + attribution="Maanmittauslaitoksen kartta" + url=" https://tiles.kartat.kapsi.fi/taustakartta/{z}/{x}/{y}.jpg" + /> + </Map> + <br /> + <button + style={{ backgroundColor: "red" }} + type="submit" + form="gameDeletionForm" + > + Delete + </button> + <button id="editGameSubmitButton" type="submit" form="gameEditForm"> + Save changes + </button> + <h2>{this.state.errorMsg}</h2> </div> </div>, document.getElementById("form") diff --git a/src/components/GameSidebar.js b/src/components/GameSidebar.js new file mode 100644 index 0000000000000000000000000000000000000000..0511cdc3e9762a1e027a7f180ff8112289e5d311 --- /dev/null +++ b/src/components/GameSidebar.js @@ -0,0 +1,31 @@ +import React from "react"; +import NewGameForm from "./NewGameForm"; +import GameList from "./GameList"; + +export default class GameSidebar extends React.Component { + state = { + form: "" + }; + + toggleView = view => { + this.setState({ + form: view + }); + }; + + render() { + return ( + <div className="game-sidebar"> + <GameList /> + {this.props.loggedIn && ( + <button id="newGameButton" onClick={() => this.toggleView("newgame")}> + New Game + </button> + )} + {this.state.form === "newgame" && ( + <NewGameForm view="" toggleView={() => this.toggleView("")} /> + )} + </div> + ); + } +} diff --git a/src/components/Header.js b/src/components/Header.js index f941d3d6fc83ce7c1cead32a09a5ebead50670da..066b5c08879590f34410982b70174b0906def37b 100644 --- a/src/components/Header.js +++ b/src/components/Header.js @@ -1,11 +1,15 @@ import React from "react"; -import GameList from "./GameList"; +import LoginForm from "./LoginForm"; +import RegisterForm from "./RegisterForm"; +import TaskListButton from "./TaskListButton"; +import GameSidebar from "./GameSidebar"; class Header extends React.Component { state = { form: "", // Popup form (login, register etc.) username: null, - token: null + token: null, + sidebar: false }; // toggles the login/register view @@ -73,6 +77,7 @@ class Header extends React.Component { login </button> )} + {this.state.username && ( <button id="logoutButton" onClick={this.handleLogout}> logout @@ -82,8 +87,31 @@ class Header extends React.Component { <button id="changeLayerButton" onClick={this.props.handleLayerChange}> change layer </button> - <GameList handleGameChange={this.props.handleGameChange} /> + {this.state.username && <TaskListButton />} + <button + id="sidebarButton" + onClick={() => this.setState({ sidebar: !this.state.sidebar })} + > + Tools + </button> + {this.state.sidebar && ( + <GameSidebar loggedIn={this.state.username ? true : false} /> + )} </div> + {this.state.form === "register" && ( + <RegisterForm + view="" + handleState={this.handleState} + toggleView={this.toggleView} + /> + )} + {this.state.form === "login" && ( + <LoginForm + view="" + handleState={this.handleState} + toggleView={this.toggleView} + /> + )} </div> ); } diff --git a/src/components/JoinGameForm.js b/src/components/JoinGameForm.js new file mode 100644 index 0000000000000000000000000000000000000000..7ea0aaf958683a807ee73ed23abe19882f591fe7 --- /dev/null +++ b/src/components/JoinGameForm.js @@ -0,0 +1,41 @@ +import React from "react"; + +export default class JoinGameForm extends React.Component { + constructor(props) { + super(props); + this.state = { + gameJSON: undefined + }; + } + + componentDidMount() { + if (this.props.gameId === undefined) { + alert("game not selected"); + } else { + fetch(`${process.env.REACT_APP_API_URL}/game/${this.props.gameId}`) + .then(result => result.json()) + .then(json => { + this.setState({ + gameJSON: json + }); + }) + .catch(error => console.log(error)); + } + } + + render() { + if (this.state.gameJSON === undefined) { + return false; + } + + return ( + <div> + <form> + <label>Join game: {this.state.gameJSON.name}</label> + <div>{this.state.gameJSON.desc}</div> + <button onClick={() => console.log("clicked")}>Submit</button> + </form> + </div> + ); + } +} diff --git a/src/components/NewGameForm.js b/src/components/NewGameForm.js index e84940d00c6619d3793d5b9cc5746ebf3269e36c..778373289f9a0980b688469baa3e05f409d4c59e 100644 --- a/src/components/NewGameForm.js +++ b/src/components/NewGameForm.js @@ -86,7 +86,12 @@ export class NewGameForm extends React.Component { }) .then(res => res.json()) .then(result => { - this.handleView(); + if (result.code) { + alert(result.message); + } else { + alert(`Game ${this.state.gamename} added`); + this.handleView(); + } }) .catch(error => console.log("Error: ", error)); }; @@ -112,83 +117,98 @@ export class NewGameForm extends React.Component { </span> </div> <div className=""> - <form onSubmit={this.handleGameCreation}> - <h1>Demo Game Creation</h1> - <br /> - <input - placeholder="Game name" - name="gamename" - value={this.state.gamename} - onChange={this.handleChange} - id="newGameNameInput" - /> - <br /> - <input - placeholder="Description" - type="text" - name="description" - value={this.state.description} - onChange={this.handleChange} - id="newGameDescriptionInput" - /> - <br /> - <label className="">Start:</label> - <input - className="formDate" - type="date" - name="startDate" - value={this.state.startDate} - onChange={this.handleChange} - id="newGameDateStartInput" - /> - <input - className="formTime" - type="time" - name="startTime" - onChange={this.handleChange} - id="newGameTimeStartInput" - /> - <br /> - <label className="">End:</label> - <input - className="formDate" - type="date" - name="endDate" - value={this.state.endDate} - onChange={this.handleChange} - min={this.state.startDate} - id="newGameDateEndInput" - /> - <input - className="formTime" - type="time" - name="endTime" - onChange={this.handleChange} - id="newGameTimeEndInput" + <form id="gameCreationForm" onSubmit={this.handleGameCreation} /> + <h1>Demo Game Creation</h1> + <br /> + <input + placeholder="Game name" + name="gamename" + value={this.state.gamename} + onChange={this.handleChange} + id="newGameNameInput" + form="gameCreationForm" + required + /> + <br /> + <input + placeholder="Description" + type="text" + name="description" + value={this.state.description} + onChange={this.handleChange} + id="newGameDescriptionInput" + form="gameCreationForm" + required + /> + <br /> + <label className="">Start:</label> + <input + className="formDate" + type="date" + name="startDate" + value={this.state.startDate} + onChange={this.handleChange} + id="newGameDateStartInput" + form="gameCreationForm" + required + /> + <input + className="formTime" + type="time" + name="startTime" + onChange={this.handleChange} + id="newGameTimeStartInput" + form="gameCreationForm" + required + /> + <br /> + <label className="">End:</label> + <input + className="formDate" + type="date" + name="endDate" + value={this.state.endDate} + onChange={this.handleChange} + min={this.state.startDate} + id="newGameDateEndInput" + form="gameCreationForm" + required + /> + <input + className="formTime" + type="time" + name="endTime" + onChange={this.handleChange} + id="newGameTimeEndInput" + form="gameCreationForm" + required + /> + <br /> + <label>Map things</label> + <br /> + <Map + id="newGameCenterMap" + className="" + center={[this.state.mapCenter.lat, this.state.mapCenter.lng]} + zoom={this.state.zoom} + style={{ height: "400px", width: "400px" }} + onmoveend={this.handleMapDrag} + onzoomend={this.handleMapScroll} + > + <TileLayer + attribution="Maanmittauslaitoksen kartta" + url=" https://tiles.kartat.kapsi.fi/taustakartta/{z}/{x}/{y}.jpg" /> - <br /> - <label>Map things</label> - <br /> - <Map - id="newGameCenterMap" - className="" - center={[this.state.mapCenter.lat, this.state.mapCenter.lng]} - zoom={this.state.zoom} - style={{ height: "400px", width: "400px" }} - onmoveend={this.handleMapDrag} - onzoomend={this.handleMapScroll} - > - <TileLayer - attribution="Maanmittauslaitoksen kartta" - url=" https://tiles.kartat.kapsi.fi/taustakartta/{z}/{x}/{y}.jpg" - /> - </Map> - <br /> - <button id="newGameSubmitButton" type="submit"> - Submit - </button> - <h2>{this.state.errorMsg}</h2> - </form> + </Map> + <br /> + <button + id="newGameSubmitButton" + type="submit" + form="gameCreationForm" + > + Submit + </button> + <h2>{this.state.errorMsg}</h2> </div> </div>, document.getElementById("form") diff --git a/src/components/RegisterForm.js b/src/components/RegisterForm.js index b1357b7c9a45a74653e643ee45368a5df4305c64..832de85fafaa6a87f06247142d87554e17b697b7 100644 --- a/src/components/RegisterForm.js +++ b/src/components/RegisterForm.js @@ -63,7 +63,7 @@ export class RegisterForm extends React.Component { this.props.handleState(result); this.handleView(); } else { - this.handleError(result.errorResponse.message); + this.handleError(result.message); } }, // Note: it's important to handle errors here @@ -83,7 +83,7 @@ export class RegisterForm extends React.Component { componentWillUnmount() { document.removeEventListener("keyup", this.handleEsc); } - + // UNCOMMENT "REQUIRED" FOR PRODUCTION render() { return ( <div className="fade-main"> diff --git a/src/components/TaskItem.js b/src/components/TaskItem.js new file mode 100644 index 0000000000000000000000000000000000000000..620ba85b27a310ef6a284cfd563e88695d5cfa0a --- /dev/null +++ b/src/components/TaskItem.js @@ -0,0 +1,119 @@ +import React from "react"; + +class TaskItem extends React.Component { + constructor(props) { + super(props); + this.state = { + edit: false, + selectedFactionId: "", + factions: [] + }; + } + + componentDidMount() { + this.getFactionlist(this.props.gameId); + } + + onEditClick = e => { + this.setState({ + edit: !this.state.edit + }); + }; + + getFactionlist(gameId) { + fetch(`${process.env.REACT_APP_URL}/game/${gameId}`, { + method: "GET" + }) + .then(result => { + if (!result.ok) { + throw Error(result.responseText); + } else { + return result.json(); + } + }) + .then(result => { + this.setState({ + factions: result.factions, + selectedFactionId: result.factions[0].factionId + }); + }) + .catch(error => console.log(error)); + } + + onSaveSubmit = e => { + e.preventDefault(); + this.props.onSave(this.props.task, this.state.selectedFactionId); + this.setState({ + edit: false + }); + }; + + handleFactionChange = e => { + this.setState({ + selectedFactionId: e.target.value + }); + }; + + onTaskDelete = e => { + e.preventDefault(); + this.props.onDelete(this.props.task.taskId); + this.setState({ + edit: false + }); + }; + + render() { + let factionlistItems = []; + for (let i = 0; i < this.state.factions.length; i++) { + const faction = this.state.factions[i]; + factionlistItems.push( + <option key={faction.factionId} value={faction.factionId}> + {faction.factionName} + </option> + ); + } + + return ( + <div className="tasklist-item"> + <div> + <label>{this.props.task.taskName}</label> + </div> + <div> + <label>{this.props.task.taskDescription}</label> + <br /> + <label> + Faction:{" "} + {this.props.task.faction !== null + ? this.props.task.faction.factionName + : "Every faction"} + </label> + <br /> + {this.props.task.taskWinner !== null && ( + <label>Winner: {this.props.task.taskWinner.factionName}</label> + )} + </div> + {this.props.task.taskIsActive && ( + <button onClick={this.onEditClick}>Edit</button> + )} + {this.state.edit && ( + <form onSubmit={this.onSaveSubmit}> + <select + value={this.state.selectedFactionId} + onChange={e => + this.setState({ selectedFactionId: e.target.value }) + } + > + {factionlistItems} + </select> + <button type="submit">Save</button> + </form> + )} + <button onClick={this.onTaskDelete} style={{ backgroundColor: "red" }}> + Delete + </button> + </div> + ); + } +} + +export default TaskItem; diff --git a/src/components/TaskList.js b/src/components/TaskList.js new file mode 100644 index 0000000000000000000000000000000000000000..fedbc03ae0dbac449f8aa4c7518dcca03597b897 --- /dev/null +++ b/src/components/TaskList.js @@ -0,0 +1,255 @@ +import ReactDOM from "react-dom"; +import React from "react"; +import TaskItem from "./TaskItem"; + +class TaskList extends React.Component { + constructor(props) { + super(props); + this.state = { + taskNameInput: "", // >= 3 + taskDescriptionInput: "", // no limits + tasks: [], + factionlist: [], + selectedFactionId: "" + }; + } + + componentDidMount() { + this.getTasks(this.props.gameId); + this.getFactionlist(this.props.gameId); // TODO: remove if the user is not admin? + } + + getTasks(gameId) { + let token = sessionStorage.getItem("token"); + fetch(`${process.env.REACT_APP_URL}/task/get-tasks/${gameId}`, { + method: "GET", + headers: { + Authorization: "Bearer " + token + } + }) + .then(result => { + if (!result.ok) { + throw Error(result.responseText); + } else { + return result.json(); + } + }) + .then(result => { + this.setState({ + tasks: result + }); + }) + .catch(error => console.log(error)); + } + + getFactionlist(gameId) { + fetch(`${process.env.REACT_APP_URL}/game/${gameId}`, { + method: "GET" + }) + .then(result => { + if (!result.ok) { + throw Error(result.responseText); + } else { + return result.json(); + } + }) + .then(result => { + this.setState({ + factionlist: result.factions + }); + }) + .catch(error => console.log(error)); + } + + handleTaskCreation = e => { + e.preventDefault(); + if (this.state.taskNameInput === "") { + return alert("Task needs a name"); + } + + let token = sessionStorage.getItem("token"); + fetch(`${process.env.REACT_APP_URL}/task/new-task/${this.props.gameId}`, { + method: "POST", + headers: { + Authorization: "Bearer " + token, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + taskName: this.state.taskNameInput, + taskDescription: this.state.taskDescriptionInput, + taskIsActive: true, + faction: + this.state.selectedFactionId === "" + ? null + : this.state.selectedFactionId, + taskWinner: null, + taskGame: this.props.gameId + }) + }) + .then(result => { + if (!result.ok) { + throw Error(Response.statusText); + } else { + return result.json(); + } + }) + .then(result => { + alert(result.message); + this.setState({ + taskDescriptionInput: "", + taskNameInput: "" + }); + this.getTasks(this.props.gameId); + }) + .catch(error => console.log(error)); + }; + + handleFactionChange = e => { + this.setState({ + selectedFactionId: e.target.value + }); + }; + + onTaskEditSave = (task, winnerFactionId) => { + let token = sessionStorage.getItem("token"); + fetch(`${process.env.REACT_APP_URL}/task/edit-task/${this.props.gameId}`, { + method: "POST", + headers: { + Authorization: "Bearer " + token, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + taskId: task.taskId, + taskWinner: winnerFactionId, + taskGame: this.props.gameId + }) + }) + .then(result => { + if (!result.ok) { + throw Error(result.responseText); + } else { + return result.json(); + } + }) + .then(result => { + alert(result.message); + this.getTasks(this.props.gameId); + }) + .catch(error => console.log(error)); + }; + + onTaskDeletion = taskId => { + if (taskId === (undefined || null)) { + return; + } + let token = sessionStorage.getItem("token"); + fetch( + `${process.env.REACT_APP_URL}/task/delete-task/${this.props.gameId}`, + { + method: "DELETE", + headers: { + Authorization: "Bearer " + token, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + taskId: taskId + }) + } + ) + .then(result => { + if (!result.ok) { + throw Error(result.responseText); + } else { + return result.json(); + } + }) + .then(result => { + alert(result.message); + this.getTasks(this.props.gameId); + }) + .catch(error => console.log(error)); + }; + + render() { + let incompleteTasks = []; + let completedTasks = []; + for (let i = 0; i < this.state.tasks.length; i++) { + const task = this.state.tasks[i]; + if (task.taskWinner !== null) { + completedTasks.push( + <TaskItem + key={task.taskId} + task={task} + gameId={this.props.gameId} + onSave={this.onTaskEditSave} + onDelete={this.onTaskDeletion} + /> + ); + } else { + incompleteTasks.push( + <TaskItem + key={task.taskId} + task={task} + gameId={this.props.gameId} + onSave={this.onTaskEditSave} + onDelete={this.onTaskDeletion} + /> + ); + } + } + + let factionlistItems = this.state.factionlist.map(item => { + return ( + <option key={item.factionId} value={item.factionId}> + {item.factionName} + </option> + ); + }); + + // add all factions option to the faction list + factionlistItems.unshift( + <option key="all" value=""> + Every faction + </option> + ); + + return ReactDOM.createPortal( + <div className="tasklist"> + <h1>Tasklist</h1> + <form className="task-form" onSubmit={this.handleTaskCreation}> + <label>New task</label> + <input + id="taskNameInput" + type="text" + placeholder="Task name" + minLength="3" + value={this.state.taskNameInput} + onChange={e => this.setState({ taskNameInput: e.target.value })} + /> + <textarea + id="taskDescriptionInput" + placeholder="Task description" + value={this.state.taskDescriptionInput} + onChange={e => + this.setState({ taskDescriptionInput: e.target.value }) + } + /> + <select id="taskFactionSelect" onChange={this.handleFactionChange}> + {factionlistItems} + </select> + <button id="newTaskSubmitButton" type="submit"> + Add new task + </button> + </form> + {incompleteTasks} + <br /> + <label>Completed tasks</label> + {completedTasks} + <br /> + </div>, + document.getElementById("tasklist") + ); + } +} + +export default TaskList; diff --git a/src/components/TaskListButton.js b/src/components/TaskListButton.js new file mode 100644 index 0000000000000000000000000000000000000000..988beab9c3b21520190f5fde33774e76ac74ba35 --- /dev/null +++ b/src/components/TaskListButton.js @@ -0,0 +1,53 @@ +import React, { Fragment } from "react"; +import TaskList from "./TaskList"; + +class TaskListButton extends React.Component { + constructor(props) { + super(props); + this.state = { + open: false, + newTasks: 0 + }; + + this.handleClick = this.handleClick.bind(this); + } + + componentDidMount() { + this.getNewTask(); + } + + getNewTask() { + this.setState({ + newTasks: this.state.open ? 0 : this.state.newTasks + 1 + }); + } + + handleClick = e => { + this.setState( + { + open: !this.state.open + }, + () => { + // Set new task cout to zero when the tasklist opens + if (this.state.open) { + this.setState({ newTasks: 0 }); + } + } + ); + }; + + render() { + return ( + <Fragment> + <button id="tasklistButton" onClick={this.handleClick}> + Tasks ({this.state.newTasks}) + </button> + {this.state.open && ( + <TaskList gameId="2c097e6a-591c-4a27-b7cb-38eb44e1f31c" /> + )} + </Fragment> + ); + } +} + +export default TaskListButton;