contrib/nitrpg: display achievements in front end
authorAlexandre Terrasa <alexandre@moz-code.org>
Mon, 23 Feb 2015 09:57:07 +0000 (10:57 +0100)
committerAlexandre Terrasa <alexandre@moz-code.org>
Thu, 26 Feb 2015 12:13:24 +0000 (13:13 +0100)
Signed-off-by: Alexandre Terrasa <alexandre@moz-code.org>

contrib/nitrpg/src/templates/panels.nit
contrib/nitrpg/src/templates/templates_base.nit
contrib/nitrpg/src/templates/templates_events.nit
contrib/nitrpg/src/web.nit

index 89d6431..14538c5 100644 (file)
@@ -112,7 +112,9 @@ class GameStatusPanel
 
        redef fun render_body do
                add "<strong class=\"text-success\">{game.load_players.length}</strong>"
-               add " <a href=\"{game.url}/players\">players</a><br><br>"
+               add " <a href=\"{game.url}/players\">players</a><br>"
+               add "<strong class=\"text-success\">{game.stats["achievements"]}</strong>"
+               add " <a href=\"{game.url}/achievements\">achievements</a><br><br>"
                add "<strong class=\"text-success\">{game.stats["pulls"]}</strong> pull requests"
                add " (<strong>{game.stats["pulls_open"]}</strong> open)<br>"
                add "<strong class=\"text-success\">{game.stats["issues"]}</strong> issues"
@@ -144,6 +146,7 @@ class PlayerStatusPanel
                add "<p class=\"lead\">ranked "
                add " <span class=\"text-success\"># {ranking[player.name]}</span></p>"
                add "<strong class=\"text-success\">{player.nitcoins}</strong> nitcoins<br><br>"
+               add "<strong class=\"text-success\">{player.stats["achievements"]}</strong> achievements<br><br>"
                add "<strong>{player.stats["pulls"]}</strong> pull requests<br>"
                add "<strong>{player.stats["issues"]}</strong> issues<br>"
                add "<strong>{player.stats["commits"]}</strong> commits"
@@ -360,3 +363,75 @@ class EventListPanel
                add "</div>"
        end
 end
+
+# Achievement unlocked list panel.
+class AchievementsListPanel
+       super Panel
+
+       # Entity to load the events from.
+       var entity: GameEntity
+
+       redef fun render_title do
+               add "<span class=\"glyphicon glyphicon-list\"></span>&nbsp;&nbsp;"
+               add "Achievements unlocked"
+       end
+
+       redef fun render_body do
+               var achs = entity.load_achievements.values.to_a
+               if achs.is_empty then
+                       add "<em>No achievement yet...</em>"
+                       return
+               end
+               for ach in achs do add ach.list_item
+       end
+end
+
+# Achievement detail panel.
+class AchievementPanel
+       super Panel
+
+       # Achievement to display.
+       var achievement: Achievement
+
+       redef fun render_title do
+               add "<span class=\"glyphicon glyphicon-check\"></span>&nbsp;&nbsp;"
+               add "Achievement details"
+       end
+
+       redef fun render_body do
+               add """<p class=\"lead\">
+                               <span class="badge progress-bar-success"
+                                style="vertical-align: middle">+{{{achievement.reward}}}</span>
+                       {{{achievement.name}}}
+                      </p>
+                      <p><strong>{{{achievement.desc}}}</strong></p>"""
+
+               var events = achievement.load_events
+
+               if events.is_empty then
+                       add "<em>Never unlocked...</em>"
+                       return
+               end
+
+               var event = events.last
+               var tpl = event.tpl_event
+               var player = tpl.player
+               add "<hr>"
+               add """<div class="media">
+                       <a class="media-left" href="{{{player.url}}}">
+                                <span class="badge progress-bar-warning" style="position: absolute">#1</span>
+                        <img class=\"img-circle\" style="width:50px"
+                         src="{{{player.user.avatar_url}}}" alt="{{{player.name}}}">
+                       </a>
+                               <div class="media-body">
+                                <h4 class="media-heading">Unlocked first by {{{player.link}}}</h4>
+                                <span class="text-muted">at {{{event.time}}} </span>
+                               </div>
+                          </div>"""
+
+               if events.length > 1 then
+                       add """<p><br>Also unlocked by <strong class="text-success">
+                       {{{events.length}}} players</strong>.</p>"""
+               end
+       end
+end
index 9b28d57..8b85a4d 100644 (file)
@@ -17,7 +17,7 @@
 # Base HTML rendering templates for `nitpg`.
 module templates_base
 
-import statistics
+import achievements
 
 redef class GameEntity
 
@@ -50,3 +50,24 @@ redef class Issue
        # Return a HTML link to this Issue.
        fun link: String do return "<a href=\"{html_url}\">#{number}</a>"
 end
