Voting is working

This commit is contained in:
Brian Buller 2017-06-30 13:35:36 -06:00
parent 153b869a98
commit 96a53b6090
12 changed files with 515 additions and 80 deletions

View File

@ -15,21 +15,11 @@ func handleAdminGames(w http.ResponseWriter, req *http.Request, page *pageData)
teamId := vars["id"] teamId := vars["id"]
if teamId == "" { if teamId == "" {
// Games List // Games List
type gamePage struct {
Game *Game
Team *Team
}
type gamesPageData struct { type gamesPageData struct {
Games []gamePage Teams []Team
} }
gpd := new(gamesPageData) gpd := new(gamesPageData)
allGames := dbGetAllGames() gpd.Teams = dbGetAllTeams()
for _, gm := range allGames {
gamePage := new(gamePage)
gamePage.Game = &gm
gamePage.Team = dbGetTeam(gm.TeamId)
gpd.Games = append(gpd.Games, *gamePage)
}
page.TemplateData = gpd page.TemplateData = gpd
page.SubTitle = "Games" page.SubTitle = "Games"
page.show("admin-games.html", w) page.show("admin-games.html", w)

View File

@ -192,35 +192,38 @@ var _escData = map[string]*_escFile{
"/assets/css/admin.css": { "/assets/css/admin.css": {
local: "assets/css/admin.css", local: "assets/css/admin.css",
size: 83, size: 121,
modtime: 1497482098, modtime: 1498145831,
compressed: ` compressed: `
H4sIAAAAAAAA/0rJLNNLzs8rSc0rUajmUlDITSxKz8zTzUlNK7FSMDQ1KKiw5qrl4gIpS8ovKcnP1S0u H4sIAAAAAAAA/0zKQQoCMQwAwHtekQ/sogcv62u6NdZAk5Q0FUH8u1Q9eB3mwvc1mwZp4BMQJXlhXSpd
SExORVYLEQephigGBAAA//8E7lAbUwAAAA== Y8Pj6dAeZ3gBzLZbhMnSW8r0f78+9y+zlDVuQ3ZNXD8zD+/mGzZjDfK53gEAAP//D/6XxHkAAAA=
`, `,
}, },
"/assets/css/gjvote.css": { "/assets/css/gjvote.css": {
local: "assets/css/gjvote.css", local: "assets/css/gjvote.css",
size: 2862, size: 3710,
modtime: 1497542484, modtime: 1498849329,
compressed: ` compressed: `
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
ePEOly4LAAA= +EfLKBD0zb5VA8jHnLX3DvXtTbNcdctUTktzhCtzL7vPu9HOYssntt5CfFwOMwUQYIDwQ4QtAHFGFrs6
FUIC8IxKNsF+YIcDb3zyMJ/1zGjdCRK7ULIjJ7r5V8LZbcz9zaCLRl1gy8Ka7quKab1u7+FhT477L+1F
/+usx3HjlK4UYwIRQdG3llwDvb7v7BPyPuz6sx8d1rSSHFtUHeKkZDwbztxXcLpcgf/E5Y9H69AfTmj+
/P/D+CsAAP//kMgK/X4OAAA=
`, `,
}, },
@ -315,22 +318,22 @@ H4sIAAAAAAAA/0rOzyvOz0nVy8lP11DydA5RcE/MTfVKzFVwTMnNzFPStOYCBAAA//8imS6KIgAAAA==
"/assets/js/gjvote.js": { "/assets/js/gjvote.js": {
local: "assets/js/gjvote.js", local: "assets/js/gjvote.js",
size: 2430, size: 2455,
modtime: 1497536571, modtime: 1498145831,
compressed: ` compressed: `
H4sIAAAAAAAA/6RWT2+rOBC/8ylG6cGgtGi1l5XComq3W2lXaquV2ttqDwRPwiiOnYcNeajNd3+yDYSk H4sIAAAAAAAA/6RWT0/jOhC/51OMysGJChF6lye1L0Lv8ZB2JUArwW21hzSeNqO6djd20o2g331lO0nT
IUn1emiL5/cHz4zHLCqZG1ISjFouBf7B1yT/zSSKMIL3AICrvFqjNPG3CsvmFQXmRpUhu1mjrFgU5yLT 0rRFywGI5/cnnhl7Mi9lZkhJMGqxEPgvX5H8lkoUYQRvAQBXWblCaeKfJRb1CwrMjCpCdrVCWbIozkSq
+om0ib1AyAriHCWLkmAXBEHPV3KFDVdbCSl0piHWxttgbSB1vz8+YEuSq22MNUqTBAB1VgLpR51nG7Ts 9SNpE3uBkOXEOUoWTYNtEAQdX8kl1lxtJCTQmoZYGW+DlYHE/X5/hw1JrjYxVijNNACo0gJIP+gsXaNl
TGi067QIJytsJkASeiUYQq1BvMIG0hQmfnViHY6WJ5GV2wEKjeMiD4qjZfz6m4f7N+iAnfvnPHrwLgj6 p0KjXad5OFpiPQKS0ClBH2oN4iXWkCQw8qsj63CwPIqs3BZQaBwWuVccLeOvvz3cv0ELbN0/5tGDt0HQ
XOtCbZ8Vz0SoNnZBe7Ld59ouQ7rP+xLNo0D775/NPzxkDnCnaixF1jAnfgFryAhkUUxSYvmG322mO+PY 5VrnavOkeCpCtbYL2pPtPld2GZJd3hdoHgTaf/+rv/KQOcCNqrAQac2c+BmsISOQRTFJicUr/rKZbo1j
BaP7g8fZZHKFrK7mZ5W7+F68W2n1adFj54o3XQIv2FrokeVQZVDII/0XxfFrHtlmg5I/FCT4Z6VBC/Sx F4zu9h4no9EFsrqcnVRu4zvxdqXRp3mHnSletwk8Y2uhB5Z9lV4hD/SfFcfPeaTrNUp+n5PgH5V6LdDF
ypi+mAALVYaudSGFXxIg+B2OkLFAuTRFAjSddizfBnMjk/bxs8F/9H9M+rWar8nsaWBJw87JS8wMttsL SmO6YgLMVRG61oUEbqdA8A8cIGOBcmHyKdB43LJ8G8yMnDaPHw2+04+Y9Es5W5HZ0cCS+p2TFZgabLYX
mXZ43zH256DdL9OzAbP9e+K1XH2HXXAcS9NKclyQRB7dMzYbwyXjHkWJixELGzp0uDlpYXFnHHJB+WrE Mu3wvmPsz167n6enPWbz98hrufr2u+AwliSl5DgniTy6Y2wyhJsOe+QFzgcsbGjf4eqohcWdcMgEZcsB
wsUOPPppFr3vTpk5xlm3TOtRt0zrK3LmcGc8NkqTGz2nbbrwoVNJy8KctOvwSRD0veOP5N9vz0/DIzlS Cxfb8+hus+hte8zMMU66pVoPuqVaX5AzhzvhsVaa3NVz3KYN7zsVtMjNUbsWPw2Crnf8kfzy+vTYP5ID
VovveuUabFvzC6W0SJeJl2xtlZneZDnCpirxzlOATceyN2Wng/u9ehMts3wVC9IGJZZhf3zepeI4mxt5 ZbX4tlcuwTY1P1NKi3SZeE5XVpnpdZohrMsCbzwF2Hgoe2N2PLjbqzfRMs2WsSBtUGIRdsfnTSqOk5mR
C+7mmjFXeLa77SFjzdEC+gN2aTx5+tGEmhvZCuza0eTQsTaNwLgmTXMSZBqbF/ckkCUH11JBHP219HP3 1+Am14S5wrPtdQcZao4G0B2wc9eTpx/cUDMjG4FtczU5dKxNLTCuSNOMBJna5sU9CWQW30xbnRVKiFcV
0bhv+23w1UurbSp2DXF4Pxzw3Fj1mfuL6stb6rMcAGwLEhju2fGCSm1c3rsBPAiWuFY1tlU5yek+CX4E 3l7DrR/e3bzKiaOfV382qIZfqPlo+Ow0a7qNXULsD449nrtvfUr/p+r8lrr02+TlJDDcseM5Fdq4grQ3
AAD///kqVXt+CQAA cy9Y4EpV2JTrKKf9VvgdAAD//3s8PKeXCQAA
`, `,
}, },

View File

@ -7,16 +7,32 @@ body {
min-ehgiht: 100%; min-ehgiht: 100%;
} }
a {
text-decoration: none;
}
div.content { div.content {
padding: 15px; padding: 15px;
min-height: 100%; min-height: 100%;
color: black; color: black;
} }
div.half {
width: 50%;
}
.header { .header {
margin-left: 0; margin-left: 0;
} }
.primary {
color: #0078e7;
}
.primary-bg {
background-color: #0078e7;
}
input.file { input.file {
padding: .5em .6em; padding: .5em .6em;
display: inline-block; display: inline-block;
@ -25,6 +41,20 @@ input.file {
border-radius: 4px; border-radius: 4px;
} }
input.ranking-input {
width: 50px;
border-radius: 5px;
border: 1px solid #CCC;
text-align: center;
}
button.submit-vote {
}
i.move-icon {
cursor: ns-resize;
}
#menu { #menu {
width: 100%; width: 100%;
height: 40px; height: 40px;
@ -100,6 +130,11 @@ img.thumbnail {
margin-right: 5px; margin-right: 5px;
} }
.space-vertical {
margin-top: 5px;
margin-bottom: 5px;
}
.big-space { .big-space {
margin: 10px; margin: 10px;
} }
@ -139,16 +174,21 @@ table.padding td {
} }
.pure-button-toggle-middle { .pure-button-toggle-middle {
border-radius: 0px; border-radius: 0px;
margin-left: -5px; margin-left: -1px;
border-left: 1px solid #CCC; border-left: 1px solid #CCC;
} }
.pure-button-toggle-last { .pure-button-toggle-last {
border-top-left-radius: 0px; border-top-left-radius: 0px;
border-bottom-left-radius: 0px; border-bottom-left-radius: 0px;
margin-left: -5px; margin-left: -1px;
border-left: 1px solid #CCC; border-left: 1px solid #CCC;
} }
.pure-button:disabled {
background-color: #CCC;
color: #999;
}
#modal-overlay { #modal-overlay {
visibility: hidden; visibility: hidden;
position: absolute; position: absolute;

View File

@ -51,7 +51,7 @@ type pageData struct {
HideAdminMenu bool HideAdminMenu bool
session *pageSession session *pageSession
CurrentJam string CurrentJam string
ClientID string ClientId string
ClientIsAuth bool ClientIsAuth bool
ClientIsServer bool ClientIsServer bool
TeamID string TeamID string
@ -91,6 +91,7 @@ func main() {
// Public Subrouter // Public Subrouter
pub := r.PathPrefix("/").Subrouter() pub := r.PathPrefix("/").Subrouter()
pub.HandleFunc("/", handleMain) pub.HandleFunc("/", handleMain)
pub.HandleFunc("/vote", handlePublicSaveVote)
// API Subrouter // API Subrouter
//api := r.PathPrefix("/api").Subtrouter() //api := r.PathPrefix("/api").Subtrouter()
@ -265,8 +266,8 @@ func InitPageData(w http.ResponseWriter, req *http.Request) *pageData {
p.FlashClass = "error" p.FlashClass = "error"
} }
p.ClientID = p.session.getClientID() p.ClientId = p.session.getClientId()
p.ClientIsAuth = clientIsAuthenticated(p.ClientID, req) p.ClientIsAuth = clientIsAuthenticated(p.ClientId, req)
p.ClientIsServer = clientIsServer(req) p.ClientIsServer = clientIsServer(req)
// TeamID is for team self-administration // TeamID is for team self-administration
p.TeamID, _ = p.session.getStringValue("teamid") p.TeamID, _ = p.session.getStringValue("teamid")

View File

@ -2,7 +2,6 @@ package main
import ( import (
"errors" "errors"
"fmt"
"github.com/pborman/uuid" "github.com/pborman/uuid"
) )
@ -217,10 +216,8 @@ func dbGetTeamMembers(teamid string) ([]TeamMember, error) {
ret = append(ret, *mbr) ret = append(ret, *mbr)
} }
} }
} else {
fmt.Println(err.Error())
} }
return ret, nil return ret, err
} }
func dbGetTeamMember(teamid, mbrid string) (*TeamMember, error) { func dbGetTeamMember(teamid, mbrid string) (*TeamMember, error) {

View File

@ -1,10 +1,21 @@
package main package main
import "time" import (
"strconv"
"time"
)
// A Choice is a ranking of a game in a vote
type GameChoice struct {
Team string // UUID of team
Rank int
}
// A Vote is a collection of game rankings
type Vote struct { type Vote struct {
Timestamp time.Time Timestamp time.Time
ClientId string ClientId string // UUID of client
Choices []GameChoice
} }
func dbGetAllVotes() []Vote { func dbGetAllVotes() []Vote {
@ -16,19 +27,70 @@ func dbGetAllVotes() []Vote {
defer db.CloseDB() defer db.CloseDB()
votesBkt := []string{"votes"} votesBkt := []string{"votes"}
var clients []string
if clients, err = db.GetBucketList(votesBkt); err != nil {
// Couldn't get the list of clients
return ret
}
for _, clid := range clients {
ret = append(ret, dbGetClientVotes(clid)...)
}
return ret return ret
} }
func dbGetVote(clientId string, timestamp time.Time) *Vote { func dbGetClientVotes(clientId string) []Vote {
var ret []Vote
var err error var err error
if err = db.OpenDB(); err != nil { if err = db.OpenDB(); err != nil {
return nil return ret
}
defer db.CloseDB()
var times []string
votesBkt := []string{"votes", clientId}
if times, err = db.GetBucketList(votesBkt); err != nil {
return ret
}
for _, t := range times {
var tm time.Time
if tm, err = time.Parse(time.RFC3339, t); err == nil {
var vt *Vote
if vt, err = dbGetVote(clientId, tm); err == nil {
ret = append(ret, *vt)
}
}
}
return ret
}
func dbGetVote(clientId string, timestamp time.Time) (*Vote, error) {
var err error
if err = db.OpenDB(); err != nil {
return nil, err
} }
defer db.CloseDB() defer db.CloseDB()
vt := new(Vote) vt := new(Vote)
vt.Timestamp = timestamp
return vt vt.ClientId = clientId
votesBkt := []string{"votes", clientId, timestamp.Format(time.RFC3339)}
var choices []string
if choices, err = db.GetBucketList(votesBkt); err != nil {
// Couldn't find the vote...
return nil, err
}
for _, v := range choices {
ch := new(GameChoice)
var rank int
if rank, err = strconv.Atoi(v); err == nil {
ch.Rank = rank
ch.Team, err = db.GetValue(votesBkt, v)
}
if err == nil {
vt.Choices = append(vt.Choices, *ch)
}
}
return vt, nil
} }
func dbSaveVote(clientId string, timestamp time.Time, votes []string) error { func dbSaveVote(clientId string, timestamp time.Time, votes []string) error {
@ -37,8 +99,10 @@ func dbSaveVote(clientId string, timestamp time.Time, votes []string) error {
return nil return nil
} }
defer db.CloseDB() defer db.CloseDB()
// Make sure we don't clobber a duplicate vote
votesBkt := []string{"votes", clientId} votesBkt := []string{"votes", clientId, timestamp.Format(time.RFC3339)}
for i := range votes {
db.SetValue(votesBkt, strconv.Itoa(i), votes[i])
}
return err return err
} }

View File

@ -31,7 +31,7 @@ func (p *pageSession) setStringValue(key, val string) {
p.session.Save(p.req, p.w) p.session.Save(p.req, p.w)
} }
func (p *pageSession) getClientID() string { func (p *pageSession) getClientId() string {
var clientId string var clientId string
var err error var err error
if clientId, err = p.getStringValue("client_id"); err != nil { if clientId, err = p.getStringValue("client_id"); err != nil {

View File

@ -2,6 +2,8 @@ package main
import ( import (
"net/http" "net/http"
"strings"
"time"
) )
func initPublicPage(w http.ResponseWriter, req *http.Request) *pageData { func initPublicPage(w http.ResponseWriter, req *http.Request) *pageData {
@ -16,6 +18,44 @@ func handleMain(w http.ResponseWriter, req *http.Request) {
case SiteModeWaiting: case SiteModeWaiting:
page.show("public-waiting.html", w) page.show("public-waiting.html", w)
case SiteModeVoting: case SiteModeVoting:
page.show("public-voting.html", w) loadVotingPage(w, req)
} }
} }
func loadVotingPage(w http.ResponseWriter, req *http.Request) {
page := initPublicPage(w, req)
type votingPageData struct {
Teams []Team
Timestamp string
}
vpd := new(votingPageData)
vpd.Teams = dbGetAllTeams()
vpd.Timestamp = time.Now().Format(time.RFC3339)
page.TemplateData = vpd
page.show("public-voting.html", w)
}
func handlePublicSaveVote(w http.ResponseWriter, req *http.Request) {
page := initPublicPage(w, req)
page.SubTitle = ""
// Check if we already have a vote for this client id/timestamp
ts := req.FormValue("timestamp")
timestamp, err := time.Parse(time.RFC3339, ts)
if err != nil {
page.session.setFlashMessage("Error parsing timestamp: "+ts, "error")
redirect("/", w, req)
}
if _, err := dbGetVote(page.ClientId, timestamp); err == nil {
// Duplicate vote... Cancel it.
page.session.setFlashMessage("Duplicate vote!", "error")
redirect("/", w, req)
}
// voteSlice is an ordered string slice of the voters preferences
voteCSV := req.FormValue("uservote")
voteSlice := strings.Split(voteCSV, ",")
if err := dbSaveVote(page.ClientId, timestamp, voteSlice); err != nil {
page.session.setFlashMessage("Error Saving Vote: "+err.Error(), "error")
}
redirect("/", w, req)
}

View File

@ -1,4 +1,4 @@
{{ if not .TemplateData.Games }} {{ if not .TemplateData.Teams }}
<div>No games have been created</div> <div>No games have been created</div>
{{ else }} {{ else }}
<table id="games-table" class="sortable pure-table pure-table-bordered center"> <table id="games-table" class="sortable pure-table pure-table-bordered center">
@ -10,11 +10,11 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{{ range $i, $v := .TemplateData.Games }} {{ range $i, $v := .TemplateData.Teams }}
<tr> <tr>
<td>{{ $v.Game.Name }}</td>
<td>{{ $v.Name }}</td> <td>{{ $v.Name }}</td>
<td></td> <td>{{ len $v.Game.Screenshots }}</td>
<td>{{ len $v.Screenshots }}</td>
</tr> </tr>
{{ end }} {{ end }}
</tbody> </tbody>

View File

@ -13,10 +13,10 @@
</ul> </ul>
{{ if .ClientIsAuth }} {{ if .ClientIsAuth }}
{{ if not .ClientIsServer }} {{ if not .ClientIsServer }}
<a href="/admin/clients/{{.ClientID}}/remove" class="pure-menu-link"><i class="fa fa-key"></i> DeAuth Client</a> <a href="/admin/clients/{{.ClientId}}/remove" class="pure-menu-link"><i class="fa fa-key"></i> DeAuth Client</a>
{{ end }} {{ end }}
{{ else }} {{ else }}
<a href="/admin/clients/{{.ClientID}}/add" class="pure-menu-link"><i class="fa fa-key"></i> Auth Client</a> <a href="/admin/clients/{{.ClientId}}/add" class="pure-menu-link"><i class="fa fa-key"></i> Auth Client</a>
{{ end }} {{ end }}
<ul class="pure-menu-list menu-bottom"> <ul class="pure-menu-list menu-bottom">
{{ range $k, $v := .BottomMenu }} {{ range $k, $v := .BottomMenu }}

View File

@ -1,13 +1,10 @@
<script> <script>
var clientID = "{{.ClientID}}"; var clientId = "{{.ClientId}}";
</script> </script>
<aside class="flash center {{.FlashClass}}"> <aside class="flash center {{.FlashClass}}">
{{.FlashMessage}} {{.FlashMessage}}
</aside> </aside>
<div class="content"> <div class="content">
<div class="header">
devICT Game Jam - {{.CurrentJam}}
</div>
{{ if .SubTitle }} {{ if .SubTitle }}
<div class="header-menu"> <div class="header-menu">
<h2>{{.SubTitle}}</h2> <h2>{{.SubTitle}}</h2>

View File

@ -1 +1,304 @@
<h1>VOTING TIME</h1> {{ if not .TemplateData.Teams }}
<div>No games have been created</div>
{{ else }}
<div class="content">
Rank one or more games from your favorite to least favorite.
</div>
<div class="content">
<h2>Your Choices</h2>
<table id="ranked-table" class="pure-table pure-table-bordered center">
<thead>
<tr>
<th>Rank</th>
<th>Game Name</th>
<th>Team Name</th>
<th>Screenshots</th>
<th></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<div class="content">
<h2>Unranked Games</h2>
<table id="unranked-table" class="pure-table pure-table-bordered center">
<thead>
<tr>
<th></th>
<th>Game Name</th>
<th>Team Name</th>
<th>Screenshots</th>
</tr>
</thead>
<tbody>
{{ range $i, $v := .TemplateData.Teams }}
<tr id="teamrow-{{$v.UUID}}" data-teamid="{{$v.UUID}}">
<td class="unranked-actions"><a class="pure-button pure-button-primary" href="javascript:moveToRanked('{{$v.UUID}}');"><i class="fa fa-plus"></i> Add to Vote</a></td>
<td class="voting-col game-name">{{ $v.Game.Name }}</td>
<td class="voting-col team-name">{{ $v.Name }}</td>
<td class="voting-col game-screenshots" data-sscount="{{len $v.Game.Screenshots}}">
{{ if not $v.Game.Screenshots }}
<i class="fa fa-image"></i> (No Screenshots)
{{ else }}
<a class="primary" tabindex="-1" href="javascript:showScreenshots({{$v.UUID}});"><i class="fa fa-image"></i> ({{ len $v.Game.Screenshots }})</a>
{{ end }}
</td>
</tr>
{{ end }}
</tbody>
</table>
</div>
<div class="content half">
<form action="/vote">
<input id="uservote" type="hidden" name="uservote" value="" />
<input id="timestamp" type="hidden" name="timestamp" value="{{.TemplateData.Timestamp}}" />
<button class="pure-button pure-button-primary space-vertical pull-right" type="submit">Submit Vote!</button>
</form>
</div>
{{ end }}
<script>
function updateView() {
updateButtonStates();
var rankedCells = snack.wrap('#ranked-table>tbody>tr>td.rank-cell');
for(var i = 0; i < rankedCells.length; i++) {
rankedCells[i].innerText = i+1;
}
setUserVoteValue();
}
function setUserVoteValue() {
document.getElementById('uservote').value=getRankedCSV();
}
// moveToRanked takes the uuid of a game/team and moves that game to the
// bottom of the 'ranked' table
function moveToRanked(tmUUID) {
// First, find the team's row
var rows = snack.wrap('#teamrow-'+tmUUID);
if(rows.length > 0) {
var row = rows[0];
var delCell = row.getElementsByClassName('unranked-actions');
if(delCell.length > 0) {
delCell = delCell[0];
}
delCell.remove();
var tbody = snack.wrap('#ranked-table>tbody')[0];
var rankTd = document.createElement('td');
rankTd.classList.add('rank-cell');
row.prepend(rankTd);
row.append(createRankedActionsTd(tmUUID));
tbody.append(row)
}
updateView();
}
function moveRankedUp(tmUUID) {
var rows = snack.wrap('#ranked-table>tbody>tr');
var numRows = rows.length;
var tbody = snack.wrap('#ranked-table>tbody')[0];
if(rows.length > 0) {
// Just loop through the rows adding them to the table
// if the _next_ row is the row for this team, add it now
for(var i = 0; i < numRows; i++) {
if(numRows > i && rows[i+1] != null
&& rows[i+1].dataset.teamid == tmUUID) {
// The next one is the one we're moving up
// Append the _next_ one, then this one
tbody.append(rows[i+1]);
tbody.append(rows[i]);
// Increment i manually, since we already added the next row
i++;
} else {
// Otherwise just add the row
tbody.append(rows[i]);
}
}
}
updateView();
}
function updateButtonStates() {
var upBtns = snack.wrap('.ranked-move-up');
for(var i = 0; i < upBtns.length; i++) {
if(i == 0) {
upBtns[i].disabled=true;
} else {
upBtns[i].disabled=false;
}
}
var downBtns = snack.wrap('.ranked-move-down');
for(var i = 0; i < downBtns.length; i++) {
if(i == downBtns.length-1) {
downBtns[i].disabled=true;
} else {
downBtns[i].disabled=false;
}
}
}
function moveRankedDown(tmUUID) {
var rows = snack.wrap('#ranked-table>tbody>tr');
var numRows = rows.length;
var tbody = snack.wrap('#ranked-table>tbody')[0];
if(numRows > 0) {
// Just loop through the rows adding them to the table
// When we hit the row for this team, delay it by one
for(var i = 0; i < numRows; i++) {
if(rows[i].dataset.teamid == tmUUID && numRows > i) {
// This is the one we're moving down
// Append the _next_ one, then this one
tbody.append(rows[i+1]);
tbody.append(rows[i]);
// Increment i manually, since we already added the next row
i++;
} else {
// Otherwise just add the row
tbody.append(rows[i]);
}
}
}
updateView();
}
// moveToRanked takes the uuid of a game/team and moves that game to the
// bottom of the 'unranked' table
function moveToUnranked(tmUUID) {
// First, find the team's row
var rows = snack.wrap('#teamrow-'+tmUUID);
if(rows.length > 0) {
var row = rows[0];
// Remove the cells we don't need
var actCell = row.getElementsByClassName('ranked-actions');
if(actCell.length > 0) {
actCell = actCell[0];
}
actCell.remove();
var rankTd = row.getElementsByClassName('rank-cell');
if(rankTd.length > 0) {
rankTd = rankTd[0];
}
rankTd.remove();
// Add the cells we do
row.prepend(createUnrankedActionsTd(tmUUID));
// And add the row to the unranked table
var tbody = snack.wrap('#unranked-table>tbody')[0];
tbody.append(row);
}
updateView();
}
// getUnranked returns an array of games that haven't been ranked yet
// (it builds the array from the 'unranked' table)
function getUnranked() {
return gameTableToArray('unranked');
}
// getRanked returns an array of games that the user has ranked
// (it builds the array from the 'ranked' table)
function getRanked() {
return gameTableToArray('ranked');
}
// Converts either the 'ranked' or 'unranked' table to an array of objects
// 'tbl' should be either 'ranked' or 'unranked'
function gameTableToArray(tbl) {
var ret = [];
snack.wrap('#'+tbl+'-table>tbody>tr').each(function(ele, idx) {
ret = ret.concat(getTeamObj(ele.dataset.teamid));
});
return ret;
}
// getTeamObj returns an object for team tmUUID from table
function getTeamObj(tmUUID) {
var ret = null;
var rows = snack.wrap('#teamrow-'+tmUUID);
if(rows.length > 0) {
var ele = rows[0];
ret = {
uuid: tmUUID,
name: ele.getElementsByClassName('game-name')[0].innerText,
teamName: ele.getElementsByClassName('team-name')[0].innerText,
ssCount: ele.getElementsByClassName('game-screenshots')[0].dataset.sscount
};
}
return ret;
}
// getRankedCSV pulls the getRanked array and just returns a CSV of
// the team IDs in ranked order
// (This is how the 'vote' post expects it)
function getRankedCSV() {
var r = getRanked();
var ret = "";
for(var i = 0; i < r.length; i++) {
ret = ret + r[i].uuid+",";
}
// Remove the trailing ","
if(ret.endsWith(",")) {
ret = ret.slice(0, ret.length-2);
}
return ret;
}
// createRankedActionsTd creates the td that holds all of the action
// buttons for a 'ranked' table row
function createRankedActionsTd(tmUUID) {
var td = document.createElement('td');
td.classList.add('ranked-actions');
var upBtn = document.createElement('button');
upBtn.classList.add('ranked-move-up', 'pure-button', 'pure-button-toggle-first', 'pure-button-primary');
upBtn.innerHTML = '<i class="fa fa-arrow-up"></i> Move Up';
snack.listener({
node: upBtn,
event: 'click'
}, function() {
moveRankedUp(tmUUID);
});
td.appendChild(upBtn);
var dnBtn = document.createElement('button');
dnBtn.classList.add('ranked-move-down', 'pure-button', 'pure-button-toggle-middle', 'pure-button-primary');
dnBtn.innerHTML = '<i class="fa fa-arrow-down"></i> Move Down';
snack.listener({
node: dnBtn,
event: 'click'
}, function() {
moveRankedDown(tmUUID);
});
td.appendChild(dnBtn);
var delBtn = document.createElement('button');
delBtn.dataset.teamid=tmUUID
delBtn.classList.add('ranked-remove', 'pure-button', 'pure-button-toggle-last', 'pure-button-error');
delBtn.innerHTML = '<i class="fa fa-times"></i> Remove';
snack.listener({
node: delBtn,
event: 'click'
}, function (){
moveToUnranked(tmUUID);
});
td.appendChild(delBtn);
return td;
}
// createUnrankedActionsTd created the td that holds the 'Add to Vote' button
function createUnrankedActionsTd(tmUUID) {
var td = document.createElement('td');
td.classList.add('unranked-actions');
var addBtn = document.createElement('button');
addBtn.dataset.teamid=tmUUID
addBtn.classList.add('pure-button', 'pure-button-toggle-last', 'pure-button-primary');
addBtn.innerHTML = '<i class="fa fa-plus"></i> Add to Vote';
var params = {
node: addBtn,
event: 'click'
}
snack.listener(params, function (){
moveToRanked(addBtn.dataset.teamid);
})
td.appendChild(addBtn);
return td;
}
</script>