| <!DOCTYPE html> | |
| <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> |