From e8b020e28ca377868ff8aad8418372debc910ae6 Mon Sep 17 00:00:00 2001 From: User Content Date: Wed, 30 Oct 2019 01:11:47 +0000 Subject: Initial commit --- Makefile | 14 +++ config-example.json | 22 ++++ rater.py | 172 ++++++++++++++++++++++++++ requirements.txt | 14 +++ static/rater.css | 292 ++++++++++++++++++++++++++++++++++++++++++++ static/rater.js | 337 +++++++++++++++++++++++++++++++++++++++++++++++++++ templates/index.html | 122 +++++++++++++++++++ 7 files changed, 973 insertions(+) create mode 100644 Makefile create mode 100644 config-example.json create mode 100755 rater.py create mode 100644 requirements.txt create mode 100644 static/rater.css create mode 100644 static/rater.js create mode 100644 templates/index.html diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..18dfbcd --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +all: install + +import: + venv/bin/python3 ./rater.py -i + +run: + PYTHONIOENCODING=utf-8 venv/bin/python3 ./rater.py + +venv: + python3 -m venv ./venv + +install: venv + venv/bin/pip install --upgrade pip + venv/bin/pip install -r requirements.txt diff --git a/config-example.json b/config-example.json new file mode 100644 index 0000000..5c4e4bc --- /dev/null +++ b/config-example.json @@ -0,0 +1,22 @@ +{ + "track": "polsoc", + "track-name": "Ethics%2C+Society+%26+Politics", + "host": "127.0.0.1", + "port": 5000, + "frab-url": "https://frab.cccv.de/", + "frab-user": "", + "frab-password": "", + "frab-conference": "36C3", + "rt-url": "https://rt.cccv.de/", + "rt-user": "", + "rt-password": "", + "categories": [ "Relevanz", "Expertise", "Praesentation", "Nachfrage" ], + "frab-person-map": { + "anna": 12345, + "ben": 6789 + }, + "rt-person-map": { + "anna": "anna@mail.org", + "ben": "ben@mailbox.com" + } +} diff --git a/rater.py b/rater.py new file mode 100755 index 0000000..e1ed9c5 --- /dev/null +++ b/rater.py @@ -0,0 +1,172 @@ +#!venv/bin/python + +from flask import Flask, render_template, jsonify, request +from flask_sqlalchemy import SQLAlchemy +from lxml import etree +from argparse import ArgumentParser +import requests +import json + +# Use this on FreeBSD when you've compiled pyopenssl with openssl from ports +# import urllib3.contrib.pyopenssl +# urllib3.contrib.pyopenssl.inject_into_urllib3() + +parser = ArgumentParser(description="C3 rating helper") +parser.add_argument("-i", action="store_true", dest="frab_import", default=False, help="import events from frab") +parser.add_argument("-c", "--config", help="Config file location", default="./config.json") +args = parser.parse_args() + +with open(args.config, mode="r", encoding="utf-8") as json_file: + config_data = json.load(json_file) + +app = Flask(__name__) +app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///'+config_data.get('frab-conference')+'-'+config_data.get('track')+'.db' +app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False +app.config['SECRET_KEY'] = 'Silence is golden. Gerd Eist.' +app.jinja_env.trim_blocks = True +app.jinja_env.lstrip_blocks = True + +db = SQLAlchemy(app) + +class Event(db.Model): + """An event as dumped from frab""" + frab_id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String(1024)) + subtitle = db.Column(db.String(1024)) + abstract = db.Column(db.Text()) + description = db.Column(db.Text()) + state = db.Column(db.String(64)) + event_type = db.Column(db.String(64)) + speakers = db.Column(db.String(1024)) + coordinator = db.Column(db.String(1024)) + notes = db.Column(db.Text()) + +class EventRating(db.Model): + """A rating as given by a logged in user""" + id = db.Column(db.Integer, primary_key=True) + submitter = db.Column(db.String(1024)) + event_id = db.Column(db.Integer, db.ForeignKey('event.frab_id')) + event = db.relationship('Event', backref=db.backref('ratings', lazy='dynamic')) + comment = db.Column(db.Text()) + rating_dict = db.Column(db.String(1024), server_default="{}") + +@app.route("/") +def root(): + events = Event.query.all() + return render_template('index.html', events=events, json=json, config=config_data, cat=config_data.get('categories')) + +@app.route('/api/ratings') +def get_ratings(): + return jsonify(EventRating.query.all()) + +@app.route('/api/set_event_state/', methods=['POST']) +def set_sevent_state(eventid): + content = request.json + dbevent = Event.query.get(eventid) + dbevent.state = content.get('state', 'new') + db.session.commit() + return jsonify({"result":"ok"}) + +@app.route('/api/set_event_coordinator/', methods=['POST']) +def set_sevent_coordinator(eventid): + content = request.json + dbevent = Event.query.get(eventid) + dbevent.coordinator = content['coordinator'] + db.session.commit() + return jsonify({"result":"ok"}) + +@app.route('/api/remove_event/', methods=['POST']) +def remove_event(eventid): + dbevent = Event.query.get(eventid) + if dbevent.state == 'gone': + db.session.delete(dbevent) + db.session.commit() + return jsonify({"result":"ok"}) + +@app.route('/api/remove_rating/', methods=['POST']) +def remove_rating(eventid): + content = request.json + rating = EventRating.query.filter_by(event_id = eventid, submitter = content['author']).first() + if rating: + db.session.delete(rating) + db.session.commit() + return jsonify({"result":"ok"}) + +@app.route('/api/add_rating/', methods=['POST']) +def add_rating(eventid): + content = request.json + print ( str(eventid) + " " + str(content)) + r = content.get('ratings', '{}'); + + rating = EventRating.query.filter_by(event_id = eventid, submitter = content['author']).first() + + rd = json.dumps({ k: r.get(k,'0') for k in ['category1', 'category2', 'category3', 'category4'] }) + if rating: + if 'comment' in content: + rating.comment = content['comment'] + rating.rating_dict = rd + else: + db.session.add( EventRating( submitter = content.get('author','anonymous'), event_id = eventid, comment = content['comment'], rating_dict = rd)) + + db.session.commit() + return jsonify({"result":"ok"}) + +def fetch_talks(config): + sess = requests.Session() + new_session_page = sess.get(config.get('frab-url')) + tree = etree.HTML(new_session_page.text) + auth_token = tree.xpath("//meta[@name='csrf-token']")[0].get("content") + login_data = dict() + login_data['user[email]'] = config.get('frab-user') + login_data['user[password]'] = config.get('frab-password') + login_data['user[remember_me]'] = 1 + login_data['authenticity_token'] = auth_token + + frab = config.get('frab-url') + conf = config.get('frab-conference') + track = config.get('track-name') + + sess.post(frab + 'users/sign_in?conference_acronym=' + conf + '&locale=en', login_data, verify=False) + response = sess.get(frab + 'en/'+conf+'/events?track_name=' + track + '&format=json', verify=False, stream=True) + + talks_json = json.loads(response.text) + +# with open('dump.txt', mode='wb') as localfile: +# localfile.write(response.content) + + imported = 0 + for json_event in talks_json['events']: +# print (json_event) + rawhtml = sess.get(frab + 'en/' + conf + '/events/'+ str(json_event['id']), verify=False, stream=True) + tree = etree.HTML(rawhtml.text) + submission_notes = tree.xpath('//b[text()="Submission Notes(user and admin):"]')[0].tail.strip() + + dbevent = Event.query.get(json_event['id']) + speakers = { speaker['id']: speaker['full_public_name'] for speaker in json_event['speakers'] } + if dbevent: + dbevent.title = json_event['title'] + dbevent.subtitle = json_event['subtitle'] + dbevent.abstract = json_event['abstract'] + dbevent.description = json_event['description'] + dbevent.event_type = json_event['type'] + dbevent.notes = submission_notes + if 'state' in json_event: + if json_event['state'] != 'new' or dbevent.state == 'gone': + dbevent.state = json_event['state'] + dbevent.speakers = json.dumps(speakers) + else: + db.session.add( Event( frab_id = json_event['id'], title = json_event['title'], subtitle = json_event['subtitle'], abstract = json_event['abstract'], description = json_event['description'], speakers = json.dumps(speakers), state = json_event.get('state', 'new'), event_type = json_event['type'], notes = submission_notes) ) + imported += 1 + for goner in Event.query.filter( Event.frab_id.notin_([ ev['id'] for ev in talks_json['events'] ])).all(): + goner.state = 'gone' + db.session.commit() + print ('Conference: ' + conf + ', track: ' + track + ', imported ' + str(len(talks_json['events'])) + ' events, ' + str(imported) + ' new.') + + +if __name__ == "__main__": + db.create_all() + if args.frab_import: + fetch_talks(config_data) + else: + app.run(host=config_data.get('host'), port=int(config_data.get('port'))) + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..fa92333 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,14 @@ +certifi==2017.7.27.1 +chardet==3.0.4 +click==6.7 +Flask==0.12.2 +Flask-SQLAlchemy==2.2 +idna==2.6 +itsdangerous==0.24 +Jinja2==2.9.6 +lxml==3.8.0 +MarkupSafe==1.0 +requests==2.18.4 +SQLAlchemy==1.1.14 +urllib3==1.22 +Werkzeug==0.12.2 diff --git a/static/rater.css b/static/rater.css new file mode 100644 index 0000000..ae54a13 --- /dev/null +++ b/static/rater.css @@ -0,0 +1,292 @@ +body { + font-family: "HelveticaNeueLight", "HelveticaNeue-Light", "Helvetica Neue Light", "HelveticaNeue", "Helvetica Neue", 'TeXGyreHerosRegular', "Helvetica", "Tahoma", "Geneva", "Arial", sans-serif; font-weight:200; font-stretch:normal; +} + +.username-wrapper { + position: absolute; + right: 5px; +} + +.changed { + background-color: #00f000; + color: white; +} + +.clearfix { + clear: both; +} + +#event-list { + padding-left: 0em; + list-style-type: none; +} + +.main-button { + vertical-align: top; + text-align: center; + height: 40px; + width: 40px; + padding: 0; +} + +.mini-button { + margin-right: 0.2em; +} + +button { + background-color: #759ae9; + background-image: linear-gradient(top, #759ae9 0%, #376fe0 50%, #1a5ad9 50%, #2463de 100%); + background-image: -webkit-linear-gradient(top, #759ae9 0%, #376fe0 50%, #1a5ad9 50%, #2463de 100%); + border-top: 1px solid #1f58cc; + border-right: 1px solid #1b4db3; + border-bottom: 1px solid #174299; + border-left: 1px solid #1b4db3; + border-radius: 4px; + box-shadow: inset 0 0 2px 0 rgba(57, 140, 255, 0.8); + color: #fff; + text-shadow: 0 -1px 1px #1a5ad9; +} + +.event-list-item { + vertical-align: top; + background-color: #f6f6f6; + margin-bottom: 0.5em; + border: 1px solid silver; + border-radius: 10px; + padding: 0 0.5em 0.5em 0.5em; + box-sizing: border-box; +} + +.event-list-item[event_state='gone'] { + background-image: + repeating-linear-gradient( + 45deg, + #eee, + #eee 20px, + #ddd 20px, + #ddd 40px /* determines size */ + ); +} + +.event-list-item[event_type=meeting] .event-title:before { content: 'MEETING '; color: red; font-size: smaller; } +.event-list-item[event_type=workshop] .event-title:before { content: 'WORKSHOP '; color: red; font-size: smaller; } +.event-list-item[event_type=concert] .event-title:before { content: 'CONCERT '; color: red; font-size: smaller; } +.event-list-item[event_type=film] .event-title:before { content: 'FILM '; color: red; font-size: smaller; } +.event-list-item[event_type=other] .event-title:before { content: 'OTHER '; color: red; font-size: smaller; } +.event-list-item[event_type=podium] .event-title:before { content: 'PODIUM '; color: red; font-size: smaller; } +.event-list-item[event_type=performance] .event-title:before { content: 'PERFORMANCE '; color: red; font-size: smaller; } +.event-list-item[event_type=lightning_talk] .event-title:before { content: 'LIGHTNING '; color: red; font-size: smaller; } + +body.two-column .event-list-item { + display: inline-block; + width: 48%; + margin-right: 1%; +} + +body.three-column .event-list-item { + display: inline-block; + width: 32%; + margin-right: 1%; +} + +body.four-column .event-list-item { + display: inline-block; + width: 23.5%; + margin-right: 1%; +} + +.event-rating { + display: inline-block; + width: 15em; + margin: 1em 1em 0 0; + padding: 0.2em; + background-color: #f0f0f0; + border-radius: 10px; + vertical-align:top; + font-size: smaller; +} + +.event-title, +.event-subtitle { + display: inline; + font-weight: bold; +} + +.event-subtitle { + font-size: smaller; +} + +.event-rating-comment { + min-height: 3em; + margin-top: 0.1em; + background-color: white; +} + +#Filter, #Username { + font-size: x-large; + height: 40px; +} + +.label { + float: left; + min-width: 8em !important; + font-style: italic; +} + +.event-persons { + margin-top: 0.2em; + margin-bottom: 0.2em; +} + +.event-speaker, .event-coordinator { + display: inline; +} + +.event-coordinator { + margin-right: 0.5em; +} + +.slider { + display: inline; +} + +.event-notes, +.event-description, +.event-abstract { + height: 1.2em; + overflow: hidden; + cursor: zoom-in; + margin-bottom: 0.2em; + margin-right: 2em; + text-overflow: ellipsis; + white-space: nowrap; +} + +.event-notes.full, +.event-description.full, +.event-abstract.full { + cursor: zoom-out; + background: white; + overflow: visible; + height: auto !important; + white-space: initial; +} + +body.only-lectures .lectures-button, +body.only-todo .todo-button, +body.show-ratings .ratings-button, +body.two-column .two-columns, +body.three-column .three-columns, +body.four-column .four-columns, +.event-list-item.editing .edit-button, +.event-list-item[event_state='accepted'] .accept-button, +.event-list-item[event_state='rejected'] .reject-button, +.event-list-item.i-am-coordinator .take-button +{ + background-image: -webkit-linear-gradient(top, #f37873 0%, #db504d 50%, #cb0500 50%, #a20601 100%); + background-image: linear-gradient(top, #f37873 0%, #db504d 50%, #cb0500 50%, #a20601 100%); +} + +output { + margin-left: 1em; +} + +#event-own-rating, +.event-ratings { + display: none; + visibility: hidden; +} + +body.show-ratings .event-ratings, +.event-list-item.editing #event-own-rating { + display: block; + visibility: initial; +} + +#event-own-rating textarea { + min-width: 10em; + max-width: 20em; + width: 80%; +} + +.event-meter-bar { + box-sizing: initial; + display: inline; + float: left; + width: 65px; + vertical-align: top; + margin: 0; + padding: 0; +} + +.has-own-rating .event-meter-bar { + height: 65px; +} +.has-own-rating .event-meter-bar:after { + color: #ff0000; + z-index: 1; + content: '✓'; + font-size: 5em; + line-height: 1; + text-align: center; + position: relative; + left: 0; top: -65px; + width: 65px; + height: 65px; + opacity: 0.2; +} + +body.safari meter { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +meter { + vertical-align: top; + width: 60px; + margin-top: 0.3em; + border-radius: 3px; + height: 8px; + + -moz-appearance: none; +} + +meter::-moz-meter-bar { + background: #ddd; +} + +:-moz-meter-optimum::-moz-meter-bar { + background: #afa; +} + +meter.meter-4::-moz-meter-bar { + background: #fdd; +} + +meter::-webkit-meter-bar { + background: #ddd; + box-shadow: 0 2px 3px rgba (0, 0, 0, 0.2) inset; + border-radius: 3px; +} + +meter.meter-4::-webkit-meter-bar { + background: #fdd; +} + +#status { + text-align: center; + font-size: xx-large; + font-weight: bold; + margin-top: 0.5em; + width: 100%; + clear: both; +} + +body.only-lectures .event-list-item:not([event_type="lecture"]), +body.only-todo .event-list-item.has-own-rating, +.filtered, +.hidden { + display: none !important; + visibility: hidden !important; +} diff --git a/static/rater.js b/static/rater.js new file mode 100644 index 0000000..ec1bcfe --- /dev/null +++ b/static/rater.js @@ -0,0 +1,337 @@ +function changed_name() { + var username = localStorage.getItem("c3-rating-user"); + var newname = document.getElementById('Username').value; + document.getElementById('Username').classList.toggle('changed', username != newname); +} + +function changed_filter() { + var filtertext = document.getElementById('Filter').value.toLowerCase(); + if (filtertext == '') { + document.querySelectorAll('.event-list-item.filtered').forEach(function(ev) { + ev.classList.remove("filtered"); + }); + return; + } + + if (!window.inner_texts) { + window.inner_texts = {}; + document.querySelectorAll('.event-list-item').forEach(function(ev) { + inner_texts[ev.getAttribute('id')] = ev.innerText.toLowerCase(); + }); + } + + Object.keys(window.inner_texts).forEach(function (eid) { + var elem = document.getElementById(eid); + elem.classList.toggle('filtered', window.inner_texts[eid].indexOf(filtertext) < 0); + }); +} + +function confirm_name() { + localStorage.setItem("c3-rating-user", document.getElementById('Username').value); + document.getElementById('Username').classList.remove("changed"); + update_status(); +} + +function toggleHidden(name) { + document.getElementById(name).classList.toggle("hidden"); +} + +function toggleEdit(eid) { + var username = document.getElementById('Username').value; + if (!username) { + alert( "Please set your name before rating."); + return; + } + + var ev = document.getElementById('event-'+eid); + if (ev.classList.contains('editing')) { + ev.classList.toggle('editing', false); + return; + } + + var other_in_edit = document.getElementsByClassName('editing'); + if (other_in_edit.length) + other_in_edit[0].classList.remove('editing'); + + ev.classList.toggle('editing', true); + + var own_rating = document.getElementById('event-own-rating'); + ev.appendChild(own_rating); + + var myrating = document.querySelectorAll('div#event-rating-'+eid+'[submitter="'+username+'"]'); + var mycomment = ''; + if (myrating.length) { + mycomment = myrating[0].getElementsByClassName('event-rating-comment')[0].innerHTML; + myrating[0].querySelectorAll('meter').forEach(function(meter) { + var category = meter.getAttribute('category'); + document.querySelectorAll('#event-own-rating .slider input[category='+category+']')[0].value = meter.value; + changeVal('event-'+category+'-output', meter.value); + }); + } else { + document.querySelectorAll('#event-own-rating .slider input').forEach(function(sl) { + sl.value = 0; + var category = sl.getAttribute('category'); + changeVal('event-'+category+'-output', '0'); + }); + } + document.getElementById('event-comment').value = mycomment; + own_rating.querySelectorAll('button.remove-rating')[0].classList.toggle('hidden', !myrating.length); +} + +function changeVal(el, value) { + document.getElementById(el).innerHTML = value.toString() + " %"; +} + +function twocol() { + document.body.classList.toggle('two-column'); + document.body.classList.remove('three-column'); + document.body.classList.remove('four-column'); +} +function threecol() { + document.body.classList.remove('two-column'); + document.body.classList.toggle('three-column'); + document.body.classList.remove('four-column'); +} +function fourcol() { + document.body.classList.remove('two-column'); + document.body.classList.remove('three-column'); + document.body.classList.toggle('four-column'); +} + +function invert_sort() { + var evl = document.getElementById('event-list'); + var nodes = Array.prototype.slice.call(evl.getElementsByClassName('event-list-item')).reverse().forEach(function(el) { + evl.appendChild(el); + }); +} + +function sort_by(order_function) { + var evl = document.getElementById('event-list'); + Array.prototype.slice.call(evl.getElementsByClassName('event-list-item')).sort(order_function).forEach(function(el) { + evl.appendChild(el); + }); +} + +function myrating_count_sort(elem1, elem2) { + var username = document.getElementById('Username').value; + return elem2.querySelectorAll('.event-rating[submitter="'+username+'"]').length - elem1.querySelectorAll('.event-rating[submitter="'+username+'"]').length; +} + +function random_sort(elem1, elem2) { return !!Math.floor(Math.random() * 2) ? -1 : 1; } +function rating_count_sort(elem1, elem2) { return elem2.querySelectorAll('.event-rating').length - elem1.querySelectorAll('.event-rating').length; } +function rating_1_sort(elem1, elem2) { return elem2.getElementsByClassName('meter-0')[0].getAttribute('value') - elem1.getElementsByClassName('meter-0')[0].getAttribute('value'); } +function rating_2_sort(elem1, elem2) { return elem2.getElementsByClassName('meter-1')[0].getAttribute('value') - elem1.getElementsByClassName('meter-1')[0].getAttribute('value'); } +function rating_3_sort(elem1, elem2) { return elem2.getElementsByClassName('meter-2')[0].getAttribute('value') - elem1.getElementsByClassName('meter-2')[0].getAttribute('value'); } +function rating_4_sort(elem1, elem2) { return elem2.getElementsByClassName('meter-3')[0].getAttribute('value') - elem1.getElementsByClassName('meter-3')[0].getAttribute('value'); } +function rating_5_sort(elem1, elem2) { return elem2.getElementsByClassName('meter-4')[0].getAttribute('value') - elem1.getElementsByClassName('meter-4')[0].getAttribute('value'); } +function coordinator_sort(elem1, elem2) { return get_coordinator(elem1).localeCompare(get_coordinator(elem2)); } +function state_sort(elem1, elem2) { return elem2.getAttribute('event_state').localeCompare(elem1.getAttribute('event_state')); } + +function get_coordinator(elem) { + var coordinator = elem.getElementsByClassName('event-coordinator'); + if (coordinator.length) + return coordinator[0].getAttribute('coordinator'); + return ''; +} + +function do_remove_rating() { + if (confirm('are you sure?') == false) + return; + + var in_edit = document.getElementsByClassName('editing'); + if (!in_edit.length) + return; + var eid = in_edit[0].getAttribute('id').replace(/^event-/, ''); + + var xhttp = new XMLHttpRequest(); + xhttp.open("POST", "api/remove_rating/"+eid, true); + xhttp.setRequestHeader("Content-type", "application/json"); + + var username = document.getElementById('Username').value; + + xhttp.onreadystatechange = function() { + if (xhttp.readyState == XMLHttpRequest.DONE && xhttp.status == 200) { + var myrating = document.querySelectorAll('div#event-rating-'+eid+'[submitter="'+username+'"]'); + if(myrating.length) + myrating[0].parentNode.removeChild(myrating[0]); + toggleEdit(eid); + update_status(); + } + } + xhttp.send( JSON.stringify( { 'author': username } ) ); +} + +function do_remove_event(eid) { + var ev = document.getElementById('event-'+eid); + var xhttp = new XMLHttpRequest(); + xhttp.open("POST", "api/remove_event/"+eid, true); + xhttp.setRequestHeader("Content-type", "application/json"); + xhttp.onreadystatechange = function() { + if (xhttp.readyState == XMLHttpRequest.DONE && xhttp.status == 200) { + ev.parentNode.removeChild(ev); + } + } + xhttp.send(); +} + +function do_rate() { + var in_edit = document.getElementsByClassName('editing'); + if (!in_edit.length) + return; + var eid = in_edit[0].getAttribute('id').replace(/^event-/, ''); + + var username = document.getElementById('Username').value; + if (!username) { + alert( "Please set your name before rating."); + return; + } + + var xhttp = new XMLHttpRequest(); + xhttp.open("POST", "api/add_rating/"+eid, true); + xhttp.setRequestHeader("Content-type", "application/json"); + + ratings = {}; + document.getElementById('event-own-rating').querySelectorAll('.category-slider input').forEach(function(ev) { + ratings[ev.getAttribute("category")] = ev.value; + }); + var comment = document.getElementById('event-comment').value; + + xhttp.onreadystatechange = function() { + if (xhttp.readyState == XMLHttpRequest.DONE && xhttp.status == 200) { + var myrating = document.querySelectorAll('div#event-rating-'+eid+'[submitter="'+username+'"]'); + if (myrating.length) { + myrating = myrating[0]; + } else { + myrating = document.getElementById('event-rating-new').cloneNode(true); + myrating.setAttribute('id', 'event-rating-'+eid); + myrating.classList.remove('hidden'); + myrating.setAttribute('submitter', username); + myrating.getElementsByClassName('event-rating-submitter')[0].innerHTML = username + ':'; + document.querySelectorAll('#event-'+eid+' .event-ratings')[0].append(myrating); + } + myrating.getElementsByClassName('event-rating-comment')[0].innerHTML = comment; + for (category in ratings) { + myrating.querySelectorAll('meter[category='+category+']')[0].value = ratings[category]; + myrating.querySelectorAll('.event-rating-category-output[category='+category+']')[0].innerHTML = ' ' + categories[category] + ' ' + ratings[category] + ' %'; + } + + toggleEdit(eid); + update_status(); + } + } + + xhttp.send( JSON.stringify( { + 'author': username, + 'comment': comment, + 'ratings': ratings + } ) ); +} + +function do_set_state(eid, state) { + if ( state == document.getElementById('event-'+eid).getAttribute('event_state')) + state = ''; + + var xhttp = new XMLHttpRequest(); + xhttp.open("POST", "api/set_event_state/"+eid, true); + xhttp.setRequestHeader("Content-type", "application/json"); + xhttp.onreadystatechange = function() { + if (xhttp.readyState == XMLHttpRequest.DONE && xhttp.status == 200) { + document.getElementById('event-'+eid).setAttribute('event_state', state); + update_status(); + } + } + xhttp.send( JSON.stringify( { 'state': state } ) ); +} + +function do_take(eid) { + var username = document.getElementById('Username').value; + if (!username) { + alert( "Please set your name before taking an event."); + return; + } + + var ev = document.getElementById('event-'+eid); + if (ev.classList.contains('i-am-coordinator')) + username = ''; + + var xhttp = new XMLHttpRequest(); + xhttp.open("POST", "api/set_event_coordinator/"+eid, true); + xhttp.setRequestHeader("Content-type", "application/json"); + xhttp.onreadystatechange = function() { + if (xhttp.readyState == XMLHttpRequest.DONE && xhttp.status == 200) { + var coor = ev.getElementsByClassName('event-coordinator'); + if (coor.length) { + if (username) { + coor[0].innerHTML = 'coordinator: '+username; + coor[0].setAttribute('coordinator', username); + } else + coor[0].parentNode.removeChild(coor[0]); + } else { + if (username) { + var pers = ev.getElementsByClassName('event-persons'); + coor = document.createElement('div'); + coor.classList.toggle('event-coordinator', true); + coor.setAttribute('coordinator', username); + coor.innerHTML = 'coordinator: '+username; + pers[0].insertBefore(coor, pers[0].firstChild); + } + } + update_status(); + } + } + xhttp.send( JSON.stringify( { 'coordinator': username } ) ); +} + +function update_status() { + var accepted_count = document.querySelectorAll('.event-list-item[event_state=accepted]').length; + var rejected_count = document.querySelectorAll('.event-list-item[event_state=rejected]').length; + var taken_count = document.querySelectorAll('.event-list-item .event-coordinator').length; + var total_count = document.getElementsByClassName('event-list-item').length; + var total_voted_count = document.querySelectorAll('.event-rating:first-child').length; + var username = document.getElementById('Username').value; + var own_voted_count = 0; + if (username) + own_voted_count = document.querySelectorAll('.event-rating[submitter="'+username+'"]').length; + document.getElementById('status').innerHTML = total_count + ' events. ' + accepted_count + ' accepted. ' + rejected_count + ' rejected. ' + (total_count - own_voted_count) + ' todo. ' + (total_count - total_voted_count) + ' unvoted. ' + (total_count - taken_count) + ' untaken.'; + + /* Do the math */ + document.querySelectorAll('.event-list-item').forEach(function(ev) { + if (username) { + ev.classList.toggle('has-own-rating', ev.querySelectorAll('.event-rating[submitter="'+username+'"]').length > 0); + ev.classList.toggle('i-am-coordinator', ev.querySelectorAll('.event-coordinator[coordinator="'+username+'"]').length > 0); + } + + var counts = {}; + var meters = ev.querySelectorAll('.event-rating meter'); + + if (!meters.length) { + ev.querySelectorAll('.top-meter').forEach(function(meter) { + meter.setAttribute('value', 0); + }); + return; + } + + meters.forEach(function(rat) { + var tmp = counts[rat.getAttribute('category')] || 0; + counts[rat.getAttribute('category')] = tmp + parseInt(rat.getAttribute('value')); + }); + var total = 0, i = 0, divisor = meters.length / Object.keys(counts).length; + for (category in counts) { + var dest_meter = ev.getElementsByClassName('meter-'+i)[0]; + dest_meter.setAttribute('value', counts[category] / divisor); + dest_meter.setAttribute('title', category + ': ' + counts[category] / divisor + ' %' ); + total += counts[category] / divisor; + i++; + } + ev.getElementsByClassName('meter-4')[0].setAttribute('value', total / Object.keys(counts).length); + }); +} + +document.addEventListener('DOMContentLoaded', function () { + var username = localStorage.getItem("c3-rating-user"); + if (username) + document.getElementById('Username').value = username; + if (window.safari !== undefined) + document.body.classList.add('safari'); + update_status(); +}); diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..13a8aec --- /dev/null +++ b/templates/index.html @@ -0,0 +1,122 @@ + +{{ config.get('frab-conference') }} {{ config.get('track') }} rating helper + + + + + + + + +
+ + + + + + +
+
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + + + +
+
+
comment
+ {%- for category in cat -%} +
+
{{category}}:
+
+ 0 % +
+ {%- endfor -%} + + +
+ +

