mirror of
http://10.0.2.1:3031/sauer/bfa-dryer-design.git
synced 2026-06-30 08:56:42 +10:00
Add login screen: user picker with initials, new user join, cookie-based session
This commit is contained in:
parent
82678f9c58
commit
f756de43c5
38
app.py
38
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)
|
||||
|
||||
@ -170,8 +170,8 @@ body{font-family:'Segoe UI',system-ui,sans-serif;background:var(--bg);color:var(
|
||||
<span class="sub">HMI Design Tool</span>
|
||||
</div>
|
||||
<div class="toolbar-right">
|
||||
<select id="user-select"></select>
|
||||
<button onclick="addUser()">+ Add Person</button>
|
||||
<span style="color:var(--blue);font-weight:600">{{ user }}</span>
|
||||
<a href="logout" style="color:var(--dim);text-decoration:none;font-size:12px">Logout</a>
|
||||
<span style="color:var(--dim)">|</span>
|
||||
<button class="mode-btn active" id="btn-edit" onclick="setMode('edit')">EDIT</button>
|
||||
<button class="mode-btn" id="btn-preview" onclick="setMode('preview')">PREVIEW</button>
|
||||
@ -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
|
||||
|
||||
51
templates/login.html
Normal file
51
templates/login.html
Normal file
@ -0,0 +1,51 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>BFA Banana Dryer — Login</title>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
:root{--bg:#0a0e17;--card:#131a2b;--border:#1e2a45;--text:#e8ecf4;--text2:#7a8baa;--dim:#4a5670;--blue:#2d7ff9;--amber:#ffab00}
|
||||
body{font-family:'Segoe UI',system-ui,sans-serif;background:var(--bg);color:var(--text);height:100vh;display:flex;align-items:center;justify-content:center}
|
||||
.login-box{width:400px;background:var(--card);border:1px solid var(--border);border-radius:16px;padding:40px;display:flex;flex-direction:column;align-items:center;gap:24px}
|
||||
.login-box h1{font-size:24px;font-weight:700}
|
||||
.login-box h2{font-size:16px;color:var(--text2);font-weight:400}
|
||||
.login-box .divider{width:100%;height:1px;background:var(--border)}
|
||||
.user-list{width:100%;display:flex;flex-direction:column;gap:8px}
|
||||
.user-btn{width:100%;padding:16px;border:2px solid var(--border);border-radius:10px;background:var(--bg);color:var(--text);font-size:18px;font-weight:600;cursor:pointer;text-align:left;transition:all 0.15s}
|
||||
.user-btn:hover{border-color:var(--blue);background:rgba(45,127,249,0.1)}
|
||||
.user-btn .initials{display:inline-flex;width:36px;height:36px;border-radius:50%;background:var(--blue);color:#fff;align-items:center;justify-content:center;font-size:14px;font-weight:700;margin-right:12px}
|
||||
.add-section{width:100%;display:flex;gap:8px}
|
||||
.add-section input{flex:1;padding:12px;border:1px solid var(--border);border-radius:8px;background:var(--bg);color:var(--text);font-size:16px}
|
||||
.add-section input::placeholder{color:var(--dim)}
|
||||
.add-section button{padding:12px 20px;border:none;border-radius:8px;background:var(--blue);color:#fff;font-size:14px;font-weight:600;cursor:pointer}
|
||||
.add-section button:hover{opacity:0.9}
|
||||
.footer{font-size:12px;color:var(--dim)}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<form method="POST" class="login-box">
|
||||
<h1>BFA Banana Dryer</h1>
|
||||
<h2>HMI Design Tool — Who are you?</h2>
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="user-list">
|
||||
{% for u in users %}
|
||||
<button type="submit" name="user" value="{{ u }}" class="user-btn">
|
||||
<span class="initials">{{ u[0] }}</span>{{ u }}
|
||||
</button>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="add-section">
|
||||
<input type="text" name="user" placeholder="Or type your name...">
|
||||
<button type="submit">Join</button>
|
||||
</div>
|
||||
|
||||
<div class="footer">SAE Engineering — Design Collaboration</div>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue
Block a user