From f756de43c5e850b9d7d4ee4535f70a6ca1d044d4 Mon Sep 17 00:00:00 2001 From: Richard Sauer Date: Wed, 8 Apr 2026 13:16:09 +1000 Subject: [PATCH] Add login screen: user picker with initials, new user join, cookie-based session --- app.py | 38 +++++++++++++++++++++++++++----- templates/editor.html | 28 +++++------------------- templates/login.html | 51 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 29 deletions(-) create mode 100644 templates/login.html diff --git a/app.py b/app.py index 94c7c1c..969dca0 100644 --- a/app.py +++ b/app.py @@ -3,7 +3,7 @@ import os import json import time import uuid -from flask import Flask, render_template, request, jsonify, send_from_directory +from flask import Flask, render_template, request, jsonify, redirect, make_response app = Flask(__name__) LAYOUT_DIR = os.path.join(os.path.dirname(__file__), "layouts") @@ -17,7 +17,6 @@ def get_layout(): if os.path.exists(CURRENT_LAYOUT): with open(CURRENT_LAYOUT) as f: return json.load(f) - # First run — copy default default = os.path.join(LAYOUT_DIR, "default.json") with open(default) as f: layout = json.load(f) @@ -28,9 +27,36 @@ def save_layout(layout): with open(CURRENT_LAYOUT, "w") as f: json.dump(layout, f, indent=2) +def get_user(): + return request.cookies.get("bfa_user", "") + @app.route("/") def index(): - return render_template("editor.html") + if not get_user(): + return redirect("login") + return render_template("editor.html", user=get_user()) + +@app.route("/login", methods=["GET", "POST"]) +def login(): + layout = get_layout() + users = layout.get("users", ["Richard", "Rob", "Guido"]) + if request.method == "POST": + name = request.form.get("user", "").strip() + if name: + resp = make_response(redirect("./")) + resp.set_cookie("bfa_user", name, max_age=86400*30) + # Add user to layout if new + if name not in users: + layout.setdefault("users", []).append(name) + save_layout(layout) + return resp + return render_template("login.html", users=users) + +@app.route("/logout") +def logout(): + resp = make_response(redirect("login")) + resp.delete_cookie("bfa_user") + return resp @app.route("/api/layout", methods=["GET"]) def api_get_layout(): @@ -49,7 +75,7 @@ def api_add_comment(): comment = { "id": str(uuid.uuid4())[:8], "target": data["target"], - "user": data["user"], + "user": data.get("user", get_user()), "time": time.strftime("%Y-%m-%dT%H:%M:%S"), "text": data["text"] } @@ -75,12 +101,12 @@ def api_upload_photo(): f = request.files["file"] fname = f"{int(time.time())}_{f.filename}" f.save(os.path.join(PHOTO_DIR, fname)) - return jsonify({"filename": fname, "url": f"/static/photos/{fname}"}) + return jsonify({"filename": fname, "url": f"static/photos/{fname}"}) @app.route("/api/photos", methods=["GET"]) def api_list_photos(): photos = sorted(os.listdir(PHOTO_DIR)) if os.path.exists(PHOTO_DIR) else [] - return jsonify([{"filename": p, "url": f"/static/photos/{p}"} for p in photos if not p.startswith(".")]) + return jsonify([{"filename": p, "url": f"static/photos/{p}"} for p in photos if not p.startswith(".")]) if __name__ == "__main__": app.run(host="0.0.0.0", port=5001, debug=True) diff --git a/templates/editor.html b/templates/editor.html index 1719347..2c6d003 100644 --- a/templates/editor.html +++ b/templates/editor.html @@ -170,8 +170,8 @@ body{font-family:'Segoe UI',system-ui,sans-serif;background:var(--bg);color:var( HMI Design Tool
- - + {{ user }} + Logout | @@ -284,7 +284,7 @@ body{font-family:'Segoe UI',system-ui,sans-serif;background:var(--bg);color:var( let layout = null; let currentPage = 0; let mode = 'edit'; // 'edit' or 'preview' -let currentUser = localStorage.getItem('bfa-user') || ''; +let currentUser = '{{ user }}'; let simState = {}; // id -> {on:bool, value:number} let commentTarget = null; let sortableInstance = null; @@ -303,17 +303,7 @@ async function init() { renderSchematic(); } -function initUsers() { - const sel = document.getElementById('user-select'); - sel.innerHTML = ''; - (layout.users || []).forEach(u => { - const o = document.createElement('option'); - o.value = u; o.textContent = u; - if (u === currentUser) o.selected = true; - sel.appendChild(o); - }); - sel.onchange = () => { currentUser = sel.value; localStorage.setItem('bfa-user', currentUser); }; -} +function initUsers() { /* handled by login page */ } function initSimState() { layout.pages.forEach(p => p.cards.forEach(c => { @@ -324,15 +314,7 @@ function initSimState() { })); } -async function addUser() { - const name = prompt('Enter name:'); - if (!name) return; - await fetch('api/users', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({name})}); - layout.users.push(name); - currentUser = name; - localStorage.setItem('bfa-user', name); - initUsers(); -} +function addUser() { window.location.href = 'logout'; } // ══════════════════════════════════════════════════════ // MODE diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..00ce285 --- /dev/null +++ b/templates/login.html @@ -0,0 +1,51 @@ + + + + + +BFA Banana Dryer — Login + + + + + +