+
+redef class Achievement
+       # Return a HTML link to this Issue.
+       fun link: String do return "<a href=\"{url}\">{name}</a>"
+
+       fun list_item: String do
+               return """<div class="media">
+                              <div class="media-left" style="width: 50px">
+                               <span class="glyphicon glyphicon-check"></span>
+                               <span class="badge progress-bar-success"
+                                style="position: absolute; margin-top: 10px;
+                                        margin-left: -5px;">+{{{reward}}}</span>
+                              </div>
+                              <div class="media-body">
+                               <h4 class="media-heading">{{{link}}}</h4>
+                               <span class="text-muted">{{{desc}}}</span>
+                              </div>
+                             </div>"""
+
+       end
+end
index 20f7b58..234c9e5 100644 (file)
@@ -17,7 +17,7 @@
 # Templates to display `GameEvent` kinds.
 module templates_events
 
-import events
+import achievements
 import templates_base
 
 redef class GameEvent
@@ -29,6 +29,8 @@ redef class GameEvent
                        return new TplPullMerged(self)
                else if kind == "pull_review" then
                        return new TplPullReview(self)
+               else if kind == "achievement_unlocked" then
+                       return new TplAchievementUnlocked(self)
                end
                abort
        end
@@ -63,6 +65,11 @@ class TplEvent
                return new IssueCommentEvent.from_json(event.game.api, obj)
        end
 
+       # Load `achievement` data key as an Achievement.
+       var achievement: Achievement is lazy do
+               return player.load_achievement(event.data["achievement"].to_s).as(not null)
+       end
+
        # Display a media item for a reward event.
        fun media_item: String do
                return """<div class="media">
@@ -109,3 +116,12 @@ class TplPullReview
                return "{player.link} reviewed {issue.link}"
        end
 end
+
+# Event: achievement_unlocked
+class TplAchievementUnlocked
+       super TplEvent
+
+       redef var title is lazy do
+               return "{player.link} unlocked {achievement.link}"
+       end
+end
index ac7f91d..b27c457 100644 (file)
@@ -114,6 +114,7 @@ class RepoHome
                page.side_panels.add new ShortListPlayersPanel(game)
                page.flow_panels.add new PodiumPanel(game)
                page.flow_panels.add new EventListPanel(game, list_limit, list_from)
+               page.flow_panels.add new AchievementsListPanel(game)
                rsp.body = page.write_to_string
                return rsp
        end
@@ -152,12 +153,50 @@ class PlayerHome
                page.side_panels.clear
                page.side_panels.add new PlayerStatusPanel(game, player)
                page.flow_panels.add new PlayerReviewsPanel(game, player)
+               page.flow_panels.add new AchievementsListPanel(player)
                page.flow_panels.add new EventListPanel(player, list_limit, list_from)
                rsp.body = page.write_to_string
                return rsp
        end
 end
 
+# Display the list of achievements unlocked for this game.
+class ListAchievements
+       super GameAction
+
+       redef fun answer(request, url) do
+               var rsp = prepare_response(request, url)
+               page.breadcrumbs.add_link(game.url / "achievements", "achievements")
+               page.flow_panels.add new AchievementsListPanel(game)
+               rsp.body = page.write_to_string
+               return rsp
+       end
+end
+
+# Player details page.
+class AchievementHome
+       super GameAction
+
+       redef fun answer(request, url) do
+               var rsp = prepare_response(request, url)
+               var name = request.param("achievement")
+               if name == null then
+                       var msg = "Bad request: should look like /:owner/:repo/achievements/:achievement."
+                       return bad_request(msg)
+               end
+               var achievement = game.load_achievement(name)
+               if achievement == null then
+                       return bad_request("Request Error: unknown achievement {name}.")
+               end
+               page.breadcrumbs.add_link(game.url / "achievements", "achievements")
+               page.breadcrumbs.add_link(achievement.url, achievement.name)
+               page.flow_panels.add new AchievementPanel(achievement)
+               page.flow_panels.add new EventListPanel(achievement, list_limit, list_from)
+               rsp.body = page.write_to_string
+               return rsp
+       end
+end
+
 if args.length != 3 then
        print "Error: missing argument"
        print ""
@@ -175,6 +214,8 @@ var vh = new VirtualHost(iface)
 vh.routes.add new Route("/styles/", new FileServer("www/styles"))
 vh.routes.add new Route("/games/:owner/:repo/players/:player", new PlayerHome(root))
 vh.routes.add new Route("/games/:owner/:repo/players", new ListPlayers(root))
+vh.routes.add new Route("/games/:owner/:repo/achievements/:achievement", new AchievementHome(root))
+vh.routes.add new Route("/games/:owner/:repo/achievements", new ListAchievements(root))
 vh.routes.add new Route("/games/:owner/:repo", new RepoHome(root))
 
 var fac = new HttpFactory.and_libevent