Please wait …

+ +
    +{% for ev in events -%} +
  • +
    + {%- for m in range(1+cat|length) -%} + + {%- endfor -%} +
    + {%- if not ev.state == 'gone' -%} + + {%- else -%} + + {%- endif -%} + + {%- if ev.subtitle -%} +
    {{ ev.subtitle }}
    + {%- endif -%} +
    + {%- if ev.coordinator -%} +
    coordinator: {{ev.coordinator}}
    + {%- endif -%} +
    speakers: + {%- for speaker_id, speaker_name in json.loads(ev.speakers or '{}').items() -%} + {{speaker_name}}  + {%- endfor -%} +
    +
    +
    abstract: {{ ev.abstract }}
    + {%- if ev.description -%} +
    description: {{ ev.description }}
    + {%- endif -%} + {%- if ev.notes -%} +
    notes: {{ ev.notes }}
    + {%- endif -%} +
    + {%- for rating in ev.ratings -%} +
    +
    {{rating.submitter}}:
    + {%- for category, value in json.loads(rating.rating_dict or '{}').items()|sort -%} +
    {{ cat[loop.index-1] + " " + value|string + " %" }}
    + {%- endfor -%} +
    {{rating.comment}}
    +
    + {%- endfor -%} +
    +
  • +{%- else %} + No events imported yet +{%- endfor %} +
      + + + -- cgit v1.2.3