contrib/nitrpg: add GamesListPanel to display the list of active games
[nit.git] / contrib / nitrpg / src / templates / panels.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2014-2015 Alexandre Terrasa <alexandre@moz-code.org>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # Panels templates for `nitpg`.
18 module panels
19
20 import templates_events
21 import markdown
22
23 # A panel can be displayed in a html page.
24 #
25 # This display a Bootstrap panel.
26 class Panel
27 super Template
28
29 redef fun rendering do
30 add """<div class="panel panel-default">
31 <div class="panel-heading">
32 <h3 class="panel-title">"""
33 render_title
34 add """ </h3>
35 </div>
36 <div class="panel-body">"""
37 render_body
38 add """</div>
39 </div>"""
40 end
41
42 # Render the panel title.
43 # Betweem `<h4>` tags.
44 fun render_title do end
45
46 # Render the panel body.
47 fun render_body do end
48 end
49
50 # A panel that contain only a table as body.
51 class TablePanel
52 super Panel
53
54 redef fun rendering do
55 add """<div class="panel panel-default">
56 <div class="panel-heading">
57 <h3 class="panel-title">"""
58 render_title
59 add """
60 </h3>
61 </div>"""
62 render_body
63 add """</div>"""
64 end
65 end
66
67 # Display an error message within a panel.
68 class ErrorPanel
69 super Panel
70
71 redef fun rendering do
72 add """
73 <div class="panel panel-danger">
74 <div class="panel-heading">
75 <h3 class="panel-title">"""
76 render_title
77 add """
78 </h3>
79 </div>
80 <div class="panel-body">"""
81 render_body
82 add """
83 </div>
84 </div>
85 """
86 end
87
88 # The error message to display as panel body.
89 var msg: String
90
91 redef fun render_title do
92 add "<span class=\"glyphicon glyphicon-warning-sign\"></span>&nbsp;&nbsp;"
93 add "Error"
94 end
95
96 redef fun render_body do
97 add msg.html_escape
98 end
99
100 end
101
102 # A panel that display a markdown content rendered as HTML.
103 class MDPanel
104 super Panel
105
106 # Markdown text to display.
107 var text: String
108
109 redef fun rendering do
110 add """<div class="panel">
111 <div class="panel-body">{{{text.md_to_html}}}</div>
112 </div>"""
113 end
114 end
115
116 # Display a list of active game.
117 #
118 # Used for NitRPG homepage.
119 class GamesShortListPanel
120 super Panel
121
122 # Root url used for links.
123 var root_url: String
124
125 # List of NitRPG games to display.
126 var games: Array[Game]
127
128 redef fun render_title do
129 add "<span class=\"glyphicon glyphicon-home\"></span>&nbsp;&nbsp;"
130 add "<a href=\"{root_url}/games\">Active games</a>"
131 end
132
133 redef fun render_body do
134 if games.is_empty then
135 add "<em>No game yet...</em>"
136 return
137 end
138 var sorted = games.to_a
139 (new GamePlayersComparator).sort(sorted)
140 for game in sorted do
141 add "{game.link} ({game.load_players.length} players)<br>"
142 end
143 end
144 end
145
146 # A panel that display a list of player in a repo.
147 class GamesListPanel
148 super GamesShortListPanel
149 super TablePanel
150
151 redef fun render_title do
152 add "<span class=\"glyphicon glyphicon-home\"></span>&nbsp;&nbsp;"
153 add "<a href=\"{root_url}/games\">Active games</a>"
154 end
155
156 redef fun render_body do
157 if games.is_empty then
158 add "<div class=\"panel-body\">"
159 add "<em>No player yet...</em>"
160 add "</div>"
161 return
162 end
163 var sorted = games.to_a
164 (new GamePlayersComparator).sort(sorted)
165 add """<table class="table table-striped table-hover">
166 <tr>
167 <th>Game</th>
168 <th>Players</th>
169 <th>Achievements</th>
170 </tr>"""
171 for game in sorted do
172 add "<tr>"
173 add " <td>{game.link}</td>"
174 add " <td>{game.load_players.length}</td>"
175 add " <td>{game.load_achievements.length}</td>"
176 add "</tr>"
177 end
178 add "</table>"
179 end
180 end
181
182 # A panel that display repo statistics.
183 class GameStatusPanel
184 super Panel
185
186 # Repo to display.
187 var game: Game
188
189 redef fun render_title do
190 add "<span class=\"glyphicon glyphicon-home\"></span>&nbsp;&nbsp;"
191 add "{game.link}"
192 end
193
194 redef fun render_body do
195 add "<strong class=\"text-success\">{game.load_players.length}</strong>"
196 add " <a href=\"{game.url}/players\">players</a><br>"
197 add "<strong class=\"text-success\">{game.stats["achievements"]}</strong>"
198 add " <a href=\"{game.url}/achievements\">achievements</a><br><br>"
199 add "<strong class=\"text-success\">{game.stats["pulls"]}</strong> pull requests"
200 add " (<strong>{game.stats["pulls_open"]}</strong> open)<br>"
201 add "<strong class=\"text-success\">{game.stats["issues"]}</strong> issues"
202 add " (<strong>{game.stats["issues_open"]}</strong> open)<br>"
203 add "<strong class=\"text-success\">{game.stats["commits"]}</strong> commits"
204 end
205 end
206
207 # Player status panel.
208 class PlayerStatusPanel
209 super Panel
210
211 # Game instance.
212 var game: Game
213
214 # Target player.
215 var player: Player
216
217 redef fun render_title do
218 add "<a href=\"{player.url}\">"
219 add " <img class=\"img-circle\" style=\"width: 30px\""
220 add " src=\"{player.user.avatar_url}\" alt=\"{player.name}\">"
221 add "</a>&nbsp;&nbsp;{player.link}"
222 end
223
224 redef fun render_body do
225 var ranking = game.player_ranking
226 # TODO player.rank
227 add "<p class=\"lead\">ranked "
228 add " <span class=\"text-success\"># {ranking[player.name]}</span></p>"
229 add "<strong class=\"text-success\">{player.nitcoins}</strong> nitcoins<br><br>"
230 add "<strong class=\"text-success\">{player.stats["achievements"]}</strong> achievements<br><br>"
231 add "<strong>{player.stats["pulls"]}</strong> pull requests<br>"
232 add "<strong>{player.stats["issues"]}</strong> issues<br>"
233 add "<strong>{player.stats["commits"]}</strong> commits"
234 end
235 end
236
237 # A panel that display a list of player in a repo.
238 class ShortListPlayersPanel
239 super Panel
240
241 # Game instance.
242 var game: Game
243
244 redef fun render_title do
245 add "<span class=\"glyphicon glyphicon-user\"></span>&nbsp;&nbsp;"
246 add "<a href=\"{game.url}/players\">Players</a>"
247 end
248
249 redef fun render_body do
250 var players = game.load_players.values.to_a
251 if players.is_empty then
252 add "<em>No player yet...</em>"
253 return
254 end
255 (new PlayerCoinComparator).sort(players)
256 for player in players do
257 add "{player.nitcoins} - {player.link}<br>"
258 end
259 end
260 end
261
262 # A panel that display a list of player in a repo.
263 class ListPlayersPanel
264 super TablePanel
265
266 # Game instance.
267 var game: Game
268
269 redef fun render_title do
270 add "<span class=\"glyphicon glyphicon-user\"></span>&nbsp;&nbsp;"
271 add "<a href=\"{game.url}/players\">Players</a>"
272 end
273
274 redef fun render_body do
275 var players = game.load_players.values.to_a
276 (new PlayerCoinComparator).sort(players)
277 if players.is_empty then
278 add "<div class=\"panel-body\">"
279 add "<em>No player yet...</em>"
280 add "</div>"
281 return
282 end
283 add """<table class="table table-striped table-hover">
284 <tr>
285 <th>#</th>
286 <th>Player</th>
287 <th>Nitcoins</th>
288 </tr>"""
289 var rank = 1
290 for player in players do
291 add "<tr>"
292 add " <td>{rank}</td>"
293 add " <td>{player.link}</td>"
294 add " <td>{player.nitcoins}</td>"
295 add "</tr>"
296 rank += 1
297 end
298 add "</table>"
299 end
300 end
301
302 # A panel that display the podium.
303 class PodiumPanel
304 super Panel
305
306 # Game instance.
307 var game: Game
308
309 redef fun render_title do
310 add "<span class=\"glyphicon glyphicon-stats\"></span>&nbsp;&nbsp;Hall of fame"
311 end
312
313 redef fun render_body do
314 var players = game.load_players.values.to_a
315 (new PlayerCoinComparator).sort(players)
316 if players.is_empty then
317 add "<em>No players yet...</em>"
318 return
319 end
320 add """
321 <div class="container-fluid">
322 <div id="podium" class="row row-sm-height">"""
323 var max = players.first.nitcoins
324 var orders = [3, 1, 0, 2, 4]
325 for order in orders do
326 if order >= players.length then continue
327 var player = players[order]
328 var size = 0
329 if max > 0 then size = player.nitcoins * 300 / max
330 add """
331 <div class="col-xs-2 col-xs-height col-xs-offset-{{{order}}} col-bottom"
332 style="text-align: center;">
333 <p>
334 <a href="{{{player.url}}}">
335 <img class="img-circle" style="width: 80px"
336 src="{{{player.user.avatar_url}}}" alt="{{{player.name}}}">
337 </a>
338 </p>
339 <p>{{{player.link}}}</p>
340 <p>{{{player.nitcoins}}}</p>
341 <div class=" progress-bar-warning progress-bar-striped"
342 style="height: {{{size}}}px;"></div>
343 </div>"""
344 end
345 add """
346 </div>
347 </div>"""
348 end
349 end
350
351 # A `Panel` that displays the list of PR to review for a `Player`.
352 class PlayerReviewsPanel
353 super Panel
354
355 # Repo to display.
356 var game: Game
357
358 # Player to display customized list for.
359 var player: Player
360
361 redef fun render_title do
362 add "<span class=\"glyphicon glyphicon-check\"></span>&nbsp;&nbsp;"
363 add "Review pull requests to gain nitcoins!"
364 end
365
366 redef fun render_body do
367 var q = "is:open label:need_review sort:updated-asc " +
368 "-involves:{player.name}"
369
370 var issues = game.repo.search_issues(q)
371 if issues.is_empty then
372 add "<em>No pull request to review yet...</em>"
373 return
374 end
375 for issue in issues do
376 var user = issue.user
377 var uplay = user.player(game)
378 add """<div class="media">
379 <a class="media-left" href="{{{uplay.url}}}">
380 <img class=\"img-circle\" style="width:50px"
381 src="{{{user.avatar_url}}}" alt="{{{uplay.name}}}">
382 </a>
383 <div class="media-body">
384 <h4 class="media-heading">
385 {{{issue.link}}} {{{issue.title}}}
386 </h4>
387 <span class="text-muted">opened by </span>
388 {{{uplay.link}}}
389 </div>
390 </div>"""
391 end
392 end
393 end
394
395 # A `Panel` that displays a pagined list of events stored in the `entity`.
396 #
397 # This way the panel can be used to view events stored under `Game`, `Player`...
398 class EventListPanel
399 super Panel
400
401 # Entity to load the events from.
402 var entity: GameEntity
403
404 # Number of events to display.
405 var limit: Int
406
407 # From where to start?
408 var from: Int
409
410 redef fun render_title do
411 add "<span class=\"glyphicon glyphicon-flash\"></span>&nbsp;&nbsp;"
412 add "Last events"
413 end
414
415 redef fun render_body do
416 var events = entity.load_events
417 if events.is_empty then
418 add "<em>No event yet...</em>"
419 return
420 end
421 # check input
422 if limit < 0 then limit = 10
423 if from < 0 then from = 0
424 # display events
425 for i in [from .. from + limit] do
426 if i >= events.length then break
427 add events[i].tpl_event.media_item
428 end
429 # pagination
430 if limit > events.length then return
431 add "<hr>"
432 add """<div class="btn-group" role="group">"""
433 if from > 0 then
434 add """<a class="btn btn-default" role="button"
435 href="?pfrom={{{from - limit}}}&plimit={{{limit}}}">
436 <span class=\"glyphicon glyphicon-chevron-left\"></span></a>"""
437 end
438 if from + limit < events.length then
439 add """
440 <a class="btn btn-default" role="button"
441 href="?pfrom={{{from + limit}}}&plimit={{{limit}}}">
442 <span class=\"glyphicon glyphicon-chevron-right\"></span></a>"""
443 end
444 add "</div>"
445 end
446 end
447
448 # Achievement unlocked list panel.
449 class AchievementsListPanel
450 super Panel
451
452 # Entity to load the events from.
453 var entity: GameEntity
454
455 redef fun render_title do
456 add "<span class=\"glyphicon glyphicon-list\"></span>&nbsp;&nbsp;"
457 add "Achievements unlocked"
458 end
459
460 redef fun render_body do
461 var achs = entity.load_achievements.values.to_a
462 if achs.is_empty then
463 add "<em>No achievement yet...</em>"
464 return
465 end
466 for ach in achs do add ach.list_item
467 end
468 end
469
470 # Achievement detail panel.
471 class AchievementPanel
472 super Panel
473
474 # Achievement to display.
475 var achievement: Achievement
476
477 redef fun render_title do
478 add "<span class=\"glyphicon glyphicon-check\"></span>&nbsp;&nbsp;"
479 add "Achievement details"
480 end
481
482 redef fun render_body do
483 add """<p class=\"lead\">
484 <span class="badge progress-bar-success"
485 style="vertical-align: middle">+{{{achievement.reward}}}</span>
486 {{{achievement.name}}}
487 </p>
488 <p><strong>{{{achievement.desc}}}</strong></p>"""
489
490 var events = achievement.load_events
491
492 if events.is_empty then
493 add "<em>Never unlocked...</em>"
494 return
495 end
496
497 var event = events.last
498 var tpl = event.tpl_event
499 var player = tpl.player
500 add "<hr>"
501 add """<div class="media">
502 <a class="media-left" href="{{{player.url}}}">
503 <span class="badge progress-bar-warning" style="position: absolute">#1</span>
504 <img class=\"img-circle\" style="width:50px"
505 src="{{{player.user.avatar_url}}}" alt="{{{player.name}}}">
506 </a>
507 <div class="media-body">
508 <h4 class="media-heading">Unlocked first by {{{player.link}}}</h4>
509 <span class="text-muted">at {{{event.time}}} </span>
510 </div>
511 </div>"""
512
513 if events.length > 1 then
514 add """<p><br>Also unlocked by <strong class="text-success">
515 {{{events.length}}} players</strong>.</p>"""
516 end
517 end
518 end