diff options
| author | Karan Jayachandra <mail@karanjayachandra.com> | 2025-07-25 16:37:37 +0200 |
|---|---|---|
| committer | Karan Jayachandra <mail@karanjayachandra.com> | 2025-07-25 16:37:37 +0200 |
| commit | 7e480ec648941355c0373197120e81721bc57dd3 (patch) | |
| tree | 6ab2b7a9faebc5707df140453cef66d95494ad4a | |
| parent | c37b96a254c3a88582e4507ea180d63ce65c9af0 (diff) | |
| parent | 38f099ae6ec7e7ae7338402a03654ee6378e4274 (diff) | |
Merge branch 'develop' into 'main'
Develop
See merge request KaranJayachandra/match_up!19
| -rw-r--r-- | CONTRIBUTING.md | 34 | ||||
| -rw-r--r-- | app/__init__.py | 30 | ||||
| -rw-r--r-- | app/static/custom.css | 41 | ||||
| -rw-r--r-- | app/static/file.js | 11 | ||||
| -rw-r--r-- | app/static/timer.js (renamed from app/static/custom.js) | 188 | ||||
| -rw-r--r-- | app/templates/controls.j2 | 83 | ||||
| -rw-r--r-- | app/templates/games.j2 | 42 | ||||
| -rw-r--r-- | app/templates/index.j2 | 11 | ||||
| -rw-r--r-- | app/templates/login.html | 8 | ||||
| -rw-r--r-- | app/templates/macros.j2 | 78 | ||||
| -rw-r--r-- | app/templates/players.j2 | 34 |
11 files changed, 282 insertions, 278 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..9c15148 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,34 @@ +# Contributing + +To contribute you need to have [git](https://git-scm.com/) and [UV](https://docs.astral.sh/uv/) setup. + +## Setup + +Set up the repository via a git clone and setup a new branch using the following commands: + +```bash +git clone git@gitlab.com:KaranJayachandra/match_up.git +cd match_up +git checkout develop +git branch <branch-name> +git checkout <branch-name> +``` + +Now we can install the dependencies using: + +```bash +uv sync +``` + +A database URI needs to be added to let the application know where to create a test database by creating a `.flaskenv` file in the root directory containing the variable `SQLALCHEMY_DATABASE_URI=sqlite:///app.db`. Now, we need to initialize a database for testing via the following commands: + +```bash +uv run flask init +uv run flask migrate +``` + +We are now ready to run the flask application using: + +```bash +uv run flask run +``` diff --git a/app/__init__.py b/app/__init__.py index 3d3b498..0e0ea09 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -31,7 +31,7 @@ g = Session() @app.route("/", methods=["GET"]) def home(): - return render_template("index.j2", url_for=url_for) + return render_template("index.j2", url_for=url_for, login_status=is_logged_in()) @app.route("/control", methods=["GET"]) @@ -46,6 +46,7 @@ def get_list_of_players(): regulars=g.players.regulars(), guests=g.players.guests(), url_for=url_for, + login_status=is_logged_in(), ) @@ -54,19 +55,29 @@ def get_list_of_courts(): return render_template("courts.j2", courts=g.courts.all()) +def login_required(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if not is_logged_in(): + response = Response("User isn't logged in!") + response.headers["HX-Redirect"] = url_for("simplelogin.login") + return response + return f(*args, **kwargs) + + return decorated_function + + @app.route("/propose", methods=["POST"]) +@login_required def get_random_games(): - if not is_logged_in(): - return redirect(url_for("login")) g.levels = int(request.form["levels"].split()[0]) g.teams = request.form["team"] == "Teams" return render_template("games.j2", games=g.propose(), title="Proposal") @app.route("/confirm", methods=["POST"]) +@login_required def confirm_games(): - if not is_logged_in(): - return redirect(url_for("simplelogin.login")) return render_template("games.j2", games=g.confirm(), title=f"Round: {g.round()}") @@ -76,15 +87,6 @@ def clear_games(): "games.j2", games=g.current_round(), title=f"Round: {g.round()}" ) -def login_required(f): - @wraps(f) - def decorated_function(*args, **kwargs): - if not is_logged_in(): - response = Response("User isn't logged in!") - response.headers["HX-Redirect"] = url_for("simplelogin.login") - return response - return f(*args, **kwargs) - return decorated_function @app.route("/reset", methods=["POST"]) @login_required diff --git a/app/static/custom.css b/app/static/custom.css index fafc51a..a5ca36f 100644 --- a/app/static/custom.css +++ b/app/static/custom.css @@ -1,19 +1,34 @@ -button, input, .select, .select select { +button, input, select { width: 100%; -} -table { - font-size: 17.5px; + height: 100%; } td { width: 20%; } -img { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%,-50%); +.grid { + margin-top: 20px; + margin-bottom: 20px; +} +footer { + text-align: center; +} +.parent, .timer { + display: grid; + grid-column-gap: 20px; + grid-row-gap: 20px; + margin-top: 20px; + margin-bottom: 20px; + grid-auto-columns: 1fr; + grid-auto-rows: 1fr; +} +@media (min-width: 900px) { + table { + font-size: xx-large; + } + .parent { + grid-template-columns: repeat(4, 1fr); + } + .timer { + grid-template-columns: 4fr repeat(2, 1fr); + } } -.center { - margin: auto; - width: 50%; -}
\ No newline at end of file diff --git a/app/static/file.js b/app/static/file.js new file mode 100644 index 0000000..403ddf6 --- /dev/null +++ b/app/static/file.js @@ -0,0 +1,11 @@ +window.onload = function() { + const fileInput = document.querySelector("#file-js input[type=file]"); + if (fileInput !== null) { + fileInput.onchange = () => { + if (fileInput.files.length > 0) { + const fileName = document.querySelector("#file-js .file-name"); + fileName.textContent = fileInput.files[0].name; + } + } + }; +}
\ No newline at end of file diff --git a/app/static/custom.js b/app/static/timer.js index 1d36493..e8e7854 100644 --- a/app/static/custom.js +++ b/app/static/timer.js @@ -1,97 +1,91 @@ -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);
- setTimer(minsPerRound, 0);
- 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: 30 * 1000,
- position: {x: 'center', y: 'top'},
- types: [{
- type: 'success',
- background: 'indianred',
- duration: 1000 * 1000,
- dismissible: true
- }]
- });
- notification.success("Round completed");
-}
-
-function startTimer(fresh) {
- if (fresh) {
- now = new Date().getTime();
- const millisecondsToDeadline = minsPerRound * 60 * 1000;
- deadline = now + millisecondsToDeadline;
- }
- timerRunningFlag = 1;
- setSessionData();
- clock = setInterval(refreshTimer, 1000);
-}
-
-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 += 1;
- resetTimer();
- }
-});
-
-document.getElementById("timer-dec").addEventListener('click', ()=> {
- if (timerRunningFlag == 0 && minsPerRound != 0) {
- minsPerRound -= 1;
- resetTimer();
- }
-});
-
-const fileInput = document.querySelector("#file-js input[type=file]");
- if (fileInput !== null) {
- fileInput.onchange = () => {
- if (fileInput.files.length > 0) {
- const fileName = document.querySelector("#file-js .file-name");
- fileName.textContent = fileInput.files[0].name;
- }
- }
-};
\ No newline at end of file +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); + setTimer(minsPerRound, 0); + 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: 30 * 1000, + position: {x: 'center', y: 'top'}, + types: [{ + type: 'success', + background: 'indianred', + duration: 1000 * 1000, + dismissible: true + }] + }); + notification.success("Round completed"); +} + +function startTimer(fresh) { + if (fresh) { + 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 += 1; + resetTimer(); + } + }); + + document.getElementById("timer-dec").addEventListener('click', ()=> { + if (timerRunningFlag == 0 && minsPerRound != 0) { + minsPerRound -= 1; + resetTimer(); + } + }); +}
\ No newline at end of file diff --git a/app/templates/controls.j2 b/app/templates/controls.j2 index 0acf5a6..97351bf 100644 --- a/app/templates/controls.j2 +++ b/app/templates/controls.j2 @@ -1,47 +1,48 @@ {% from "macros.j2" import court_button %} -<div class="fixed-grid has-6-cols"> - <div class="grid"> - {% for court in courts %} - {{ court_button(court) }} - {% endfor %} +<h3>Timer</h3> +<div class="timer"> + <div role="group" style="height: 100%"> + <button id="timer-dec"><i class="fas fa-light fa-minus"></i></button> + <input id="timer-display" type="text" style="text-align:center; height: 100%" readonly></input> + <button id="timer-inc"><i class="fas fa-light fa-plus"></i></button> </div> + <button id="timer-start"><i class="fas fa-light fa-play"></i></button> + <button id="timer-stop"><i class="fas fa-light fa-stop"></i></button> </div> -<div class="columns"> - <div class="column"> - <div class="select is-responsive"> - <select name="levels"> - <option>10 Levels</option> - <option>8 Levels</option> - <option>6 Levels</option> - <option>4 Levels</option> - <option>2 Levels</option> - <option>0 Levels</option> - </select> - </div> - </div> - <div class="column"> - <div class="select is-responsive"> - <select name="team"> - <option>Random</option> - <option>Teams</option> - </select> - </div> - </div> - <div class="column"> - <button class="button is-dark is-responsive" - hx-post="/propose" - hx-target="#games" - hx-include="[name='levels'], [name='team']">Generate</button> - </div> - <div class="column"></div> - <div class="column"> - <button class="button is-dark is-responsive" - hx-post="/confirm" - hx-target="#games">Confirm</button> +<h3>Generation</h3> +<div class="grid"> + <button class="button is-dark is-responsive" + hx-post="/propose" + hx-target="#games" + hx-include="[name='levels'], [name='team']">Generate</button> + <button class="button is-dark is-responsive" + hx-post="/confirm" + hx-target="#games">Confirm</button> + <button class="button is-dark is-responsive" + hx-post="/reset" + hx-target="#games">RESET!</button> +</div> +<div class="grid"> + <div class="select is-responsive"> + <select name="levels"> + <option>10 Levels</option> + <option>8 Levels</option> + <option>6 Levels</option> + <option>4 Levels</option> + <option>2 Levels</option> + <option>0 Levels</option> + </select> </div> - <div class="column"> - <button class="button is-dark is-responsive" - hx-post="/reset" - hx-target="#games">RESET!</button> + <div class="select is-responsive"> + <select name="team"> + <option>Random</option> + <option>Teams</option> + </select> </div> +</div> +<h3>Courts</h3> +<div class="parent"> +{% for court in courts %} + {{ court_button(court) }} +{% endfor %} </div>
\ No newline at end of file diff --git a/app/templates/games.j2 b/app/templates/games.j2 index dff1e0f..00ffb71 100644 --- a/app/templates/games.j2 +++ b/app/templates/games.j2 @@ -1,21 +1,21 @@ -<table class="table is-bordered is-striped is-narrow is-hoverable is-fullwidth" id="games"> -<thead> - <th colspan="5" align="center"> {{ title }} </th> -</thead> -<thead> - <th align="center"> Court </th> - <th colspan="2" align="center"> Team 1 </th> - <th colspan="2" align="center"> Team 2 </th> -</thead> -<tbody> -{% for game in games %} - <tr {% if game.team_1[0].first_name == 'RESERVED' %} class="is-danger" {% endif %}> - <th align="center" style="width: 8%;">{{ game.court.id }}</th> - <td style="width: 23%;"><strong>{{ game.team_1[0].first_name }}</strong>{{ " " + game.team_1[0].last_name }}</th> - <td style="width: 23%;"><strong>{{ game.team_1[1].first_name }}</strong>{{ " " + game.team_1[1].last_name }}</th> - <td style="width: 23%;"><strong>{{ game.team_2[0].first_name }}</strong>{{ " " + game.team_2[0].last_name }}</th> - <td style="width: 23%;"><strong>{{ game.team_2[1].first_name }}</strong>{{ " " + game.team_2[1].last_name }}</th> - </tr> -{% endfor %} -</tbody> -</table>
\ No newline at end of file +<div id="games" class="overflow-auto"> + <h3> {{ title }} </h3> + <table> + <thead> + <th align="center"> Court </th> + <th colspan="2" align="center"> Team 1 </th> + <th colspan="2" align="center"> Team 2 </th> + </thead> + <tbody> + {% for game in games %} + <tr {% if game.team_1[0].first_name == 'RESERVED' %} data-theme="dark" {% endif %}> + <td align="center" style="width: 8%;">{{ game.court.id }}</th> + <td style="width: 23%;"><strong>{{ game.team_1[0].first_name }}</strong>{{ " " + game.team_1[0].last_name }}</th> + <td style="width: 23%;"><strong>{{ game.team_1[1].first_name }}</strong>{{ " " + game.team_1[1].last_name }}</th> + <td style="width: 23%;"><strong>{{ game.team_2[0].first_name }}</strong>{{ " " + game.team_2[0].last_name }}</th> + <td style="width: 23%;"><strong>{{ game.team_2[1].first_name }}</strong>{{ " " + game.team_2[1].last_name }}</th> + </tr> + {% endfor %} + </tbody> + </table> +<div>
\ No newline at end of file diff --git a/app/templates/index.j2 b/app/templates/index.j2 index ce0d531..af6ca4c 100644 --- a/app/templates/index.j2 +++ b/app/templates/index.j2 @@ -3,11 +3,14 @@ <html lang="en"> {{ header(url_for) }} <body> - <section class="section"> - {{ navbar(url_for) }} + <header class="container">{{ navbar(url_for, login_status) }}</header> + <main class="container"> + <h2>Games</h2> <div hx-post="/clear" hx-swap="outerHTML" hx-trigger="load"></div> + <h2>Controls</h2> <div hx-get="/control" hx-swap="outerHTML" hx-trigger="load"></div> - {{ footer() }} - </section> + </main> + <footer class="container">{{ footer() }}</footer> + <script src="{{ url_for('static', filename='timer.js') }}"></script> </body> </html> diff --git a/app/templates/login.html b/app/templates/login.html index a42f2c5..e3109b6 100644 --- a/app/templates/login.html +++ b/app/templates/login.html @@ -3,8 +3,8 @@ <html lang="en"> {{ header(url_for) }} <body> - <section class="section"> - {{ navbar(url_for) }} + <header class="container">{{ navbar(url_for) }}</header> + <main class="container"> <form action="{{ url_for('simplelogin.login') }}" method="post"> <div class="form-group"> {{ form.csrf_token }} @@ -16,8 +16,8 @@ </form> <input class="button is-dark" type="submit" value="Login"> </form> - {{ footer() }} - </section> + </main> + <footer class="container">{{ footer() }}</footer> </body> </html>
\ No newline at end of file diff --git a/app/templates/macros.j2 b/app/templates/macros.j2 index e08bfe1..e82b910 100644 --- a/app/templates/macros.j2 +++ b/app/templates/macros.j2 @@ -1,93 +1,45 @@ {% macro court_button(court) -%} -<button class= "button is-responsive {% if court.status %}is-success{% else %}is-danger{% endif %}" +<button class= "{% if court.status %}{% else %}contrast{% endif %}" hx-post="{{ "/court-toggle/" ~ court.id}}" hx-swap="outerHTML"> Court {{ court.id }}</button> {%- endmacro %} {% macro player_button(player) -%} -<button class="button is-responsive {% if player.status %}is-success{% else %}is-danger {% endif %}" +<button class="{% if player.status %}{% else %}contrast{% endif %}" hx-post="{{ "/player-toggle/" ~ player.id}}" hx-swap="outerHTML"> {{ player.first_name + " " + player.last_name}} </button> {%- endmacro %} -{% macro guest_control(level, value) -%} -<div class="field center has-addons is-centered" id="guests_{{level}}"> - <div class="control"> - <button class="button is-responsive is-dark" - hx-post="/decrement_guest" - hx-swap="outerHTML" - hx-vals='{"level": "{{ level }}"}' - hx-target="#guests_{{ level }}"><span class="icon"><i class="fas fa-light fa-minus"></i></span></button> - </div> - <div class="control"> - <input class="input is-responsive" type="text" style="text-align:center" value="{{ value }}" disabled> - </div> - <div class="control"> - <button class="button is-responsive is-dark" - hx-post="/increment_guest" - hx-swap="outerHTML" - hx-vals='{"level": "{{ level }}"}' - hx-target="#guests_{{ level }}"><span class="icon"><i class="fas fa-light fa-plus"></i></span></button> - </div> -</div> -{%- endmacro %} - {% macro header(url_for) -%} <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Match Up!</title> <link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}"> -<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@1.0.4/css/bulma.min.css"> +<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/notyf@3/notyf.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css"> <link rel="stylesheet" href="{{ url_for('static', filename='custom.css') }}"> <script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.6/dist/htmx.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/notyf@3/notyf.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/js/fontawesome.min.js"></script> -<script defer src="{{ url_for('static', filename='custom.js') }}"></script> </head> {%- endmacro %} {% macro footer() -%} -<div class="content has-text-centered">Made by <a href="https://karanjayachandra.com/">Karan</a> using <a href="https://flask.palletsprojects.com">Flask</a>, <a href="https://htmx.org/">HTMX</a>, <a href="https://carlosroso.com/notyf/">Notyf</a> and <a href="https://bulma.io">Bulma</a></div> +<div class="container">Made by <a href="https://karanjayachandra.com/">Karan</a> with <i class="fas fa-heart"></i></div> {%- endmacro %} -{% macro navbar(url_for) -%} -<nav class="level"> - <p class="level-item has-text-centered"> - <button class="button is-link" onclick="location.href = '/';">Games</button> - </p> - <p class="level-item has-text-centered"> - <button class="button is-link" onclick="location.href = '/players';">Players</button> - </p> - <div class="level-item has-text-centered"></div> - <div class="level-left"></div> - <div class="level-item has-text-centered"> - <p class="title">Match Up!</p> - </div> - <div class="level-item has-text-centered"></div> - <div class="level-right"> - <div class="field has-addons has-addons-centered"> - <div class="control"><button class="button is-dark" id="timer-dec"> - <span class="icon"><i class="fas fa-light fa-minus"></i></span> - </button></div> - <p class="control has-icons-left has-icons-right"> - <input class="input is-responsive" id="timer-display" type="text" style="text-align:center; " readonly> - <span class="icon is-small is-left"> - <i class="fas fa-light fa-clock"></i> - </span> - </p> - <div class="control"><button class="button is-dark" id="timer-inc"> - <span class="icon"><i class="fas fa-light fa-plus"></i></span> - </button></div> - <div class="control"><button class="button is-dark" id="timer-start"> - <span class="icon"><i class="fas fa-light fa-play"></i></span> - </button></div> - <div class="control"><button class="button is-dark" id="timer-stop"> - <span class="icon"><i class="fas fa-light fa-stop"></i></span> - </button></div> - </div> - </div> + +{% macro navbar(url_for, login_status) -%} +<nav> + <ul> + <li><strong>Match Up!</strong></li> + </ul> + <ul> + <li><a href="/">Games</a></li> + <li><a href="/players">Players</a></li> + {% if login_status %}<li><a href="/logout/">Logout</a></li>{% else %}<li><a href="/login/">Login</a></li>{% endif %} + </ul> </nav> {%- endmacro %}
\ No newline at end of file diff --git a/app/templates/players.j2 b/app/templates/players.j2 index 16e3878..bacb215 100644 --- a/app/templates/players.j2 +++ b/app/templates/players.j2 @@ -3,46 +3,38 @@ <html lang="en"> {{ header(url_for) }} <body> - <section class="section"> - {{ navbar(url_for) }} - <h4 class="title is-4">Members</h4> - <div class="fixed-grid has-4-cols"> - <div class="grid"> + <header class="container">{{ navbar(url_for, login_status) }}</header> + <main class="container"> + <h3 >Members</h3> + <div class="parent"> {% for regular in regulars %} <div class="cell"> {{ player_button(regular) }} </div> {% endfor %} </div> - <h4 class="title is-4">Guests</h4> - <div class="fixed-grid has-4-cols"> - <div class="grid"> + <h3>Guests</h3> + <div class="parent"> {% for guest in guests %} <div class="cell"> {{ player_button(guest) }} </div> {% endfor %} </div> - </section> - <section class="section"> - <div class="column"> + <h3>Controls</h3> + <div class="grid"> <button class="button is-dark is-responsive" hx-post="/reset_players">RESET!</button> </div> - <div class="column"> + <h3>Player Update</h3> + <div class="grid"> <form method="POST" enctype="multipart/form-data" hx-post="/update_players"> <div id="file-js" class="file has-name is-fullwidth"> <label class="file-label"> <input class="file-input" type="file" name="players" /> - <span class="file-cta"> - <span class="file-icon"> - <i class="fas fa-upload"></i> - </span> - <span class="file-label"> Choose a file⦠</span> - </span> - <span class="file-name"> No file uploaded </span> </label> <button class="button is-dark is-responsive">UPDATE!</button> </div> </form> </div> - {{ footer() }} - </section> + <footer class="container">{{ footer() }}</footer> + <script src="{{ url_for('static', filename='file.js') }}"></script> + </main> </body> </html>
\ No newline at end of file |
