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