Commit e1ad9bc69057dd6428360b633f4627b4249b6ca6

Authored by Miguel Barão
1 parent 0b1675b0
Exists in master and in 1 other branch dev

- titulo da página configurado no ficheiro de configuração.

- mostra o estado dos tópicos numa coluna do lado esquerdo.
- adicionado type: information, para perguntas que são apenas informativas.
1 BUGS: 1 BUGS:
2 2
3 -- não entra à primeira 3 +- guardar state cada vez que topico termina
  4 +- mostrar quantas perguntas faltam
4 - logs mostram que está a gerar cada pergunta 2 vezes...?? 5 - logs mostram que está a gerar cada pergunta 2 vezes...??
5 - mostra tópicos do lado esquerdo, indicando quais estão feitos e quantas perguntas contêm. 6 - mostra tópicos do lado esquerdo, indicando quais estão feitos e quantas perguntas contêm.
6 - se students.db não existe, rebenta. 7 - se students.db não existe, rebenta.
@@ -9,13 +10,14 @@ BUGS: @@ -9,13 +10,14 @@ BUGS:
9 10
10 TODO: 11 TODO:
11 12
  13 +- usar codemirror
12 - mostrar comments quando falha a resposta 14 - mostrar comments quando falha a resposta
13 -- configuração e linha de comando.  
14 -- como gerar uma sequencia de perguntas?  
15 - generators not working: bcrypt (ver blog) 15 - generators not working: bcrypt (ver blog)
16 16
17 SOLVED: 17 SOLVED:
18 18
  19 +- não entra à primeira
  20 +- configuração e linha de comando.
19 - o browser é redireccionado para /question em vez de fazer um post?? quando se pressiona enter numa caixa text edit. 21 - o browser é redireccionado para /question em vez de fazer um post?? quando se pressiona enter numa caixa text edit.
20 - load/save the knowledge state of the student 22 - load/save the knowledge state of the student
21 - servir ficheiros de public temporariamente 23 - servir ficheiros de public temporariamente
@@ -102,6 +102,14 @@ class LearnApp(object): @@ -102,6 +102,14 @@ class LearnApp(object):
102 return self.online[uid].get('name', '') 102 return self.online[uid].get('name', '')
103 103
104 # ------------------------------------------------------------------------ 104 # ------------------------------------------------------------------------
  105 + def get_student_state(self, uid):
  106 + return self.online[uid]['state'].get_knowledge_state()
  107 +
  108 + # ------------------------------------------------------------------------
  109 + def get_title(self):
  110 + return self.depgraph.graph['title']
  111 +
  112 + # ------------------------------------------------------------------------
105 def get_current_public_dir(self, uid): 113 def get_current_public_dir(self, uid):
106 topic = self.online[uid]['state'].get_current_topic() 114 topic = self.online[uid]['state'].get_current_topic()
107 p = self.depgraph.graph['path'] 115 p = self.depgraph.graph['path']
@@ -110,7 +118,7 @@ class LearnApp(object): @@ -110,7 +118,7 @@ class LearnApp(object):
110 # ------------------------------------------------------------------------ 118 # ------------------------------------------------------------------------
111 # check answer and if correct returns new question, otherise returns None 119 # check answer and if correct returns new question, otherise returns None
112 def check_answer(self, uid, answer): 120 def check_answer(self, uid, answer):
113 - logger.debug(f'check_answer("{uid}", "{answer}")') 121 + # logger.debug(f'check_answer("{uid}", "{answer}")')
114 knowledge = self.online[uid]['state'] 122 knowledge = self.online[uid]['state']
115 current_question = knowledge.check_answer(answer) 123 current_question = knowledge.check_answer(answer)
116 124
@@ -159,7 +167,8 @@ class LearnApp(object): @@ -159,7 +167,8 @@ class LearnApp(object):
159 raise e 167 raise e
160 168
161 prefix = config['path'] # FIXME default if does not exist? 169 prefix = config['path'] # FIXME default if does not exist?
162 - g = nx.DiGraph(path=prefix) 170 + title = config.get('title', '')
  171 + g = nx.DiGraph(path=prefix, title=title)
