From 792f070d0f3e9d09e37272785f20f9bff92ad2b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Setni=C4=8Dka?= <setnicka@seznam.cz> Date: Mon, 22 Mar 2021 23:41:09 +0100 Subject: [PATCH] =?UTF-8?q?Zpr=C3=A1vi=C4=8Dky:=20Zobrazen=C3=AD=20a=20pra?= =?UTF-8?q?videln=C3=BD=20reload=20v=20=C3=BA=C4=8Dastnick=C3=A9=20=C4=8D?= =?UTF-8?q?=C3=A1sti?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reloaduje se jednou za 30s z JSON endpointu, při změně počtu zpráv začne blikat záhlaví okna a nová zprávička se zvýrazní. --- mo/web/__init__.py | 1 + mo/web/templates/parts/user_news.html | 6 ++ mo/web/templates/user_contest.html | 14 +++++ mo/web/templates/user_contest_task.html | 13 ++++ mo/web/user.py | 29 ++++++++- static/js/news-reloader.js | 81 +++++++++++++++++++++++++ 6 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 mo/web/templates/parts/user_news.html create mode 100644 static/js/news-reloader.js diff --git a/mo/web/__init__.py b/mo/web/__init__.py index 57d40439..61f8fa51 100644 --- a/mo/web/__init__.py +++ b/mo/web/__init__.py @@ -114,6 +114,7 @@ mo.ext.assets.Assets(app, url_prefix='/assets', asset_dir=static_dir) app.assets.add_assets([ 'bootstrap.min.css', 'mo.css', + 'js/news-reloader.js', ]) diff --git a/mo/web/templates/parts/user_news.html b/mo/web/templates/parts/user_news.html new file mode 100644 index 00000000..b873d205 --- /dev/null +++ b/mo/web/templates/parts/user_news.html @@ -0,0 +1,6 @@ +<div id="novinky"> +{% include "parts/messages.html" %} +</div> +<script type="text/javascript"> +r = new NewsReloader(document.getElementById("novinky"), "{{ url }}", 60000); +</script> diff --git a/mo/web/templates/user_contest.html b/mo/web/templates/user_contest.html index d784945c..c676c073 100644 --- a/mo/web/templates/user_contest.html +++ b/mo/web/templates/user_contest.html @@ -3,6 +3,12 @@ {% set round = contest.round %} {% set state = contest.ct_state() %} +{% block head %} +{% if contest.round.has_messages %} + <script src="{{ asset_url('js/news-reloader.js') }}" type="text/javascript"></script> +{% endif %} +{% endblock %} + {% block title %}{{ round.name }} {{ round.year }}. ročníku kategorie {{ round.category }}: {{ contest.place.name }}{% endblock %} {% block breadcrumbs %} <li><a href='{{ url_for('user_index') }}'>Soutěže</a> @@ -98,4 +104,12 @@ </table> {% endif %} + +{% if contest.round.has_messages %} +<h3>Novinky k soutěži</h3> +{% with title="Novinky k soutěži", url=url_for('user_contest_news', id=contest.contest_id) %} + {% include "parts/user_news.html" %} +{% endwith %} +{% endif %} + {% endblock %} diff --git a/mo/web/templates/user_contest_task.html b/mo/web/templates/user_contest_task.html index 525f1071..01c3263b 100644 --- a/mo/web/templates/user_contest_task.html +++ b/mo/web/templates/user_contest_task.html @@ -3,6 +3,12 @@ {% set round = contest.round %} {% set state = contest.ct_state() %} +{% block head %} +{% if contest.round.has_messages %} + <script src="{{ asset_url('js/news-reloader.js') }}" type="text/javascript"></script> +{% endif %} +{% endblock %} + {% block title %}Úloha {{ task.code }}: {{ task.name }}{% endblock %} {% block breadcrumbs %} <li><a href='{{ url_for('user_index') }}'>Soutěže</a> @@ -85,4 +91,11 @@ {% endif %} {% endif %} +{% if contest.round.has_messages %} +<h3>Novinky k soutěži</h3> +{% with title="Novinky k soutěži", url=url_for('user_contest_news', id=contest.contest_id) %} + {% include "parts/user_news.html" %} +{% endwith %} +{% endif %} + {% endblock %} diff --git a/mo/web/user.py b/mo/web/user.py index a4cf524e..e54667f5 100644 --- a/mo/web/user.py +++ b/mo/web/user.py @@ -1,17 +1,17 @@ -from flask import render_template, request, g, redirect, url_for, flash +from flask import render_template, jsonify, g, redirect, url_for, flash from flask_wtf import FlaskForm import flask_wtf.file from sqlalchemy import and_ from sqlalchemy.orm import joinedload import werkzeug.exceptions import wtforms -import wtforms.validators as validators import mo.config as config import mo.db as db import mo.submit import mo.util from mo.util import logger +from mo.util_format import time_and_timedelta from mo.web import app import mo.web.util @@ -66,9 +66,12 @@ def get_task(contest: db.Contest, id: int) -> db.Task: @app.route('/user/contest/<int:id>/') def user_contest(id: int): + sess = db.get_session() contest = get_contest(id) - task_sols = (db.get_session().query(db.Task, db.Solution) + messages = sess.query(db.Message).filter_by(round_id=contest.round_id).order_by(db.Message.created_at).all() + + task_sols = (sess.query(db.Task, db.Solution) .select_from(db.Task) .outerjoin(db.Solution, and_(db.Solution.task_id == db.Task.task_id, db.Solution.user == g.user)) .filter(db.Task.round == contest.round) @@ -81,10 +84,27 @@ def user_contest(id: int): 'user_contest.html', contest=contest, task_sols=task_sols, + messages=messages, max_submit_size=config.MAX_CONTENT_LENGTH, ) +@app.route('/user/contest/<int:id>/news') +def user_contest_news(id: int): + sess = db.get_session() + contest = get_contest(id) + + messages = sess.query(db.Message).filter_by(round_id=contest.round_id).order_by(db.Message.created_at).all() + + out_messages = [{ + 'title': msg.title, + 'date_format': time_and_timedelta(msg.created_at), + 'body': msg.html, + } for msg in messages] + + return jsonify(out_messages) + + @app.route('/user/contest/<int:id>/task-statement/zadani.pdf') def user_task_statement(id: int): contest = get_contest(id) @@ -108,6 +128,8 @@ def user_contest_task(contest_id: int, task_id: int): task = get_task(contest, task_id) sess = db.get_session() + messages = sess.query(db.Message).filter_by(round_id=contest.round_id).order_by(db.Message.created_at).all() + state = contest.ct_state() if state == db.RoundState.preparing: # Dokud se kolo připravuje nebo čeká na zveřejnění zadání, tak ani nezobrazujeme @@ -164,6 +186,7 @@ def user_contest_task(contest_id: int, task_id: int): sol=sol, papers=papers, form=form, + messages=messages, ) diff --git a/static/js/news-reloader.js b/static/js/news-reloader.js new file mode 100644 index 00000000..04fe721d --- /dev/null +++ b/static/js/news-reloader.js @@ -0,0 +1,81 @@ +class NewsReloader { + news_count = 0; + notification_interval = null; + original_title = ""; + + constructor(element, url, check_interval=60000) { + this.element = element; + this.url = url; + this.check_interval = check_interval; + + this.news_count = element.childElementCount; + this.original_title = document.title; + + var t = this + setInterval(function() { t.refreshNews();}, this.check_interval); + window.addEventListener('focus', function() { + t.news_count = t.element.childElementCount; + t.notificationOff(); + }); + } + + notificationOn(notification) { + clearInterval(this.notification_interval); // clear any previous interval + var t = this; + this.notification_interval = setInterval(function() { + document.title = (t.original_title == document.title) + ? notification + t.original_title + : t.original_title; + }, 1000); + } + + notificationOff() { + if (this.notification_interval) { + clearInterval(this.notification_interval); + document.title = this.original_title; + } + } + + refreshNews() { + var xmlhttp = new XMLHttpRequest(); + var t = this; + xmlhttp.onreadystatechange = function() { + if (this.readyState == 4 && this.status == 200) { + var newsArr = JSON.parse(this.responseText); + var count = newsArr.length + + var markN = 0; // how many elements to mark with class "new" + if (count > t.news_count) { + markN = count - t.news_count; + } + + // Create all new <div>s + var newElements = document.createDocumentFragment(); + for (var i = 0; i < count; i++) { + var div = document.createElement("div"); + div.className = "message"; + if (i + markN >= count) div.className += " new"; // mark N last elements + div.innerHTML = "<span class='msg-title'>" + newsArr[i]["title"] + "</span>" + +"<span class='msg-date'>" + newsArr[i]["date_format"] + "</span>" + +"<div class='msg-text'>" + newsArr[i]["body"] + "</div>"; + newElements.appendChild(div); + } + + // Remove all childs and append new + t.element.innerHTML = ""; + t.element.appendChild(newElements); + + // Notification + if (count != t.news_count) { + if (document.hasFocus()) t.news_count = count; + else { + // Add (1) to the title (with removing any previous value) + t.notificationOn("(" + Math.abs(count - t.news_count).toString() + ") "); + } + } + } + } + xmlhttp.open("GET", this.url, true); + xmlhttp.send(); + } +} -- GitLab