Merge: concurrent_collections: Add implementation of has method
[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 or else "#"}\" 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 or else "#"}}}" 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 and comment issues 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 q2 = "is:open label:request_for_comments sort:updated-asc " +
371 "-involves:{player.name}"
372
373 var issues = new ArraySet[Issue]
374 issues.add_all game.api.search_repo_issues(game.repo, q)
375 issues.add_all game.api.search_repo_issues(game.repo, q2)
376 if issues.is_empty then
377 add "<em>No pull request or issue to review yet...</em>"
378 return
379 end
380 for issue in issues do
381 var user = issue.user
382 var uplay = user.player(game)
383 add """<div class="media">
384 <a class="media-left" href="{{{uplay.url}}}">
385 <img class=\"img-circle\" style="width:50px"
386 src="{{{user.avatar_url or else "#"}}}" alt="{{{uplay.name}}}">
387 </a>
388 <div class="media-body">
389 <h4 class="media-heading">
390 {{{issue.link}}} {{{issue.title}}}
391 </h4>
392 <span class="text-muted">opened by </span>
393 {{{uplay.link}}}
394 </div>
395 </div>"""
396 end
397 end
398 end
399
400 # A `Panel` that displays the work assigned or tagged.
401 class PlayerWorkPanel
402 super Panel
403
404 # Repo to display.
405 var game: Game
406
407 # Player to display customized list for.
408 var player: Player
409
410 redef fun render_title do
411 add "<span class=\"glyphicon glyphicon-check\"></span>&nbsp;&nbsp;"
412 add "Do your tasks to gain nitcoins!"
413 end
414
415 redef fun render_body do
416 var q = "is:open label:need_work sort:updated-asc author:{player.name}"
417 var q2 = "is:open sort:updated-asc assignee:{player.name}"
418
419 var issues = new ArraySet[Issue]
420 issues.add_all game.api.search_repo_issues(game.repo, q)
421 issues.add_all game.api.search_repo_issues(game.repo, q2)
422 if issues.is_empty then
423 add "<em>No work to do yet...</em>"
424 return
425 end
426 for issue in issues do
427 var user = issue.user
428 var uplay = user.player(game)
429 add """<div class="media">
430 <a class="media-left" href="{{{uplay.url}}}">
431 <img class=\"img-circle\" style="width:50px"
432 src="{{{user.avatar_url or else "#"}}}" alt="{{{uplay.name}}}">
433 </a>
434 <div class="media-body">
435 <h4 class="media-heading">
436 {{{issue.link}}} {{{issue.title}}}
437 </h4>
438 <span class="text-muted">opened by </span>
439 {{{uplay.link}}}
440 </div>
441 </div>"""
442 end
443 end
444 end
445
446 # A `Panel` that displays a pagined list of events stored in the `entity`.
447 #
448 # This way the panel can be used to view events stored under `Game`, `Player`...
449 class EventListPanel
450 super Panel
451
452 # Entity to load the events from.
453 var entity: GameEntity
454
455 # Number of events to display.
456 var limit: Int
457
458 # From where to start?
459 var from: Int
460
461 redef fun render_title do
462 add "<span class=\"glyphicon glyphicon-flash\"></span>&nbsp;&nbsp;"
463 add "Last events"
464 end
465
466 redef fun render_body do
467 var events = entity.load_events
468 if events.is_empty then
469 add "<em>No event yet...</em>"
470 return
471 end
472 # check input
473 if limit < 0 then limit = 10
474 if from < 0 then from = 0
475 # display events
476 for i in [from .. from + limit] do
477 if i >= events.length then break
478 add events[i].tpl_event.media_item
479 end
480 # pagination
481 if limit > events.length then return
482 add "<hr>"
483 add """<div class="btn-group" role="group">"""
484 if from > 0 then
485 add """<a class="btn btn-default" role="button"
486 href="?pfrom={{{from - limit}}}&plimit={{{limit}}}">
487 <span class=\"glyphicon glyphicon-chevron-left\"></span></a>"""
488 end
489 if from + limit < events.length then
490 add """
491 <a class="btn btn-default" role="button"
492 href="?pfrom={{{from + limit}}}&plimit={{{limit}}}">
493 <span class=\"glyphicon glyphicon-chevron-right\"></span></a>"""
494 end
495 add "</div>"
496 end
497 end
498
499 # Achievement unlocked list panel.
500 class AchievementsListPanel
501 super Panel
502
503 # Entity to load the events from.
504 var entity: GameEntity
505
506 redef fun render_title do
507 add "<span class=\"glyphicon glyphicon-list\"></span>&nbsp;&nbsp;"
508 add "Achievements unlocked"
509 end
510
511 redef fun render_body do
512 var achs = entity.load_achievements.values.to_a
513 if achs.is_empty then
514 add "<em>No achievement yet...</em>"
515 return
516 end
517 for ach in achs do add ach.list_item
518 end
519 end
520
521 # Achievement detail panel.
522 class AchievementPanel
523 super Panel
524
525 # Achievement to display.
526 var achievement: Achievement
527
528 redef fun render_title do
529 add "<span class=\"glyphicon glyphicon-check\"></span>&nbsp;&nbsp;"
530 add "Achievement details"
531 end
532
533 redef fun render_body do
534 add """<p class=\"lead\">
535 <span class="badge progress-bar-success"
536 style="vertical-align: middle">+{{{achievement.reward}}}</span>
537 {{{achievement.name}}}
538 </p>
539 <p><strong>{{{achievement.desc}}}</strong></p>"""
540
541 var events = achievement.load_events
542
543 if events.is_empty then
544 add "<em>Never unlocked...</em>"
545 return
546 end
547
548 var event = events.last
549 var tpl = event.tpl_event
550 var player = tpl.player
551 add "<hr>"
552 add """<div class="media">
553 <a class="media-left" href="{{{player.url}}}">
554 <span class="badge progress-bar-warning" style="position: absolute">#1</span>
555 <img class=\"img-circle\" style="width:50px"
556 src="{{{player.user.avatar_url or else "#"}}}" alt="{{{player.name}}}">
557 </a>
558 <div class="media-body">
559 <h4 class="media-heading">Unlocked first by {{{player.link}}}</h4>
560 <span class="text-muted">at {{{event.time}}} </span>
561 </div>
562 </div>"""
563
564 if events.length > 1 then
565 add """<p><br>Also unlocked by <strong class="text-success">
566 {{{events.length}}} players</strong>.</p>"""
567 end
568 end
569 end