added confused dashboard
This commit is contained in:
@@ -1,9 +1,11 @@
|
|||||||
from flask import Flask
|
from flask import Flask
|
||||||
from .routes import main
|
from flask_socketio import SocketIO
|
||||||
from .models import db
|
from .models import db
|
||||||
from .utils import init_password_generator, init_serializer
|
from .utils import init_password_generator, init_serializer
|
||||||
from .auth import init_auth
|
from .auth import init_auth
|
||||||
|
|
||||||
|
socketio = SocketIO()
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
app = Flask(__name__, static_folder='static', static_url_path='/static')
|
app = Flask(__name__, static_folder='static', static_url_path='/static')
|
||||||
app.config.from_object('config.Config')
|
app.config.from_object('config.Config')
|
||||||
@@ -16,5 +18,8 @@ def create_app():
|
|||||||
init_serializer(app)
|
init_serializer(app)
|
||||||
init_auth(app)
|
init_auth(app)
|
||||||
|
|
||||||
|
socketio.init_app(app)
|
||||||
|
|
||||||
|
from .routes import main
|
||||||
app.register_blueprint(main)
|
app.register_blueprint(main)
|
||||||
return app
|
return app
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ from datetime import datetime
|
|||||||
from typing import List, cast
|
from typing import List, cast
|
||||||
|
|
||||||
import flask_login
|
import flask_login
|
||||||
|
from flask_socketio import join_room
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
from flask import (
|
from flask import (
|
||||||
Blueprint,
|
Blueprint,
|
||||||
@@ -19,6 +21,8 @@ from flask import (
|
|||||||
url_for,
|
url_for,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from . import socketio
|
||||||
|
|
||||||
from .auth import check_password, user_loader
|
from .auth import check_password, user_loader
|
||||||
from .forms import DeckForm, InfoBannerForm, LoginForm, PasswordForm
|
from .forms import DeckForm, InfoBannerForm, LoginForm, PasswordForm
|
||||||
from .models import Deck, InfoBanner, Password, db
|
from .models import Deck, InfoBanner, Password, db
|
||||||
@@ -40,6 +44,10 @@ main = Blueprint('main', __name__)
|
|||||||
slidev_deck_pattern = re.compile(r"^\/slides\/[^\/]+\/\d*(?:\?.*)?$")
|
slidev_deck_pattern = re.compile(r"^\/slides\/[^\/]+\/\d*(?:\?.*)?$")
|
||||||
slidev_all_pattern = re.compile(r"^\/slides\/[^\/]+(?:\/presenter)?\/\d+(?:\?.*)?$")
|
slidev_all_pattern = re.compile(r"^\/slides\/[^\/]+(?:\/presenter)?\/\d+(?:\?.*)?$")
|
||||||
|
|
||||||
|
@socketio.on("connect")
|
||||||
|
def on_connect():
|
||||||
|
if flask_login.current_user.is_authenticated:
|
||||||
|
join_room("instructor")
|
||||||
|
|
||||||
@main.before_request
|
@main.before_request
|
||||||
def ensure_cookie():
|
def ensure_cookie():
|
||||||
@@ -56,7 +64,7 @@ def ensure_cookie():
|
|||||||
@main.after_request
|
@main.after_request
|
||||||
def inject_confused_button(response):
|
def inject_confused_button(response):
|
||||||
if True:
|
if True:
|
||||||
if (slidev_deck_pattern.match(request.path) or request.path == '/')and response.content_type.startswith('text/html'):
|
if (slidev_deck_pattern.match(request.path) or request.path == '/') and response.content_type.startswith('text/html') and not flask_login.current_user.is_authenticated :
|
||||||
if response.direct_passthrough:
|
if response.direct_passthrough:
|
||||||
response.direct_passthrough = False
|
response.direct_passthrough = False
|
||||||
body = response.get_data(as_text=True)
|
body = response.get_data(as_text=True)
|
||||||
@@ -157,8 +165,16 @@ def access_link(access_code: str):
|
|||||||
|
|
||||||
@main.route('/confused', methods=['POST'])
|
@main.route('/confused', methods=['POST'])
|
||||||
def confused():
|
def confused():
|
||||||
resp = jsonify(success=True)
|
|
||||||
return resp
|
socketio.emit(
|
||||||
|
"confused",
|
||||||
|
{
|
||||||
|
"time": datetime.utcnow().isoformat()
|
||||||
|
},
|
||||||
|
to="instructor"
|
||||||
|
)
|
||||||
|
return ("", 204)
|
||||||
|
|
||||||
|
|
||||||
@main.route('/retrieve-token', methods=['POST'])
|
@main.route('/retrieve-token', methods=['POST'])
|
||||||
def set_username_token():
|
def set_username_token():
|
||||||
@@ -293,3 +309,7 @@ def hide_active_banner():
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
return redirect(url_for('main.banner'))
|
return redirect(url_for('main.banner'))
|
||||||
|
|
||||||
|
@main.route('/admin/confused')
|
||||||
|
@flask_login.login_required
|
||||||
|
def confused_notifications():
|
||||||
|
return render_template("confused.html")
|
||||||
|
|||||||
70
app/templates/confused.html
Normal file
70
app/templates/confused.html
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Live Confusion Alerts</title>
|
||||||
|
|
||||||
|
<!-- Tailwind DEV CDN (for production, use your compiled CSS) -->
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.1/socket.io.min.js"></script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body class="bg-gray-100 min-h-screen">
|
||||||
|
|
||||||
|
<div class="max-w-3xl mx-auto mt-12 px-4">
|
||||||
|
<h1 class="text-3xl font-bold mb-6">Live Confusion Alerts</h1>
|
||||||
|
<div id="notif-list" class="space-y-4 text-gray-700">
|
||||||
|
<p class="italic text-gray-500">Waiting for students to click “I’m confused…”</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Socket.IO client (served by your Flask-SocketIO) -->
|
||||||
|
<script>
|
||||||
|
const notifList = document.getElementById("notif-list");
|
||||||
|
const socket = io();
|
||||||
|
|
||||||
|
socket.on("confused", data => {
|
||||||
|
// Remove placeholder if it’s still there
|
||||||
|
if (notifList.children.length === 1 &&
|
||||||
|
notifList.children[0].classList.contains("italic")) {
|
||||||
|
notifList.innerHTML = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a new alert box
|
||||||
|
const box = document.createElement("div");
|
||||||
|
box.className = [
|
||||||
|
"flex items-start",
|
||||||
|
"bg-red-50 border border-red-400",
|
||||||
|
"text-red-800 px-4 py-3 rounded-lg shadow",
|
||||||
|
"relative"
|
||||||
|
].join(" ");
|
||||||
|
|
||||||
|
// Message text
|
||||||
|
const who = data.who || "Someone";
|
||||||
|
const now = new Date().toLocaleTimeString();
|
||||||
|
box.innerHTML = `
|
||||||
|
<div class="flex-1">
|
||||||
|
<strong class="font-semibold">Confused!</strong>
|
||||||
|
<p>${who} clicked “I’m confused.” at ${now}</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Close button
|
||||||
|
const btn = document.createElement("button");
|
||||||
|
btn.className = "ml-4 text-red-600 hover:text-red-900 focus:outline-none";
|
||||||
|
btn.setAttribute("aria-label", "Dismiss");
|
||||||
|
btn.innerHTML = "×";
|
||||||
|
btn.onclick = () => box.remove();
|
||||||
|
|
||||||
|
box.appendChild(btn);
|
||||||
|
|
||||||
|
// Prepend new alert
|
||||||
|
notifList.prepend(box);
|
||||||
|
|
||||||
|
// Auto‐remove after 20 s
|
||||||
|
setTimeout(() => box.remove(), 20000);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
@@ -29,8 +29,14 @@
|
|||||||
|
|
||||||
<!-- Flowbite -->
|
<!-- Flowbite -->
|
||||||
<link href="https://cdn.jsdelivr.net/npm/flowbite@3.1.2/dist/flowbite.min.css" rel="stylesheet" />
|
<link href="https://cdn.jsdelivr.net/npm/flowbite@3.1.2/dist/flowbite.min.css" rel="stylesheet" />
|
||||||
|
|
||||||
|
<!-- in base.html, in <head> or just before </body> -->
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.1/socket.io.min.js"></script>
|
||||||
|
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
||||||
<body class="bg-background text-text min-h-screen font-sans">
|
<body class="bg-background text-text min-h-screen font-sans">
|
||||||
|
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
@@ -129,6 +135,7 @@
|
|||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/flowbite@3.1.2/dist/flowbite.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/flowbite@3.1.2/dist/flowbite.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
function enableUsernameEdit() {
|
function enableUsernameEdit() {
|
||||||
@@ -144,5 +151,5 @@
|
|||||||
saveBtn.classList.remove('hidden');
|
saveBtn.classList.remove('hidden');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user