Voting is working
This commit is contained in:
parent
153b869a98
commit
96a53b6090
@ -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)
|
||||||
|
77
assets.go
77
assets.go
@ -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
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
7
main.go
7
main.go
@ -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")
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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:
|
||||||
|
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)
|
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)
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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 }}
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user