aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--index.html97
-rw-r--r--package-lock.json882
-rw-r--r--package.json22
-rw-r--r--public/favicon.icobin0 -> 13054 bytes
-rw-r--r--public/icon.pngbin0 -> 1453 bytes
-rw-r--r--src/court.mjs53
-rw-r--r--src/game.mjs186
-rw-r--r--src/guest.mjs69
-rw-r--r--src/main.js20
-rw-r--r--src/player.mjs150
-rw-r--r--src/style.css12
-rw-r--r--src/timer.js88
-rw-r--r--src/utils.mjs51
13 files changed, 1630 insertions, 0 deletions
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..287841a
--- /dev/null
+++ b/index.html
@@ -0,0 +1,97 @@
+<!DOCTYPE html>
+<html lang="en" data-theme="light">
+ <head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Match Up!</title>
+ </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>
+ </div>
+ <div class="navbar-end">
+ <div class="field is-grouped is-grouped-right">
+ <button class="button is-medium is-dark" id="timer-dec"><i class="fa-solid fa-minus"></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"><i class="fa-solid fa-plus"></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>
+ </div>
+ </nav>
+ <div class="container">
+ <section class="section">
+ <table class="table is-fullwidth">
+ <thead>
+ <tr><th colspan="5"><div id="game_description"></div></th></tr>
+ <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>
+ <button class="button is-medium is-danger" id="reset">Reset</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">Player Update</h1>
+ <div id="player-load" class="file is-danger has-name is-boxed">
+ <label class="file-label">
+ <input class="file-input" type="file" name="players" accept=".csv"/>
+ <span class="file-cta">
+ <span class="file-icon">
+ <i class="fas fa-cloud-upload-alt"></i>
+ </span>
+ <span class="file-label"> Update players... </span>
+ </span>
+ <span class="file-name"> No file selected... </span>
+ </label>
+ </div>
+ </section>
+ </div>
+ </section>
+ <script src="src/main.js" type="module"></script>
+ <script src="src/timer.js"></script>
+ </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>. 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.flaticon.com/free-icons/shuttle-cock" title="shuttle cock icons">Flaticon</a>.
+ </p>
+ </div>
+ </footer>
+</html> \ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..2e440f5
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,882 @@
+{
+ "name": "matchup",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "matchup",
+ "version": "0.0.0",
+ "dependencies": {
+ "@fortawesome/fontawesome-free": "^7.1.0",
+ "bulma": "^1.0.4",
+ "notyf": "^3.10.0"
+ },
+ "devDependencies": {
+ "vite": "npm:rolldown-vite@7.2.2"
+ }
+ },
+ "node_modules/@emnapi/core": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.0.tgz",
+ "integrity": "sha512-pJdKGq/1iquWYtv1RRSljZklxHCOCAJFJrImO5ZLKPJVJlVUcs8yFwNQlqS0Lo8xT1VAXXTCZocF9n26FWEKsw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.1.0",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.0.tgz",
+ "integrity": "sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz",
+ "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@fortawesome/fontawesome-free": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-7.1.0.tgz",
+ "integrity": "sha512-+WxNld5ZCJHvPQCr/GnzCTVREyStrAJjisUPtUxG5ngDA8TMlPnKp6dddlTpai4+1GNmltAeuk1hJEkBohwZYA==",
+ "license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@napi-rs/wasm-runtime": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz",
+ "integrity": "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.5.0",
+ "@emnapi/runtime": "^1.5.0",
+ "@tybys/wasm-util": "^0.10.1"
+ }
+ },
+ "node_modules/@oxc-project/runtime": {
+ "version": "0.96.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.96.0.tgz",
+ "integrity": "sha512-34lh4o9CcSw09Hx6fKihPu85+m+4pmDlkXwJrLvN5nMq5JrcGhhihVM415zDqT8j8IixO1PYYdQZRN4SwQCncg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxc-project/types": {
+ "version": "0.96.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.96.0.tgz",
+ "integrity": "sha512-r/xkmoXA0xEpU6UGtn18CNVjXH6erU3KCpCDbpLmbVxBFor1U9MqN5Z2uMmCHJuXjJzlnDR+hWY+yPoLo8oHDw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ }
+ },
+ "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",
+ "integrity": "sha512-vPP9/MZzESh9QtmvQYojXP/midjgkkc1E4AdnPPAzQXo668ncHJcVLKjJKzoBdsQmaIvNjrMdsCwES8vTQHRQw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-arm64": {
+ "version": "1.0.0-beta.47",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.47.tgz",
+ "integrity": "sha512-Lc3nrkxeaDVCVl8qR3qoxh6ltDZfkQ98j5vwIr5ALPkgjZtDK4BGCrrBoLpGVMg+csWcaqUbwbKwH5yvVa0oOw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-x64": {
+ "version": "1.0.0-beta.47",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.47.tgz",
+ "integrity": "sha512-eBYxQDwP0O33plqNVqOtUHqRiSYVneAknviM5XMawke3mwMuVlAsohtOqEjbCEl/Loi/FWdVeks5WkqAkzkYWQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-freebsd-x64": {
+ "version": "1.0.0-beta.47",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.47.tgz",
+ "integrity": "sha512-Ns+kgp2+1Iq/44bY/Z30DETUSiHY7ZuqaOgD5bHVW++8vme9rdiWsN4yG4rRPXkdgzjvQ9TDHmZZKfY4/G11AA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+ "version": "1.0.0-beta.47",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.47.tgz",
+ "integrity": "sha512-4PecgWCJhTA2EFOlptYJiNyVP2MrVP4cWdndpOu3WmXqWqZUmSubhb4YUAIxAxnXATlGjC1WjxNPhV7ZllNgdA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-gnu": {
+ "version": "1.0.0-beta.47",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.47.tgz",
+ "integrity": "sha512-CyIunZ6D9U9Xg94roQI1INt/bLkOpPsZjZZkiaAZ0r6uccQdICmC99M9RUPlMLw/qg4yEWLlQhG73W/mG437NA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-musl": {
+ "version": "1.0.0-beta.47",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.47.tgz",
+ "integrity": "sha512-doozc/Goe7qRCSnzfJbFINTHsMktqmZQmweull6hsZZ9sjNWQ6BWQnbvOlfZJe4xE5NxM1NhPnY5Giqnl3ZrYQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-gnu": {
+ "version": "1.0.0-beta.47",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.47.tgz",
+ "integrity": "sha512-fodvSMf6Aqwa0wEUSTPewmmZOD44rc5Tpr5p9NkwQ6W1SSpUKzD3SwpJIgANDOhwiYhDuiIaYPGB7Ujkx1q0UQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-musl": {
+ "version": "1.0.0-beta.47",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.47.tgz",
+ "integrity": "sha512-Rxm5hYc0mGjwLh5sjlGmMygxAaV2gnsx7CNm2lsb47oyt5UQyPDZf3GP/ct8BEcwuikdqzsrrlIp8+kCSvMFNQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-openharmony-arm64": {
+ "version": "1.0.0-beta.47",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.47.tgz",
+ "integrity": "sha512-YakuVe+Gc87jjxazBL34hbr8RJpRuFBhun7NEqoChVDlH5FLhLXjAPHqZd990TVGVNkemourf817Z8u2fONS8w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-wasm32-wasi": {
+ "version": "1.0.0-beta.47",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.47.tgz",
+ "integrity": "sha512-ak2GvTFQz3UAOw8cuQq8pWE+TNygQB6O47rMhvevvTzETh7VkHRFtRUwJynX5hwzFvQMP6G0az5JrBGuwaMwYQ==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@napi-rs/wasm-runtime": "^1.0.7"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-arm64-msvc": {
+ "version": "1.0.0-beta.47",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.47.tgz",
+ "integrity": "sha512-o5BpmBnXU+Cj+9+ndMcdKjhZlPb79dVPBZnWwMnI4RlNSSq5yOvFZqvfPYbyacvnW03Na4n5XXQAPhu3RydZ0w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-ia32-msvc": {
+ "version": "1.0.0-beta.47",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.47.tgz",
+ "integrity": "sha512-FVOmfyYehNE92IfC9Kgs913UerDog2M1m+FADJypKz0gmRg3UyTt4o1cZMCAl7MiR89JpM9jegNO1nXuP1w1vw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-x64-msvc": {
+ "version": "1.0.0-beta.47",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.47.tgz",
+ "integrity": "sha512-by/70F13IUE101Bat0oeH8miwWX5mhMFPk1yjCdxoTNHTyTdLgb0THNaebRM6AP7Kz+O3O2qx87sruYuF5UxHg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.47",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.47.tgz",
+ "integrity": "sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.10.1",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
+ "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/bulma": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/bulma/-/bulma-1.0.4.tgz",
+ "integrity": "sha512-Ffb6YGXDiZYX3cqvSbHWqQ8+LkX6tVoTcZuVB3lm93sbAVXlO0D6QlOTMnV6g18gILpAXqkG2z9hf9z4hCjz2g==",
+ "license": "MIT"
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
+ "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.30.2",
+ "lightningcss-darwin-arm64": "1.30.2",
+ "lightningcss-darwin-x64": "1.30.2",
+ "lightningcss-freebsd-x64": "1.30.2",
+ "lightningcss-linux-arm-gnueabihf": "1.30.2",
+ "lightningcss-linux-arm64-gnu": "1.30.2",
+ "lightningcss-linux-arm64-musl": "1.30.2",
+ "lightningcss-linux-x64-gnu": "1.30.2",
+ "lightningcss-linux-x64-musl": "1.30.2",
+ "lightningcss-win32-arm64-msvc": "1.30.2",
+ "lightningcss-win32-x64-msvc": "1.30.2"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz",
+ "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz",
+ "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz",
+ "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz",
+ "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz",
+ "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz",
+ "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz",
+ "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz",
+ "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz",
+ "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz",
+ "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz",
+ "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/notyf": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/notyf/-/notyf-3.10.0.tgz",
+ "integrity": "sha512-Mtnp+0qiZxgrH+TzVlzhWyZceHdAZ/UWK0/ju9U0HQeDpap1mZ8cC7H5wSI5mwgni6yeAjaxsTw0sbMK+aSuHw==",
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/rolldown": {
+ "version": "1.0.0-beta.47",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.47.tgz",
+ "integrity": "sha512-Mid74GckX1OeFAOYz9KuXeWYhq3xkXbMziYIC+ULVdUzPTG9y70OBSBQDQn9hQP8u/AfhuYw1R0BSg15nBI4Dg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@oxc-project/types": "=0.96.0",
+ "@rolldown/pluginutils": "1.0.0-beta.47"
+ },
+ "bin": {
+ "rolldown": "bin/cli.mjs"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "optionalDependencies": {
+ "@rolldown/binding-android-arm64": "1.0.0-beta.47",
+ "@rolldown/binding-darwin-arm64": "1.0.0-beta.47",
+ "@rolldown/binding-darwin-x64": "1.0.0-beta.47",
+ "@rolldown/binding-freebsd-x64": "1.0.0-beta.47",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.47",
+ "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.47",
+ "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.47",
+ "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.47",
+ "@rolldown/binding-linux-x64-musl": "1.0.0-beta.47",
+ "@rolldown/binding-openharmony-arm64": "1.0.0-beta.47",
+ "@rolldown/binding-wasm32-wasi": "1.0.0-beta.47",
+ "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.47",
+ "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.47",
+ "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.47"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "dev": true,
+ "license": "0BSD",
+ "optional": true
+ },
+ "node_modules/vite": {
+ "name": "rolldown-vite",
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/rolldown-vite/-/rolldown-vite-7.2.2.tgz",
+ "integrity": "sha512-Fl3ZdmJhDMJGcqrr342pPVrhugXdOcuNBRBauz4S7QGSRXbQy7y8q5QYJtgkcrG8XjY0EENSZeTk58c3m20FxA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@oxc-project/runtime": "0.96.0",
+ "fdir": "^6.5.0",
+ "lightningcss": "^1.30.2",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rolldown": "1.0.0-beta.47",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "esbuild": "^0.25.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..6e96392
--- /dev/null
+++ b/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "matchup",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "devDependencies": {
+ "vite": "npm:rolldown-vite@7.2.2"
+ },
+ "overrides": {
+ "vite": "npm:rolldown-vite@7.2.2"
+ },
+ "dependencies": {
+ "@fortawesome/fontawesome-free": "^7.1.0",
+ "bulma": "^1.0.4",
+ "notyf": "^3.10.0"
+ }
+}
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..6cb5040
--- /dev/null
+++ b/public/favicon.ico
Binary files differ
diff --git a/public/icon.png b/public/icon.png
new file mode 100644
index 0000000..655915f
--- /dev/null
+++ b/public/icon.png
Binary files differ
diff --git a/src/court.mjs b/src/court.mjs
new file mode 100644
index 0000000..713c488
--- /dev/null
+++ b/src/court.mjs
@@ -0,0 +1,53 @@
+import { Manager } from "./utils.mjs";
+
+class Court {
+ constructor(id, status) {
+ this.id = id;
+ this.status = typeof status !== "undefined" ? status : true;
+ this.name = "Court " + (id + 1);
+ }
+ toggle() {
+ this.status = !this.status;
+ this.print();
+ }
+ print() {
+ let status = this.status ? "active" : "inactive";
+ console.log(this.name + " is currently " + status);
+ }
+ render() {
+ let color = this.status ? "is-success" : "is-danger";
+ let type = `class="button is-medium ${color}"`;
+ return `<button id=${"court_" + this.id} ${type}>${this.name}</button>`;
+ }
+}
+
+export class CourtManager extends Manager {
+ maxCourtCount = 12;
+
+ constructor() {
+ super("court", []);
+ console.log("Loading court data.");
+ let data = JSON.parse(localStorage.getItem(this.description));
+ if (data === null || data.length != this.maxCourtCount) {
+ console.log("Court data unavailable: " + data);
+ this.generate();
+ return;
+ }
+ for (let i = 0; i < this.maxCourtCount; i++) {
+ this.listData.push(new Court(data[i].id, data[i].status));
+ }
+ console.log("Loaded " + this.listData.length + " courts.");
+ this.render();
+ this.addBehaviour();
+ }
+ generate() {
+ console.log("Generating court data.");
+ for (let i = 0; i < this.maxCourtCount; i++) {
+ this.listData.push(new Court(i));
+ }
+ console.log("Generated data for " + this.listData.length + " courts");
+ this.store();
+ this.render();
+ this.addBehaviour();
+ }
+}
diff --git a/src/game.mjs b/src/game.mjs
new file mode 100644
index 0000000..c8931ac
--- /dev/null
+++ b/src/game.mjs
@@ -0,0 +1,186 @@
+import { Notyf } from 'notyf';
+import { Manager } from "./utils.mjs";
+import { CourtManager } from "./court.mjs";
+import { PlayerManager } from "./player.mjs";
+
+class Team {
+ constructor(playerOne, playerTwo) {
+ this.playerOne = playerOne;
+ this.playerTwo = playerTwo;
+ }
+ print() {
+ console.log(this.playerOne + " and " + this.playerTwo);
+ }
+ render() {
+ let p1 = `<td style="width: 23%; text-align: right;">${this.playerOne}</td>`;
+ let p2 = `<td style="width: 23%; text-align: right;">${this.playerTwo}</td>`;
+ return p1 + p2;
+ }
+}
+
+class Game {
+ constructor(id, teamOne, teamTwo, status) {
+ this.id = id;
+ this.teamOne = teamOne;
+ this.teamTwo = teamTwo;
+ this.status = typeof status !== "undefined" ? status : false;
+ }
+ print() {
+ let teamOne = this.teamOne.playerOne + " and " + this.teamOne.playerTwo;
+ let teamTwo = this.teamTwo.playerOne + " and " + this.teamTwo.playerTwo;
+ console.log(teamOne + " is playing against " + teamTwo);
+ }
+ render() {
+ let blockedState = this.teamOne.playerOne === "RESERVED";
+ let type = blockedState ? `class="is-danger is-dark"` : ``;
+ return `<tr ${type}><td style="width: 8%; text-align: center;">${
+ this.id + 1
+ }</td>${this.teamOne.render()}${this.teamTwo.render()}</tr>`;
+ }
+}
+
+function generateDummyGames(courts, description) {
+ let team = new Team(description, description);
+ let games = [];
+ for (const court of courts) {
+ let game = new Game(court.id, team, team);
+ games.push(game);
+ }
+ return games;
+}
+
+export class GameManager extends Manager {
+ courts = new CourtManager();
+ players = new PlayerManager();
+
+ constructor() {
+ super("game", []);
+ this.round = Number(localStorage.getItem("round")) || 0;
+ console.log("Loading game data.");
+ let data = JSON.parse(localStorage.getItem(this.description));
+ if (data === null || data.length === 0) {
+ console.log("Game data unavailable: " + data);
+ this.generate();
+ return;
+ }
+ for (let i = 0; i < data.length; i++) {
+ let id = Number(data[i].id);
+ let teamOne = new Team(
+ data[i].teamOne.playerOne,
+ data[i].teamOne.playerTwo
+ );
+ let teamTwo = new Team(
+ data[i].teamTwo.playerOne,
+ data[i].teamTwo.playerTwo
+ );
+ let status = Boolean(data[i].status);
+ this.listData.push(new Game(id, teamOne, teamTwo, status));
+ }
+ console.log("Loaded " + this.listData.length + " games.");
+ this.render();
+ }
+ isConfirmed() {
+ return this.listData[0].status;
+ }
+ generate() {
+ console.log("Generating the dummy round.");
+ this.listData = generateDummyGames(this.courts.listData, "---");
+ console.log("Loaded " + this.listData.length + " dummy round.");
+ for (let i = 0; i < this.listData.length; i++) {
+ this.listData[i].status = true;
+ }
+ console.log(this.listData);
+ this.store();
+ this.render();
+ }
+ addBehaviour() {
+ let description = this.isConfirmed() ? "Round " + this.round : "Proposal";
+ document.getElementById("game_description").innerHTML = description;
+ }
+ getBlockedGames() {
+ const inactiveCourts = this.courts.inactive();
+ console.log("Total reserved courts are " + inactiveCourts.length + ".");
+ return generateDummyGames(inactiveCourts, "RESERVED");
+ }
+ inactive(courts) {
+ return generateDummyGames(courts, "---");
+ }
+ active(courts, players) {
+ let games = [];
+ for (let index = 0; index < courts.length; index++) {
+ const startIndex = 4 * index;
+ const endIndex = startIndex + 4;
+ const courtPlayers = players.slice(startIndex, endIndex);
+ const team_1 = new Team(courtPlayers[0].name, courtPlayers[3].name);
+ const team_2 = new Team(courtPlayers[1].name, courtPlayers[2].name);
+ const game = new Game(courts[index].id, team_1, team_2);
+ game.print();
+ games.push(game);
+ }
+ return games;
+ }
+ propose() {
+ console.log("Proposing new games!");
+ let possibleMatches = getMatchCount(
+ this.players.listData,
+ this.courts.listData
+ );
+ console.log("Total possible matches are " + possibleMatches + ".");
+ this.listData = this.getBlockedGames();
+ const activeCourts = this.courts.active();
+ const unusedCourts = activeCourts.slice(possibleMatches + 1);
+ this.listData = this.listData.concat(this.inactive(unusedCourts));
+ const usedCourts = activeCourts.slice(0, possibleMatches);
+ let requiredPlayerCount = possibleMatches * 4;
+ const selectedPlayers = this.players.propose(requiredPlayerCount);
+ console.log("The matches are:");
+ this.listData = this.listData.concat(
+ this.active(usedCourts, selectedPlayers)
+ );
+ this.listData.sort((a, b) => a.id - b.id);
+ this.render();
+ }
+ confirm() {
+ if (this.isConfirmed()) {
+ const notification = new Notyf();
+ notification.error("Create a proposal first!");
+ return;
+ }
+ console.log("Confirm the proposal for round " + this.round + ".");
+ this.round += 1;
+ localStorage.setItem("round", this.round);
+ for (let i = 0; i < this.listData.length; i++) {
+ this.listData[i].status = true;
+ }
+ // Need to update the number of games played here
+ this.store();
+ this.render();
+ document.getElementById("timer-start").click();
+ const notification = new Notyf();
+ notification.success("Round confirmed!");
+ }
+ reset() {
+ localStorage.removeItem("round");
+ localStorage.removeItem(this.description);
+ localStorage.removeItem(this.courts.description);
+ document.getElementById("timer-stop").click();
+ window.location.reload();
+ const notification = new Notyf();
+ notification.error("Session has been reset!");
+ }
+}
+
+function getMatchCount(players, courts) {
+ courts = courts.filter(function (c) {
+ return c.status;
+ });
+ console.log("There are a total of " + courts.length + " courts available.");
+ console.log(players);
+ players = players.filter(function (p) {
+ return p.status;
+ });
+ console.log("There are a total of " + players.length + " players available.");
+ var possibleMatches = Math.floor(players.length / 4);
+ possibleMatches = Math.min(possibleMatches, courts.length);
+ return possibleMatches;
+}
diff --git a/src/guest.mjs b/src/guest.mjs
new file mode 100644
index 0000000..ba9e8cc
--- /dev/null
+++ b/src/guest.mjs
@@ -0,0 +1,69 @@
+import { Manager } from "./utils.mjs";
+
+export class Guest {
+ constructor(id, name, level, status, totalPlayed) {
+ this.id = id;
+ this.status = typeof status !== "undefined" ? status : false;
+ this.name = name;
+ this.level = typeof level !== "undefined" ? level : 1;
+ this.totalPlayed = typeof totalPlayed !== "undefined" ? status : 0;
+ }
+ toggle() {
+ this.status = !this.status;
+ this.print();
+ }
+ print() {
+ let status = this.status ? "active" : "inactive";
+ console.log(this.name + " is currently " + status);
+ }
+ render() {
+ let color = this.status ? "is-success" : "is-danger";
+ let type = `class="button is-medium ${color}"`;
+ return `<button id=${"regular_" + this.id} ${type}>${this.name}</button>`;
+ }
+}
+
+export class GuestManager extends Manager {
+ constructor() {
+ super("guest", []);
+ console.log("Loading guest data.");
+ let data = JSON.parse(localStorage.getItem(this.description));
+ if (data === null || data.length === 0) {
+ console.log("Guest data unavailable: " + data);
+ this.generate();
+ return;
+ }
+ for (let i = 0; i < data.length; i++) {
+ let id = Number(data[i].id);
+ let name = data[i].name;
+ let level = data[i].level;
+ let status = data[i].status;
+ let totalPlayed = data[i].totalPlayed;
+ this.listData.push(new Player(id, name, level, status, totalPlayed));
+ }
+ console.log("Loaded " + this.listData.length + " players.");
+ this.render();
+ }
+ generate() {
+ console.log("Generating guest data.");
+ let maxPlayerCount = 12;
+ for (let i = 0; i < maxPlayerCount; i++) {
+ let level = i < 3 ? 1 : ( i < 6 ? 3 : 7);
+ let p = new Player(i, "Player " + i, level, false, 0);
+ this.listData.push(p);
+ }
+ console.log("Generated data for " + this.listData.length + " guest");
+ this.store();
+ this.render();
+ }
+ active(factor) {
+ factor = typeof factor !== "undefined" ? factor : 1;
+ let data = super.active();
+ console.log("Normalize skill by a factor of " + factor + ".");
+ let players = data.map(function (p) {
+ p.skill = Math.ceil(p.skill / factor);
+ return p;
+ });
+ return players;
+ }
+} \ No newline at end of file
diff --git a/src/main.js b/src/main.js
new file mode 100644
index 0000000..91ac386
--- /dev/null
+++ b/src/main.js
@@ -0,0 +1,20 @@
+import 'notyf/notyf.min.css'
+import 'bulma/css/bulma.css'
+import '@fortawesome/fontawesome-free/css/all.css'
+import './style.css'
+
+import { GameManager } from "./game.mjs";
+
+// Court and player handling
+let g = new GameManager();
+
+// Game handling
+document.getElementById("propose").addEventListener("click", () => {
+ g.propose();
+});
+document.getElementById("confirm").addEventListener("click", () => {
+ g.confirm();
+});
+document.getElementById("reset").addEventListener("click", () => {
+ g.reset();
+});
diff --git a/src/player.mjs b/src/player.mjs
new file mode 100644
index 0000000..adf874d
--- /dev/null
+++ b/src/player.mjs
@@ -0,0 +1,150 @@
+import { GuestManager } from "./guest.mjs";
+import { shuffle, Manager } from "./utils.mjs";
+
+class Player {
+ constructor(id, name, level, status, totalPlayed) {
+ this.id = id;
+ this.status = typeof status !== "undefined" ? status : false;
+ this.name = name;
+ this.level = typeof level !== "undefined" ? level : 1;
+ this.totalPlayed = typeof totalPlayed !== "undefined" ? status : 0;
+ }
+ toggle() {
+ this.status = !this.status;
+ this.print();
+ }
+ print() {
+ let status = this.status ? "active" : "inactive";
+ console.log(this.name + " is currently " + status);
+ }
+ render() {
+ let color = this.status ? "is-success" : "is-danger";
+ let type = `class="button is-medium ${color}"`;
+ return `<button id=${"regular_" + this.id} ${type}>${this.name}</button>`;
+ }
+}
+
+function randomLevel() {
+ const max_level = 10;
+ return Math.floor(Math.random() * max_level + 1);
+}
+
+function randomStatus() {
+ return Math.random() < 0.5;
+}
+
+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, name, level, true, 0);
+}
+
+export class PlayerManager extends Manager {
+ // g = new GuestManager();
+
+ constructor() {
+ super("regular", []);
+ console.log("Loading player data.");
+ let data = JSON.parse(localStorage.getItem(this.description));
+ if (data === null || data.length === 0) {
+ console.log("Player data unavailable: " + data);
+ this.generate();
+ return;
+ }
+ for (let i = 0; i < data.length; i++) {
+ let id = Number(data[i].id);
+ let name = data[i].name;
+ let level = data[i].level;
+ let status = data[i].status;
+ let totalPlayed = data[i].totalPlayed;
+ this.listData.push(new Player(id, name, level, status, totalPlayed));
+ }
+ console.log("Loaded " + this.listData.length + " players.");
+ this.render();
+ this.addBehaviour();
+ addLoadBehaviour(this);
+ }
+ generate() {
+ console.log("Generating player data.");
+ let maxPlayerCount = 71;
+ for (let i = 0; i < maxPlayerCount; i++) {
+ let p = new Player(i, "Player " + i, randomLevel(), randomStatus(), 0);
+ this.listData.push(p);
+ }
+ console.log("Generated data for " + this.listData.length + " players");
+ this.store();
+ this.render();
+ }
+ import(data) {
+ var lines = data.split("\n");
+ var players = [];
+ for (var i = 1; i < lines.length; i++) {
+ if (lines[i] == undefined || lines[i].trim() == "") {
+ continue;
+ }
+ players.push(parseLineToPlayer(lines[i]));
+ }
+ console.log("Loaded " + players.length + " Players");
+ for (var i = 0; i < players.length; i++) {
+ players[i].print();
+ }
+ this.listData = players;
+ this.store();
+ this.render();
+ }
+ active(factor) {
+ factor = typeof factor !== "undefined" ? factor : 1;
+ let data = super.active();
+ console.log("Normalize skill by a factor of " + factor + ".");
+ let players = data.map(function (p) {
+ p.skill = Math.ceil(p.skill / factor);
+ return p;
+ });
+ return players;
+ }
+ propose(count) {
+ console.log("Required players are " + count);
+ let factor = Number(document.getElementById("skill").value);
+ console.log("Skill normalized by a factor of " + factor + ".");
+ const activePlayers = this.active(factor);
+ console.log("Active players are:");
+ console.log(
+ activePlayers
+ .map((i) => {
+ return i.name;
+ })
+ .join(",")
+ );
+ const shuffledPlayers = shuffle(activePlayers);
+ shuffledPlayers.sort((a, b) => a.totalPlayed - b.totalPlayed);
+ const selectedPlayers = shuffledPlayers.slice(0, count);
+ selectedPlayers.sort((a, b) => a.level - b.level);
+ console.log("Selected players are:");
+ console.log(
+ selectedPlayers
+ .map((i) => {
+ return i.name;
+ })
+ .join(",")
+ );
+ return selectedPlayers;
+ }
+}
+
+// TODO Add load behaviour to constructor
+export function addLoadBehaviour(p) {
+ const f = document.querySelector("#player-load input[type=file]");
+ f.addEventListener("change", () => {
+ const file = f.files[0];
+ let importData;
+ console.log("Loading data from " + file.name);
+ const reader = new FileReader();
+ reader.onload = function (e) {
+ importData = reader.result;
+ p.import(importData);
+ };
+ reader.readAsText(file);
+ });
+}
diff --git a/src/style.css b/src/style.css
new file mode 100644
index 0000000..8e3b713
--- /dev/null
+++ b/src/style.css
@@ -0,0 +1,12 @@
+.button-grid {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ grid-gap: 15px;
+}
+#timer-display {
+ text-align: center;
+ width: fit-content;
+}
+table {
+ font-size: 1.5em;
+} \ No newline at end of file
diff --git a/src/timer.js b/src/timer.js
new file mode 100644
index 0000000..09189c0
--- /dev/null
+++ b/src/timer.js
@@ -0,0 +1,88 @@
+let minsPerRound = Number(sessionStorage.getItem("minsPerRound")) || 12;
+let timerRunningFlag = Number(sessionStorage.getItem("timerRunningFlag")) || 0;
+let deadline =
+ Number(sessionStorage.getItem("deadline")) || new Date().getTime();
+let clock = 0;
+
+function setTimer(m, s) {
+ let timer = document.getElementById("timer-display");
+ m = m < 10 ? "0" + m : m;
+ s = s < 10 ? "0" + s : s;
+ timer.value = `${m}:${s}`;
+}
+
+function setSessionData() {
+ sessionStorage.setItem("deadline", deadline);
+ sessionStorage.setItem("minsPerRound", minsPerRound);
+ sessionStorage.setItem("timerRunningFlag", timerRunningFlag);
+}
+
+function resetTimer() {
+ clearInterval(clock);
+ let mins = Math.floor(minsPerRound);
+ let secs = (minsPerRound - mins) * 60;
+ setTimer(mins, secs);
+ timerRunningFlag = 0;
+ setSessionData();
+}
+
+function updateTimer(milliseconds) {
+ const m = Math.floor((milliseconds % (1000 * 60 * 60)) / (1000 * 60));
+ const s = Math.floor((milliseconds % (1000 * 60)) / 1000);
+ setTimer(m, s);
+}
+
+function refreshTimer() {
+ const now = new Date().getTime();
+ const millisecondsToDeadline = deadline - now;
+ if (millisecondsToDeadline > 0) {
+ updateTimer(millisecondsToDeadline);
+ return;
+ }
+ resetTimer();
+ const notification = new Notyf({
+ duration: 0,
+ position: { x: "center", y: "top" },
+ dismissible: true,
+ });
+ notification.error("Round completed");
+}
+
+function startTimer(fresh) {
+ if (fresh) {
+ let now = new Date().getTime();
+ const millisecondsToDeadline = minsPerRound * 60 * 1000;
+ deadline = now + millisecondsToDeadline;
+ }
+ timerRunningFlag = 1;
+ setSessionData();
+ clock = setInterval(refreshTimer, 1000);
+}
+
+window.onload = function () {
+ if (timerRunningFlag) startTimer(0);
+ else resetTimer();
+
+ document.getElementById("timer-start").addEventListener("click", () => {
+ if (timerRunningFlag != 0) return;
+ startTimer(1);
+ });
+
+ document.getElementById("timer-stop").addEventListener("click", () => {
+ resetTimer();
+ });
+
+ document.getElementById("timer-inc").addEventListener("click", () => {
+ if (timerRunningFlag == 0) {
+ minsPerRound += 0.5;
+ resetTimer();
+ }
+ });
+
+ document.getElementById("timer-dec").addEventListener("click", () => {
+ if (timerRunningFlag == 0 && minsPerRound != 0) {
+ minsPerRound -= 0.5;
+ resetTimer();
+ }
+ });
+};
diff --git a/src/utils.mjs b/src/utils.mjs
new file mode 100644
index 0000000..06a2519
--- /dev/null
+++ b/src/utils.mjs
@@ -0,0 +1,51 @@
+export class Manager {
+ constructor(description, listData) {
+ this.description = description;
+ this.listData = listData;
+ this.count = listData.length;
+ }
+ store() {
+ let stringData = JSON.stringify(this.listData);
+ localStorage.setItem(this.description, stringData);
+ }
+ render() {
+ let renderedHTML = this.listData
+ .map((i) => {
+ return i.render();
+ })
+ .join("");
+ let elementId = this.description + "_list";
+ document.getElementById(elementId).innerHTML = renderedHTML;
+ this.addBehaviour();
+ }
+ addBehaviour() {
+ for (let idx = 0; idx < this.listData.length; ++idx) {
+ const elementId = this.description + "_" + this.listData[idx].id;
+ const p = document.getElementById(elementId);
+ p.addEventListener("click", () => {
+ this.listData[idx].toggle();
+ this.store();
+ p.classList.toggle("is-success");
+ p.classList.toggle("is-danger");
+ });
+ }
+ }
+ active() {
+ return this.listData.filter(function (c) {
+ return c.status;
+ });
+ }
+ inactive() {
+ return this.listData.filter(function (c) {
+ return !c.status;
+ });
+ }
+}
+
+export function shuffle(array) {
+ for (let i = array.length - 1; i > 0; i--) {
+ const j = Math.floor(Math.random() * (i + 1));
+ [array[i], array[j]] = [array[j], array[i]];
+ }
+ return array;
+}