| <!DOCTYPE html> | 
 | <!-- | 
 |  Copyright 2022 The Go Authors. All rights reserved. | 
 |  Use of this source code is governed by a BSD-style | 
 |  license that can be found in the LICENSE file. | 
 | --> | 
 |  | 
 | <meta charset="utf-8"> | 
 | <title>GopherCon 2017 Contribution Dashboard</title> | 
 | <meta name=viewport content="width=device-width,minimum-scale=1,maximum-scale=1"> | 
 | <style> | 
 | * { | 
 |   box-sizing: border-box; | 
 |   margin: 0; | 
 |   padding: 0; | 
 | } | 
 | a:link, | 
 | a:visited { | 
 |   color: #fff; | 
 | } | 
 | .Site { | 
 |   background-color: #2a2a2a; | 
 |   color: #fff; | 
 |   display: flex; | 
 |   flex-direction: column; | 
 |   font: 14px 'Helvetica-Neue', Helvetica, sans-serif; | 
 |   height: 100vh; | 
 |   text-rendering: optimizeLegibility; | 
 | } | 
 | .Site-body { | 
 |   display: flex; | 
 |   flex: 1; | 
 | } | 
 | .Site-content { | 
 |   border-right: 1px solid #3a3939; | 
 |   overflow: hidden; | 
 |   padding: 1.5em; | 
 |   width: 60%; | 
 | } | 
 | .Site-activity { | 
 |   background-color: #323232; | 
 |   flex: 1; | 
 |   overflow: scroll; | 
 | } | 
 | .Site-logo { | 
 |   height: 60px; | 
 |   margin-right: 2em; | 
 | } | 
 | .Site-pointsTitle, | 
 | .Site-points { | 
 |   text-align: center; | 
 | } | 
 | .Site-pointsTitle { | 
 |   font-size: 4vh; | 
 |   letter-spacing: 1px; | 
 |   text-transform: uppercase; | 
 | } | 
 | .Site-points { | 
 |   font-size: 30vh; | 
 | } | 
 | .u-avatar { | 
 |   border-radius: 50%; | 
 | } | 
 | .AvatarGroup { | 
 |   display: flex; | 
 |   flex-wrap: wrap; | 
 |   justify-content: center; | 
 | } | 
 | .AvatarGroup-avatar { | 
 |   height: 6vh; | 
 |   margin: 0 .5em .5em 0; | 
 |   width: 6vh; | 
 | } | 
 | .Activity { | 
 |   align-items: flex-start; | 
 |   background-color: #323232; | 
 |   border-bottom: 1px solid #2d2d2d; | 
 |   border-top: 1px solid #404040; | 
 |   display: flex; | 
 |   font-size: 2vw; | 
 |   padding: 1em; | 
 | } | 
 | .Activity-avatar { | 
 |   height: 2.5em; | 
 |   margin-right: .75em; | 
 |   width: 2.5em; | 
 | } | 
 | .Activity-main { | 
 |   flex: 1; | 
 |   margin-right: .15em; | 
 | } | 
 | .Activity-points { | 
 |   color: #85ebf9; | 
 |   font-size: .85em; | 
 |   margin-top: .25em; | 
 | } | 
 | .Activity-icon { | 
 |   font-size: 2em; | 
 | } | 
 | .Site-footer { | 
 |   align-items: center; | 
 |   background: #3a3737; | 
 |   display: flex; | 
 |   justify-content: space-between; | 
 |   padding: 1em 1.5em; | 
 | } | 
 | </style> | 
 | <body class="Site"> | 
 |   <div class="Site-body"> | 
 |     <main class="Site-content"> | 
 |       <div class="Site-pointsTitle">Total points</div> | 
 |       <div class="Site-points js-totalPoints">0</div> | 
 |       <div class="AvatarGroup js-avatarGroup"></div> | 
 |     </main> | 
 |     <aside class="Site-activity js-activityList"></aside> | 
 |   </div> | 
 |   <footer class="Site-footer"> | 
 |     <img class="Site-logo" src="https://github.com/ashleymcnamara/gophers/raw/master/GoCommunity.png" alt="Go Community"> | 
 |     <div> | 
 |       Gopher artwork by <a href="https://github.com/ashleymcnamara/gophers" target="_blank">Ashley McNamara</a> | 
 |       based on the original artwork by the amazing | 
 |       <a href="https://reneefrench.blogspot.com/" target="_blank">Renee French</a>. | 
 |       Licensed under a <a href="http://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.</a> | 
 |     </div> | 
 |   </footer> | 
 | </body> | 
 |  | 
 | <template class="activityTemplate"> | 
 |   <div class="Activity"> | 
 |     <img class="u-avatar Activity-avatar js-avatar"> | 
 |     <div class="Activity-main"> | 
 |       <div class="Activity-body js-body"></div> | 
 |       <div class="Activity-points js-points"></div> | 
 |     </div> | 
 |     <div class="Activity-icon js-icon"></div> | 
 |   </div> | 
 | </template> | 
 |  | 
 | <script> | 
 | (function() { | 
 |   'use strict'; | 
 |  | 
 |   const ActivityType = { | 
 |     AMEND_CHANGE: 'AMEND_CHANGE', | 
 |     CREATE_CHANGE: 'CREATE_CHANGE', | 
 |     MERGE_CHANGE: 'MERGE_CHANGE', | 
 |     REGISTER: 'REGISTER', | 
 |   }; | 
 |  | 
 |   const iconMap = {}; | 
 |   iconMap.AMEND_CHANGE = '👍'; | 
 |   iconMap.CREATE_CHANGE = '👏'; | 
 |   iconMap.MERGE_CHANGE = '🤘'; | 
 |   iconMap.REGISTER = '🎉'; | 
 |  | 
 |   let lastUpdateMs = 0; | 
 |   getActivities(lastUpdateMs); | 
 |   startUpdateLoop(); | 
 |  | 
 |   function getActivities(since) { | 
 |     const url = `/_/activities?since=${since}`; | 
 |     fetch(url).then(resp => { | 
 |       resp.json().then(obj => { | 
 |         processAll(obj.activities); | 
 |         updatePoints(obj.totalPoints); | 
 |       }); | 
 |     }).catch(err => { | 
 |       console.error(err); | 
 |     }); | 
 |   } | 
 |  | 
 |   function startUpdateLoop() { | 
 |     window.setInterval(() => { | 
 |       getActivities(lastUpdateMs); | 
 |     }, 5000); | 
 |   } | 
 |  | 
 |   // Username => img element. | 
 |   let avatarMap = {}; | 
 |  | 
 |   function processAll(activities) { | 
 |     const activityListEl = document.querySelector('.js-activityList'); | 
 |     const avatarGroupEl = document.querySelector('.js-avatarGroup'); | 
 |     activities.forEach(activity => { | 
 |       if (!avatarMap[activity.gitHubUser]) { | 
 |         const el = createAvatarEl(activity.gitHubUser); | 
 |         el.classList.add('AvatarGroup-avatar'); | 
 |         avatarMap[activity.gitHubUser] = el; | 
 |       } | 
 |       avatarGroupEl.insertBefore(avatarMap[activity.gitHubUser], | 
 |           avatarGroupEl.firstChild); | 
 |       const activityEl = createActivityEl(activity); | 
 |       activityListEl.insertBefore(activityEl, activityListEl.firstChild); | 
 |     }); | 
 |     if (activities.length > 0) { | 
 |       lastUpdateMs = new Date(activities[activities.length - 1].created).getTime(); | 
 |     } | 
 |   } | 
 |  | 
 |   function updatePoints(points) { | 
 |     document.querySelector('.js-totalPoints').textContent = points; | 
 |   } | 
 |  | 
 |   function createAvatarEl(username) { | 
 |     let img = document.createElement('IMG'); | 
 |     img.classList.add('u-avatar'); | 
 |     img.src = avatarSrc(username); | 
 |     img.alt = avatarAlt(username); | 
 |     return img; | 
 |   } | 
 |  | 
 |   function avatarSrc(username) { | 
 |     return `https://github.com/${encodeURIComponent(username)}.png?size=100`; | 
 |   } | 
 |  | 
 |   function avatarAlt(username) { | 
 |     return `Avatar for ${username}`; | 
 |   } | 
 |  | 
 |   function activityBody(type, username) { | 
 |     switch (type) { | 
 |       case ActivityType.AMEND_CHANGE: | 
 |       return `${username} just amended a change!`; | 
 |  | 
 |       case ActivityType.CREATE_CHANGE: | 
 |       return `${username} just created a change!`; | 
 |  | 
 |       case ActivityType.MERGE_CHANGE: | 
 |       return `${username} just merged a change!`; | 
 |  | 
 |       case ActivityType.REGISTER: | 
 |       return `${username} just registered!`; | 
 |     } | 
 |   } | 
 |  | 
 |   function pointsStr(points) { | 
 |     return `+${points} Point` + (points > 1 ? 's' : ''); | 
 |   } | 
 |  | 
 |   function createActivityEl(activity) { | 
 |     const tmpl = document.querySelector('.activityTemplate'); | 
 |     const imgEl = tmpl.content.querySelector('.js-avatar'); | 
 |     imgEl.src = avatarSrc(activity.gitHubUser); | 
 |     imgEl.alt = avatarAlt(activity.gitHubUser); | 
 |     tmpl.content.querySelector('.js-icon').textContent = iconMap[activity.type]; | 
 |     tmpl.content.querySelector('.js-body').textContent = | 
 |         activityBody(activity.type, activity.gitHubUser); | 
 |     tmpl.content.querySelector('.js-points').textContent = | 
 |         pointsStr(activity.points); | 
 |     return document.importNode(tmpl.content, true); | 
 |   } | 
 | })(); | 
 | </script> |