diff options
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | README.md | 89 | ||||
| -rw-r--r-- | assets/docs.png | 3 | ||||
| -rw-r--r-- | index.html | 158 | ||||
| -rw-r--r-- | package-lock.json | 19 | ||||
| -rw-r--r-- | package.json | 4 | ||||
| -rw-r--r-- | public/icon.png | bin | 1453 -> 0 bytes | |||
| -rw-r--r-- | public/icon.svg | 5 | ||||
| -rw-r--r-- | src/data.js | 54 | ||||
| -rw-r--r-- | src/main.js | 57 | ||||
| -rw-r--r-- | src/manage.js | 79 | ||||
| -rw-r--r-- | src/style.css | 23 | ||||
| -rw-r--r-- | src/timer.js | 9 |
13 files changed, 285 insertions, 218 deletions
@@ -1,3 +1,6 @@ +# Mac files +.DS_Store + # Data files *.csv @@ -1,79 +1,64 @@ # Match Up -Match Up! is a flask web application to create pairings of players from a pool and assign to a limited number of slots. +Match Up! is a web application to create pairings of players from a pool and assign to a limited number of slots. It is [live](https://mu.karanj.com)! Here it is in action: - - - + Some features of this application are: -- Propose pairings to create a round +- Activate and deactivate named players, guests and courts +- Propose pairings to each court from a set of players + - Considers player levels (optionally mingle players) + - Prioritize players that haven't played as many games - Timer to limit the time for each round -- Create pairings based on player level -- Prioritize players with fewer played games -- Blocking spaces that aren't available any more -- Blocking players that aren't available any more -- Regular and guest players What this application ***IS***: -- Simple: Easy to modify, extend and use +- Open: The source code is available for modifications and extension +- Private: All player data is stored in your browser via local storage What this application ***IS NOT***: -- Secure: This was designed to be used locally on a machine -- Performant: It was designed for use by a single admin user +- Secure: The data is available for anyone to observe via the browser console +- Performant: Works with a small number of named players (<3 MB) -## How to install +## Usage -With [python](https://realpython.com/installing-python/) installed, run the following command preferably in a seperate virtual environment. +- Select or deselect the courts available +- Check in and check out the regular players +- Optionally add guests to the game + - Beginner: Level 1 + - Novice: Level 3 + - Intermediate: Level 6 +- Choose how the pools should be created + - All levels (Disregard levels) + - 2 Levels (Players split into two categories) + - 3 Levels (Players split into three categories) + - Skill based (Consider all levels) +- Request a proposal and confirm to start a round +- Repeat as many times as needed +- Reset the session at the bottom of the page +- You can update the list of players by providing a CSV file. The format is show below: -### Using pip +|id|first_name|last_name|level| +|---|---|---|---| +|104|John|Doe|4| +|104|Jane|Doe|9| -```bash -pip install git+https://gitlab.com/KaranJayachandra/match_up.git -``` +Levels are defined between 1 to 10. -### How to complile from source +## Compilation -Please install [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) and [python](https://realpython.com/installing-python/) on your machine before proceeding further. Once installed, open the terminal to a folder of your choosing and run the following commands to setup the application. +Please install [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) and [node](https://nodejs.org/en) on your machine before proceeding further. + +Once installed, open the terminal to a folder of your choosing and run the following commands to setup the application. ```bash git clone https://gitlab.com/KaranJayachandra/match_up.git cd match_up -python -m venv .venv -.venv/Scripts/activate -pip install . -``` - -To run the application, just run: - -```bash -match_up -``` - -The application should be running now in 'localhost', just navigate there using a browser. Use the `--help` option to get an explanation of the application. - -## Startup Automation - -Create a powershell script with the following content in the folder with the code. - -```bash -.venv/Scripts/activate -pythonw match_up +npm install ``` -Create a task using Task Scheduler to run this script on start up. - -## Architectural Design Record - -This part of the README focuses on the motivation of why this application was built and contains background information. Please read on in case you want to know more about the technological choices made. - -This is a simple application build using [flask](https://flask.palletsprojects.com/en/3.0.x/), [htmx](https://htmx.org/) and [Bulma](https://bulma.io/). Flask acts as the backend that generates HTML responses using the wonderful [Jinja](https://jinja.palletsprojects.com/en/3.1.x/) templating language. Interactivity of the application is done using HTMX using simple GET and POST methods to the backend. The application is styled using the Bulma with its easy to used CSS classes. - -- Python was choose to prioritize delivery speed not for performance. More over, the sheer number of Python programmers allows for the application to be easily maintained by others. -- Flask was choosed as it more than enough for the features that I needed instead of Django. The application is to run locally on a laptop and doesn't really need to have complicated features. -- Due to a lack of frontend developers, the simplest UI stack was choosen. HTMX requires no need of JavaScript and was a natural choice. The website needed some basic styling and after looking at a few CSS frameworks, Bulma provided most of the styling. A toasting library, "Notyf" was also used for simple notifications to be sent via some custom javascript code for a simple timer. +To run a development server run `npm run dev` or run `npm run build` to create a optimized bundle of static content. I choose to serve this content via GitLab pages but you could choose to do so in any other server. diff --git a/assets/docs.png b/assets/docs.png new file mode 100644 index 0000000..a285f5d --- /dev/null +++ b/assets/docs.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15e4f5d832d0589d5054bcbfa61e011143051c20ed6150501ff98f2935d5e436 +size 332802 @@ -1,98 +1,84 @@ <!DOCTYPE html> -<html lang="en" data-theme="light"> +<html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Match Up!</title> + <meta name="keywords" content="Badminton, Matchmaking, Open"> + <meta name="author" content="Karan Jayachandra"> + <meta name="description" content="Matchup is a free utility that helps you create matches for Badminton from a pool a players. The tool is completely open source and private as all data is store locally on your machine."> + <meta http-equiv="Cache-control" content="public"> </head> <body> - <section class="section"> - <nav class="navbar" role="navigation" aria-label="main navigation"> - <div class="navbar-brand"> - <div class="navbar-item"><img src="icon.png"></div> - <div class="navbar-item">Match Up</div> + <header class="container"> + <nav> + <ul><li><img src="icon.svg" alt="Matchup"></li></ul> + <ul> + <li> + <fieldset id="timer" role="group"> + <button id="timer-dec" title="Timer decrement"><i class="fa-solid fa-minus"></i></button> + <input id="timer-display" title="Time display" type="text" readonly></input> + <button id="timer-inc" title="Timer increment"><i class="fa-solid fa-plus"></i></button> + <button id="timer-start" title="Timer start"><i class="fa-solid fa-play"></i></button> + <button id="timer-stop" title="Timer stop"><i class="fa-solid fa-stop"></i></button> + </fieldset> + </li> + </ul> + </nav> + </header> + <main class="container"> + <section> + <h1 class="title" id="Game-description"></h1> + <div id="Game-list" class="grid-container"></div> + </section> + <section> + <select id="skill" aria-label="State"> + <option value="10">All Levels</option> + <option value="5">2 Levels</option> + <option value="3">3 Levels</option> + <option value="1">Skill Based</option> + </select> + <div class="grid"> + <button id="propose">Propose</button> + <button id="confirm">Confirm</button> </div> - <div class="navbar-end"> - <div class="field is-grouped is-grouped-right"> - <button class="button is-medium is-dark" id="timer-dec" value="-0.5"><i class="fa-solid fa-minus" value="-0.5"></i></button> - <input class="input is-medium is-dark" id="timer-display" type="text" readonly></input> - <button class="button is-medium is-dark" id="timer-inc" value="0.5"><i class="fa-solid fa-plus" value="0.5"></i></button> - <button class="button is-medium is-dark" id="timer-start"><i class="fa-solid fa-play"></i></button> - <button class="button is-medium is-dark" id="timer-stop"><i class="fa-solid fa-stop"></i></button> - </div> + </section> + <section> + <hgroup> + <h2>Courts</h2> + <p>Reserve courts for training or competition.</p> + </hgroup> + <div id="Court-list" class="grid-container"></div> + </section> + <section> + <hgroup> + <h2>Regulars</h2> + <p>Check in and check out club members.</p> + </hgroup> + <div id="Regular-list" class="grid-container"></div> + </section> + <section> + <hgroup> + <h2>Guests</h2> + <p>Add guests to the pool.</p> + </hgroup> + <div id="Guest-list" class="grid-container"></div> + </section> + <section> + <hgroup> + <h2>Admin</h2> + <p>Changes here are permanent and cannot be reverted.</p> + </hgroup> + <div id="admin" class="grid"> + <button id="reset">Reset Session</button> + <button id='button-player-load'>Upload Database</button> </div> - </nav> - <div class="container"> - <section class="section"> - <h1 class="title" id="Game_description"></h1> - <table class="table is-fullwidth"> - <thead> - <tr> - <th>Court</th> - <th colspan='2' style="text-align: center;">Team 1</th> - <th colspan='2' style="text-align: center;">Team 2</th> - </tr> - </thead> - <tbody id="Game_list"></tbody> - </table> - </section> - <section class="section"> - <div class="field is-grouped is-grouped-centered"> - <button class="button is-medium is-dark" id="propose">Propose</button> - <div class="select is-medium is-dark"> - <select name="skill" id="skill"> - <option value="10">All Levels</option> - <option value="5">2 Levels</option> - <option value="3">3 Levels</option> - <option value="1">Skill Based</option> - </select> - </div> - <button class="button is-medium is-dark" id="confirm">Confirm</button> - </div> - </section> - <section class="section"> - <h1 class="title">Courts</h1> - <div id="Court_list" class="button-grid"></div> - </section> - <section class="section"> - <h1 class="title">Regulars</h1> - <div id="Regular_list" class="button-grid"></div> - </section> - <section class="section"> - <h1 class="title">Guests</h1> - <div id="Guest_list" class="button-grid"></div> - </section> - <section class="section"> - <h1 class="title">Admin</h1> - <h1 class="subtitle">Changes here are permanent and cannot be reverted.</h1> - <div class="field is-grouped is-grouped-centered"> - <button class="button is-medium is-danger" id="reset"> - Reset Session - </button> - </div> - <div class="field is-grouped is-grouped-centered"> - <div id="player-load" class="file is-danger is-boxed is-medium"> - <label class="file-label"> - <input class="file-input" type="file" name="players" accept=".csv" /> - <span class="file-cta"> - <span class="file-icon"> - <i class="fa-solid fa-upload"></i> - </span> - <span class="file-label">Update Regulars</span> - </span> - </label> - </div> - </div> - </section> + <input type="file" id="player-load" accept=".csv" placeholder="Player database" hidden/> </div> - </section> - <script src="src/main.js" type="module"></script> + </main> + <footer class="container" style="text-align: center;"> + <strong>Matchup</strong> made with ❤️ by <a href="https://karanj.com">Karan</a>. The source code is licensed <a href="https://creativecommons.org/licenses/by-sa/4.0/deed.en">CC BY-SA 4.0</a>. Logo from <a href="https://www.svgrepo.com">SVG Repo</a>. + </footer> </body> - <footer class="footer"> - <div class="content has-text-centered"> - <p><strong>Match Up</strong> made with ❤️ by <a href="https://karanj.com">Karan</a>.</p> - <p>The source code is licensed <a href="https://creativecommons.org/licenses/by-sa/4.0/deed.en">CC BY-SA 4.0</a>.</p> - <p>Logo from <a href="https://www.flaticon.com/free-icons/shuttle-cock" title="shuttle cock icons">Flaticon</a>.</p> - </div> - </footer> + <script src="src/main.js" type="module"></script> </html>
\ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2e440f5..b5f333e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,10 @@ "version": "0.0.0", "dependencies": { "@fortawesome/fontawesome-free": "^7.1.0", + "@picocss/pico": "^2.1.1", "bulma": "^1.0.4", - "notyf": "^3.10.0" + "notyf": "^3.10.0", + "unique-names-generator": "^4.7.1" }, "devDependencies": { "vite": "npm:rolldown-vite@7.2.2" @@ -92,6 +94,12 @@ "url": "https://github.com/sponsors/Boshen" } }, + "node_modules/@picocss/pico": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@picocss/pico/-/pico-2.1.1.tgz", + "integrity": "sha512-kIDugA7Ps4U+2BHxiNHmvgPIQDWPDU4IeU6TNRdvXQM1uZX+FibqDQT2xUOnnO2yq/LUHcwnGlu1hvf4KfXnMg==", + "license": "MIT" + }, "node_modules/@rolldown/binding-android-arm64": { "version": "1.0.0-beta.47", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.47.tgz", @@ -801,6 +809,15 @@ "license": "0BSD", "optional": true }, + "node_modules/unique-names-generator": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/unique-names-generator/-/unique-names-generator-4.7.1.tgz", + "integrity": "sha512-lMx9dX+KRmG8sq6gulYYpKWZc9RlGsgBR6aoO8Qsm3qvkSJ+3rAymr+TnV8EDMrIrwuFJ4kruzMWM/OpYzPoow==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/vite": { "name": "rolldown-vite", "version": "7.2.2", diff --git a/package.json b/package.json index 6e96392..11d527b 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,9 @@ }, "dependencies": { "@fortawesome/fontawesome-free": "^7.1.0", + "@picocss/pico": "^2.1.1", "bulma": "^1.0.4", - "notyf": "^3.10.0" + "notyf": "^3.10.0", + "unique-names-generator": "^4.7.1" } } diff --git a/public/icon.png b/public/icon.png Binary files differdeleted file mode 100644 index 655915f..0000000 --- a/public/icon.png +++ /dev/null diff --git a/public/icon.svg b/public/icon.svg new file mode 100644 index 0000000..79fc328 --- /dev/null +++ b/public/icon.svg @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
+<svg width="64px" height="64px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M12.2754 0.944403C11.3453 0.744005 10.2222 0.970412 9.13178 2.0608C8.33219 2.86038 7.9983 3.67642 7.95466 4.41945C7.21168 4.46315 6.39574 4.79705 5.59623 5.59656C4.7968 6.39598 4.46289 7.21185 4.41914 7.95476C3.67615 7.99845 2.86019 8.33235 2.06067 9.13187C0.970292 10.2223 0.743884 11.3454 0.94428 12.2755C1.21143 13.5155 2.36155 14.0955 2.99091 14.3815L13.0368 18.9479C13.7958 19.2928 14.6891 19.1308 15.2786 18.5413L18.5412 15.2788C19.1307 14.6892 19.2927 13.7959 18.9478 13.0369L14.3815 2.99103C14.0954 2.36167 13.5154 1.21155 12.2754 0.944403ZM11.3631 2.92663C11.5577 2.86548 11.7198 2.87057 11.8542 2.89954C11.9267 2.91516 12.0234 2.96174 12.154 3.11733C12.2925 3.28248 12.4237 3.51715 12.5607 3.81864L17.127 13.8645L17.0038 13.9877L10.2365 5.28681C10.1076 5.15387 10.0151 4.97688 9.97358 4.79696C9.92398 4.58201 9.88826 4.13274 10.546 3.47501C10.8869 3.13412 11.1654 2.98872 11.3631 2.92663ZM5.2867 10.2368L13.9875 17.0041L13.8644 17.1271L3.81852 12.5608C3.51702 12.4238 3.28236 12.2926 3.11721 12.1541C2.96162 12.0235 2.91504 11.9268 2.89942 11.8543C2.87045 11.7198 2.86535 11.5578 2.92651 11.3632C2.9886 11.1655 3.134 10.887 3.47489 10.5461C4.13262 9.88835 4.58189 9.92407 4.79683 9.97368C4.97795 10.0155 5.15478 10.1048 5.2867 10.2368ZM15.413 15.5788L6.6399 8.75532C6.58371 8.68236 6.4812 8.51974 6.43804 8.33271C6.38844 8.11777 6.35271 7.6685 7.01045 7.01077C7.66818 6.35304 8.11744 6.38876 8.33238 6.43837C8.51988 6.48163 8.68289 6.58461 8.75554 6.64065L15.5786 15.4132L15.413 15.5788Z" fill="#0172ad"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M15.7878 19.4464C15.2924 19.9419 15.1623 20.7967 15.6981 21.4206C16.0694 21.853 16.8569 22.653 17.9452 23.0047C18.5098 23.1873 19.1614 23.2505 19.8583 23.0724C20.5523 22.8951 21.2237 22.4958 21.8597 21.8598C22.4957 21.2238 22.895 20.5523 23.0724 19.8583C23.2504 19.1614 23.1872 18.5099 23.0047 17.9452C22.6529 16.857 21.8529 16.0694 21.4206 15.6982C20.7966 15.1624 19.9418 15.2924 19.4464 15.7879L15.7878 19.4464ZM18.5603 21.1017C18.1893 20.9818 17.848 20.7386 17.5743 20.4883L20.4882 17.5744C20.7385 17.8481 20.9817 18.1894 21.1016 18.5603C21.1853 18.8192 21.2062 19.0832 21.1346 19.3632C21.0623 19.6461 20.8783 20.0128 20.4455 20.4456C20.0127 20.8784 19.646 21.0624 19.3631 21.1347C19.0832 21.2062 18.8192 21.1854 18.5603 21.1017Z" fill="#0172ad"/>
+</svg>
\ No newline at end of file diff --git a/src/data.js b/src/data.js index 589b5a3..0b0c9ab 100644 --- a/src/data.js +++ b/src/data.js @@ -3,7 +3,7 @@ class Switch { this.id = id; this.status = typeof status !== "undefined" ? status : false; this.description = description; - this.name = description + " " + (id + 1); + this.name = this.description + " " + (id + 1); } toggle() { this.status = !this.status; @@ -14,10 +14,9 @@ class Switch { console.log(this.name + " is currently " + status); } render() { - let color = this.status ? "is-success" : "is-danger"; - let type = `class="button is-medium ${color}"`; + let color = this.status ? `` : `class="outline secondary"`; let id = this.description + "_" + this.id; - return `<button id=${id} ${type}>${this.name}</button>`; + return `<button id=${id} ${color}>${this.name}</button>`; } } @@ -27,14 +26,15 @@ export class Court extends Switch { } } -export class Player extends Switch { - constructor(id, status, level, games, name) { +export class Regular extends Switch { + constructor(id, status, level, games, firstName, lastName) { super(id, "Regular", status); this.level = typeof level !== "undefined" ? level : 1; this.games = typeof games !== "undefined" ? games : 0; - if (typeof name !== "undefined") { - this.name = name; - } + this.firstName = + typeof firstName !== "undefined" ? firstName : this.description; + this.lastName = typeof lastName !== "undefined" ? lastName : this.id + 1; + this.name = this.firstName + " " + this.lastName; } } @@ -54,10 +54,16 @@ export class Guest extends Switch { this.level = guestCategories[categoryId].level; this.games = typeof games !== "undefined" ? games : 0; const guestNumber = (id % this.maxCount) + 1; - this.name = guestCategories[categoryId].description + " " + guestNumber; + this.firstName = guestCategories[categoryId].description; + this.lastName = guestNumber; + this.name = this.firstName + " " + this.lastName; } } +function renderPlayerName(player) { + return `<strong>${player.firstName}</strong> ${player.lastName}`; +} + export class Team { constructor(playerOne, playerTwo) { this.playerOne = playerOne; @@ -69,9 +75,9 @@ export class Team { this.playerTwo.print(); } render() { - let p1 = `<td style="width: 23%; text-align: right;">${this.playerOne}</td>`; - let p2 = `<td style="width: 23%; text-align: left;">${this.playerTwo}</td>`; - return p1 + p2; + const p1 = renderPlayerName(this.playerOne); + const p2 = renderPlayerName(this.playerTwo); + return p1 + `<br/><br/>` + p2; } } @@ -82,18 +88,24 @@ export class Game extends Switch { this.teamTwo = teamTwo; } print() { - let teamOne = this.teamOne.playerOne + " and " + this.teamOne.playerTwo; - let teamTwo = this.teamTwo.playerOne + " and " + this.teamTwo.playerTwo; + const teamOne = + this.teamOne.playerOne.firstName + + " and " + + this.teamOne.playerTwo.firstName; + const teamTwo = + this.teamTwo.playerOne.firstName + + " and " + + this.teamTwo.playerTwo.firstName; console.log( "Game " + this.id + ": " + teamOne + " is playing against " + teamTwo ); } render() { - let blockedState = this.teamOne.playerOne === "RESERVED"; - let type = blockedState ? `class="is-dark"` : ``; - let court = `<td style="width: 8%; text-align: center;">${ - this.id + 1 - }</td>`; - return `<tr ${type}>${court}${this.teamOne.render()}${this.teamTwo.render()}</tr>`; + const blockedState = this.teamOne.playerOne.firstName === "RESERVED"; + const playerState = this.teamOne.render() + "<hr>" + this.teamTwo.render(); + const type = blockedState ? `<h5>Reserved</p>` : playerState; + return `<article> + <header>Court ${this.id + 1}</header>${type} + </article>`; } } diff --git a/src/main.js b/src/main.js index ec33b18..59f66d0 100644 --- a/src/main.js +++ b/src/main.js @@ -1,19 +1,8 @@ -import "notyf/notyf.min.css"; -import "bulma/css/bulma.css"; -import "@fortawesome/fontawesome-free/css/all.css"; -import "./style.css"; -import { Notyf } from "notyf"; +import "./style.css" import { Timer } from "./timer"; -import { Team, Game } from "./data"; +import { Team, Game, Regular } from "./data"; import { Manager, CourtManager, RegularManager, GuestManager } from "./manage"; -export function getNotifier(seconds) { - return new Notyf({ - duration: seconds * milliSecond, - position: { x: "center", y: "top" }, - dismissible: true, - }); -} export class GameManager extends Manager { timer = new Timer(); @@ -27,12 +16,17 @@ export class GameManager extends Manager { this.render(); addGameBehaviour(this); } - createItem(i, description) { + createItem(i, description, status) { if (typeof description === "undefined") { description = "---"; } - let team = new Team(description, description); - return new Game(i, true, team, team); + if (typeof status === "undefined") { + status = true; + } + let player = new Regular(null, null, null, null, description, ""); + console.log(player); + let team = new Team(player, player); + return new Game(i, status, team, team); } loadItem(data) { let id = Number(data.id); @@ -44,20 +38,20 @@ export class GameManager extends Manager { addBehaviour() { let confirmedStatus = this.data[0].status; let description = confirmedStatus ? "Round " + this.round : "Proposal"; - document.getElementById("Game_description").innerHTML = description; + document.getElementById("Game-description").innerHTML = description; } addBlockedGames() { const inactiveCourts = this.courts.inactive(); console.log("Total reserved courts are " + inactiveCourts.length + "."); for (let court of inactiveCourts) { - this.data.push(this.createItem(court.id, "RESERVED")); + this.data.push(this.createItem(court.id, "RESERVED", false)); } } addInactiveGames(possibleMatches) { const activeCourts = this.courts.active(); const unusedCourts = activeCourts.slice(possibleMatches + 1); for (let court of unusedCourts) { - this.data.push(this.createItem(court.id)); + this.data.push(this.createItem(court.id, "---", false)); } } addActiveGames(possibleMatches) { @@ -69,8 +63,8 @@ export class GameManager extends Manager { const startIndex = 4 * index; const endIndex = startIndex + 4; const courtPlayers = selectedPlayers.slice(startIndex, endIndex); - const teamOne = new Team(courtPlayers[0].name, courtPlayers[3].name); - const teamTwo = new Team(courtPlayers[1].name, courtPlayers[2].name); + const teamOne = new Team(courtPlayers[0], courtPlayers[3]); + const teamTwo = new Team(courtPlayers[1], courtPlayers[2]); const game = new Game(usedCourts[index].id, false, teamOne, teamTwo); this.data.push(game); } @@ -98,7 +92,7 @@ export class GameManager extends Manager { p.print(); }); const shuffledPlayers = shuffle(activePlayers); - shuffledPlayers.sort((a, b) => a.totalPlayed - b.totalPlayed); + shuffledPlayers.sort((a, b) => a.games - b.games); const selectedPlayers = shuffledPlayers.slice(0, count); selectedPlayers.sort((a, b) => a.level - b.level); console.log("Selected players are:"); @@ -125,7 +119,6 @@ export class GameManager extends Manager { confirm() { let confirmedStatus = this.data[0].status; if (confirmedStatus) { - getNotifier().error("Create a proposal first!"); return; } console.log("Confirm the proposal for round " + this.round + "."); @@ -134,7 +127,18 @@ export class GameManager extends Manager { for (let i = 0; i < this.data.length; i++) { this.data[i].status = true; } - // Increment the number of games played + let selectedPlayers = []; + for (let game of this.data) { + console.log(game); + if ( game.teamOne.playerOne.name !== "RESERVED" ){ + selectedPlayers.push(game.teamOne.playerOne.name); + selectedPlayers.push(game.teamOne.playerTwo.name); + selectedPlayers.push(game.teamTwo.playerOne.name); + selectedPlayers.push(game.teamTwo.playerTwo.name); + } + } + this.regulars.incrementGames(selectedPlayers); + this.guests.incrementGames(selectedPlayers); this.store(); this.render(); document.getElementById("timer-start").click(); @@ -144,7 +148,7 @@ export class GameManager extends Manager { localStorage.removeItem(this.description); localStorage.removeItem(this.courts.description); localStorage.removeItem(this.guests.description); - // Clear the number of games played + this.regulars.reset(); document.getElementById("timer-stop").click(); window.location.reload(); } @@ -164,12 +168,11 @@ function addGameBehaviour(g) { }); document.getElementById("confirm").addEventListener("click", () => { g.confirm(); - getNotifier().success("Round confirmed!"); }); document.getElementById("reset").addEventListener("click", () => { g.reset(); - getNotifier().error("Session has been reset!"); }); } const g = new GameManager(); + diff --git a/src/manage.js b/src/manage.js index 7449866..10a9896 100644 --- a/src/manage.js +++ b/src/manage.js @@ -1,4 +1,11 @@ -import { Court, Guest, Player } from "./data"; +import { Court, Guest, Regular } from "./data"; +import { uniqueNamesGenerator, names, adjectives } from 'unique-names-generator'; + +const config = { + dictionaries: [adjectives, names], + style: 'capital', + length: 2 +} export class Manager { constructor(description, maxCount) { @@ -38,7 +45,7 @@ export class Manager { return i.render(); }) .join(""); - let elementId = this.data[0].description + "_list"; + let elementId = this.data[0].description + "-list"; document.getElementById(elementId).innerHTML = renderedHTML; this.addBehaviour(); } @@ -49,8 +56,8 @@ export class Manager { p.addEventListener("click", () => { this.data[idx].toggle(); this.store(); - p.classList.toggle("is-success"); - p.classList.toggle("is-danger"); + p.classList.toggle("secondary"); + p.classList.toggle("outline"); }); } } @@ -94,8 +101,14 @@ function normalize(data) { } export class GuestManager extends Manager { - constructor() { - super("Guest", 18); + constructor(description, count) { + if (typeof description === "undefined") { + description = "Guest"; + } + if (typeof count === "undefined") { + count = 18; + } + super(description, count); } createItem(i) { return new Guest(i, randomStatus()); @@ -109,6 +122,15 @@ export class GuestManager extends Manager { active() { return normalize(super.active()); } + incrementGames(list) { + for (let index = 0; index < this.data.length; index++) { + if (list.includes(this.data[index].name)) { + console.log("Added game count to " + this.data[index].name); + this.data[index].games += 1; + } + } + this.store(); + } } export function randomLevel() { @@ -116,33 +138,42 @@ export function randomLevel() { return Math.floor(Math.random() * maxLevel + 1); } -export class RegularManager extends Manager { +export class RegularManager extends GuestManager { constructor() { super("Regular", 71); addLoadBehaviour(this); } createItem(i) { - return new Player(i, randomStatus(), randomLevel()); + const name = uniqueNamesGenerator(config); + console.log(name); + const firstName = name.split("_")[0]; + const lastName = name.split("_")[1]; + return new Regular(i, randomStatus(), randomLevel(), 0, firstName, lastName); } loadItem(data) { - let id = Number(data.id); - let name = data.name; - let level = Number(data.level); - let status = Boolean(Number(data.status)); - let games = Number(data.games); - return new Player(id, status, level, games, name); + const id = Number(data.id); + const level = Number(data.level); + const status = Boolean(Number(data.status)); + const games = Number(data.games); + const firstName = data.firstName; + const lastName = data.lastName; + return new Regular(id, status, level, games, firstName, lastName); } - active() { - return normalize(super.active()); + reset() { + for (let index = 0; index < this.data.length; index++) { + this.data[index].games = 0; + this.data[index].status = false; + } } } function parseLineToPlayer(data) { - var info = data.split(","); - var id = Number(info[0].trim()); - var name = info[1].trim() + " " + info[2].trim(); - var level = Number(info[3].trim()); - return new Player(id, randomStatus(), level, 0, name); + const info = data.split(","); + const id = Number(info[0].trim()); + const firstName = info[1].trim(); + const lastName = info[2].trim(); + const level = Number(info[3].trim()); + return new Regular(id, randomStatus(), level, 0, firstName, lastName); } function parseCsv(data) { @@ -161,8 +192,12 @@ function parseCsv(data) { return players; } +document.getElementById("button-player-load").addEventListener("click", () => { + document.getElementById("player-load").click(); +}); + function addLoadBehaviour(p) { - const f = document.querySelector("#player-load input[type=file]"); + const f = document.getElementById("player-load"); f.addEventListener("change", () => { const file = f.files[0]; let importData; diff --git a/src/style.css b/src/style.css index 73550ca..357b043 100644 --- a/src/style.css +++ b/src/style.css @@ -1,17 +1,26 @@ -.button-grid { +@import "@picocss/pico"; +@import "notyf/notyf.min.css"; +@import "@fortawesome/fontawesome-free/css/all.css"; + +.grid-container { display: grid; - grid-template-columns: repeat(3, 1fr); + grid-template-columns: repeat(6, 1fr); grid-gap: 15px; } #timer-display { text-align: center; - width: fit-content; + width: 100px; } -table { - font-size: 1.5em; +#timer { + margin: 0; +} +@media (min-width: 900px) { + #Regular-list button { + height: 100px; + } } @media (max-width: 900px) { - .button-grid { + .grid-container { grid-template-columns: repeat(1, 1fr); } -}
\ No newline at end of file +} diff --git a/src/timer.js b/src/timer.js index c00ffdc..9b0a8b9 100644 --- a/src/timer.js +++ b/src/timer.js @@ -1,3 +1,5 @@ +import { Notyf } from "notyf"; + const milliSecond = 1000; export class Timer { @@ -42,7 +44,12 @@ export class Timer { return; } p.reset(); - getNotifier().error("Round completed"); + const n = new Notyf({ + duration: 0, + position: { x: "center", y: "top" }, + dismissible: true, + }); + n.error("Round completed"); } start(fresh) { if (fresh) { |