163 172
164 # Build dependency graph 173 # Build dependency graph
165 deps = config.get('dependencies', {}) 174 deps = config.get('dependencies', {})
@@ -28,8 +28,8 @@ class Knowledge(object): @@ -28,8 +28,8 @@ class Knowledge(object):
28 28
29 # ------------------------------------------------------------------------ 29 # ------------------------------------------------------------------------
30 def topic_generator(self): 30 def topic_generator(self):
31 - topics = nx.topological_sort(self.depgraph) # FIXME for now...  
32 - for t in topics: 31 + self.topic_sequence = nx.topological_sort(self.depgraph) # FIXME for now...
  32 + for t in self.topic_sequence:
33 if self.state.get(t, 0.0) > 0.999: 33 if self.state.get(t, 0.0) > 0.999:
34 continue 34 continue
35 self.questions = self.generate_questions_for_topic(t) 35 self.questions = self.generate_questions_for_topic(t)
@@ -51,7 +51,7 @@ class Knowledge(object): @@ -51,7 +51,7 @@ class Knowledge(object):
51 51
52 # ------------------------------------------------------------------------ 52 # ------------------------------------------------------------------------
53 def get_knowledge_state(self): 53 def get_knowledge_state(self):
54 - return self.state 54 + return [(t, self.state.get(t, 0.0)) for t in self.topic_sequence]
55 55
56 # --- generates a new question given the current state ------------------- 56 # --- generates a new question given the current state -------------------
57 def new_question(self): 57 def new_question(self):
@@ -76,7 +76,7 @@ class Knowledge(object): @@ -76,7 +76,7 @@ class Knowledge(object):
76 # --- checks answer ------------------------------------------------------ 76 # --- checks answer ------------------------------------------------------
77 # returns current question with correction, time and comments updated 77 # returns current question with correction, time and comments updated
78 def check_answer(self, answer): 78 def check_answer(self, answer):
79 - logger.debug(f'check_answer("{answer}")') 79 + logger.debug(f'check_answer {answer}')
80 question = self.current_question 80 question = self.current_question
81 if question is not None: 81 if question is not None:
82 question['finish_time'] = datetime.now() 82 question['finish_time'] = datetime.now()
@@ -110,7 +110,8 @@ class LearnHandler(BaseHandler): @@ -110,7 +110,8 @@ class LearnHandler(BaseHandler):
110 uid = self.current_user 110 uid = self.current_user
111 self.render('learn.html', 111 self.render('learn.html',
112 uid=uid, 112 uid=uid,
113 - name=self.learn.get_student_name(uid) 113 + name=self.learn.get_student_name(uid),
  114 + title=self.learn.get_title(),
114 ) 115 )
115 116
116 # ---------------------------------------------------------------------------- 117 # ----------------------------------------------------------------------------
@@ -129,6 +130,7 @@ class FileHandler(BaseHandler): @@ -129,6 +130,7 @@ class FileHandler(BaseHandler):
129 # respond to AJAX to get a JSON question 130 # respond to AJAX to get a JSON question
130 class QuestionHandler(BaseHandler): 131 class QuestionHandler(BaseHandler):
131 templates = { 132 templates = {
  133 + 'information': 'question-information.html',
132 'checkbox': 'question-checkbox.html', 134 'checkbox': 'question-checkbox.html',
133 'radio': 'question-radio.html', 135 'radio': 'question-radio.html',
134 'text': 'question-text.html', 136 'text': 'question-text.html',
@@ -146,16 +148,21 @@ class QuestionHandler(BaseHandler): @@ -146,16 +148,21 @@ class QuestionHandler(BaseHandler):
146 # ref = self.get_body_arguments('question_ref') 148 # ref = self.get_body_arguments('question_ref')
147 user = self.current_user 149 user = self.current_user
148 answer = self.get_body_arguments('answer') 150 answer = self.get_body_arguments('answer')
149 - # logger.debug(f'Answer POST from "{user}"')  
150 next_question = self.learn.check_answer(user, answer) 151 next_question = self.learn.check_answer(user, answer)
  152 + state = self.learn.get_student_state(user)
151 153
152 if next_question is not None: 154 if next_question is not None:
153 - html_out = self.render_string(self.templates[next_question['type']], 155 + question_html = self.render_string(self.templates[next_question['type']],
154 question=next_question, # dictionary with the question 156 question=next_question, # dictionary with the question
155 md=md, # function that renders markdown to html 157 md=md, # function that renders markdown to html
156 ) 158 )
  159 + topics_html = self.render_string('topics.html', state=state)
  160 +
157 self.write({ 161 self.write({
158 - 'params': tornado.escape.to_unicode(html_out), 162 + 'params': {
  163 + 'question': tornado.escape.to_unicode(question_html),
  164 + 'state': tornado.escape.to_unicode(topics_html),
  165 + },
159 'method': 'new_question', 166 'method': 'new_question',
160 }) 167 })
161 else: 168 else:
static/css/learn.css
@@ -6,4 +6,27 @@ img { @@ -6,4 +6,27 @@ img {
6 display: block; 6 display: block;
7 margin: auto; 7 margin: auto;
8 width: 80%; 8 width: 80%;
9 -}  
10 \ No newline at end of file 9 \ No newline at end of file
  10 +}
  11 +
  12 +/* progress bars have height of 4 pixels */
  13 +.progress {height: 4px;}
  14 +
  15 +/* make markdown tables beautiful */
  16 +table {
  17 + border-collapse: collapse;
  18 + margin-left: 50px;
  19 +}
  20 +thead, tbody, td, th {
  21 + padding: 5px;
  22 + border-bottom: 1px solid #ddd;
  23 +}
  24 +
  25 +textarea {
  26 + font-family: monospace !important;
  27 +}
  28 +
  29 +/* Hack to avoid name clash between pygments and mathjax */
  30 +.MathJax .mo,
  31 +.MathJax .mi {
  32 + color: inherit;
  33 +}
templates/learn.html
@@ -2,7 +2,11 @@ @@ -2,7 +2,11 @@
2 <html> 2 <html>
3 <head> 3 <head>
4 <meta charset="utf-8"> 4 <meta charset="utf-8">
5 - <title>Learn</title> 5 + <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6 + <meta name="viewport" content="width=device-width, initial-scale=1">
  7 + <meta name="author" content="Miguel Barão">
  8 +
  9 + <title>iLearn</title>
6 <link rel="icon" href="/static/favicon.ico"> 10 <link rel="icon" href="/static/favicon.ico">
7 11
8 <!-- MathJax --> 12 <!-- MathJax -->
@@ -31,7 +35,7 @@ @@ -31,7 +35,7 @@
31 <body> 35 <body>
32 <!-- ===================================================================== --> 36 <!-- ===================================================================== -->
33 <!-- Navbar --> 37 <!-- Navbar -->
34 -<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation"> 38 +<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
35 <div class="container-fluid"> 39 <div class="container-fluid">
36 40
37 <div class="navbar-header"> 41 <div class="navbar-header">
@@ -41,7 +45,7 @@ @@ -41,7 +45,7 @@
41 <span class="icon-bar"></span> 45 <span class="icon-bar"></span>
42 <span class="icon-bar"></span> 46 <span class="icon-bar"></span>
43 </button> 47 </button>
44 - <a class="navbar-brand" href="#">BrainPower</a> 48 + <a class="navbar-brand" href="#">{{ title }}</a>
45 </div> 49 </div>
46 50
47 <div class="collapse navbar-collapse" id="myNavbar"> 51 <div class="collapse navbar-collapse" id="myNavbar">
@@ -65,7 +69,17 @@ @@ -65,7 +69,17 @@
65 69
66 <!-- ===================================================================== --> 70 <!-- ===================================================================== -->
67 <!-- Container --> 71 <!-- Container -->
68 -<div class="container"> 72 +<div class="container-fluid">
  73 +<div class="row">
  74 +<div class="col-lg-2">
  75 + <div id="topics"></div>
  76 +</div>
  77 +
  78 +
  79 +
  80 + <!-- Blog Post Content Column -->
  81 + <div class="col-md-10">
  82 +
69 <!-- <audio> 83 <!-- <audio>
70 <source id="snd-intro" src="/static/sounds/intro.mp3" type="audio/mpeg"> 84 <source id="snd-intro" src="/static/sounds/intro.mp3" type="audio/mpeg">
71 <source id="snd-correct" src="/static/sounds/correct.mp3" type="audio/mpeg"> 85 <source id="snd-correct" src="/static/sounds/correct.mp3" type="audio/mpeg">
@@ -76,15 +90,15 @@ @@ -76,15 +90,15 @@
76 {% module xsrf_form_html() %} 90 {% module xsrf_form_html() %}
77 91
78 <div id="question_div"> 92 <div id="question_div">
79 - O jogo está prestes a começar. A bola está do teu lado. 93 + Pronto?
80 </div> 94 </div>
81 95
82 </form> 96 </form>
83 -<button class="btn btn-primary" id="submit">Próxima</button> 97 +<button class="btn btn-primary" id="submit">Continuar</button>
84 98
  99 +</div> <!-- col -->
85 </div> <!-- container --> 100 </div> <!-- container -->
86 101
87 -  
88 <!-- ===================================================================== --> 102 <!-- ===================================================================== -->
89 <!-- JAVASCRIP --> 103 <!-- JAVASCRIP -->
90 <!-- ===================================================================== --> 104 <!-- ===================================================================== -->
@@ -102,7 +116,8 @@ $.fn.extend({ @@ -102,7 +116,8 @@ $.fn.extend({
102 function updateQuestion(response){ 116 function updateQuestion(response){
103 switch (response["method"]) { 117 switch (response["method"]) {
104 case "new_question": 118 case "new_question":
105 - $("#question_div").html(response["params"]); 119 + $("#question_div").html(response["params"]["question"]);
  120 + $("#topics").html(response["params"]["state"]);
106 MathJax.Hub.Queue(["Typeset",MathJax.Hub,"question_div"]); 121 MathJax.Hub.Queue(["Typeset",MathJax.Hub,"question_div"]);
107 122
108 $("textarea, input:text, input:radio, input:checkbox").keydown(function (e) { 123 $("textarea, input:text, input:radio, input:checkbox").keydown(function (e) {
@@ -142,6 +157,7 @@ function getQuestion() { @@ -142,6 +157,7 @@ function getQuestion() {
142 $(document).ready(function() { 157 $(document).ready(function() {
143 // var audio = new Audio('/static/sounds/intro.mp3'); 158 // var audio = new Audio('/static/sounds/intro.mp3');
144 // audio.play(); 159 // audio.play();
  160 + getQuestion();
145 $("#submit").click(getQuestion); 161 $("#submit").click(getQuestion);
146 }); 162 });
147 </script> 163 </script>
templates/question-information.html 0 → 100644
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
  1 +{% extends "question.html" %}
  2 +
  3 +{% block answer %}
  4 +<input type="hidden" name="question_ref" value="{{ question['ref'] }}">
  5 +{% end %}
templates/question.html
@@ -3,8 +3,7 @@ @@ -3,8 +3,7 @@
3 <!-- <div class="panel panel-default"> 3 <!-- <div class="panel panel-default">
4 <div class="panel-body"> 4 <div class="panel-body">
5 --> 5 -->
6 - <h3>{{ question['title'] }}</h3>  
7 - 6 + <h1 class="page-header">{{ question['title'] }}</h1>
8 <div id="text"> 7 <div id="text">
9 {{ md(question['text']) }} 8 {{ md(question['text']) }}
10 </div> 9 </div>
templates/topics.html 0 → 100644
@@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
  1 +{% autoescape %}
  2 +
  3 +<!-- <div class="well"> -->
  4 +<div class="panel panel-default">
  5 + <div class="panel-heading">Tópicos</div>
  6 + <div class="panel-body">
  7 + <!-- <ul class="list-group"> -->
  8 + {% for t in state %}
  9 + <!-- <li class="list-group-item"> -->
  10 + <p>{{ t[0] }}</p>
  11 +
  12 + <div class="progress">
  13 + <div class="progress-bar {{ 'progress-bar-success' if t[1]>=0.9 else 'progress-bar-danger' }}" role="progressbar" aria-valuenow="{{ 100*t[1] }}" aria-valuemin="0" aria-valuemax="100" style="min-width: 1em;width: {{ 100*t[1] }}%">
  14 +
  15 + </div>
  16 + </div>
  17 + <!-- </li> -->
  18 + {% end %}
  19 + <!-- </ul> -->
  20 + </div>
  21 +</div>