Add Sponsor Slideshow Configuration Page

This commit is contained in:
Nick Eyre
2014-08-24 20:28:04 -07:00
parent 5118421b07
commit b1aa2dcbbc
12 changed files with 357 additions and 62 deletions

21
api.go
View File

@@ -55,6 +55,27 @@ func MatchesApiHandler(w http.ResponseWriter, r *http.Request) {
}
}
func SponsorsApiHandler(w http.ResponseWriter, r *http.Request) {
sponsors, err := db.GetAllSponsorSlideshows()
if err != nil {
handleWebErr(w, err)
return
}
jsonData, err := json.MarshalIndent(sponsors, "", " ")
if err != nil {
handleWebErr(w, err)
return
}
w.Header().Set("Content-Type", "application/json")
_, err = w.Write(jsonData)
if err != nil {
handleWebErr(w, err)
return
}
}
// Generates a JSON dump of the qualification rankings.
func RankingsApiHandler(w http.ResponseWriter, r *http.Request) {
rankings, err := db.GetAllRankings()

View File

@@ -15,15 +15,16 @@ import (
const migrationsDir = "db/migrations"
type Database struct {
path string
db *sql.DB
eventSettingsMap *modl.DbMap
matchMap *modl.DbMap
matchResultMap *modl.DbMap
rankingMap *modl.DbMap
teamMap *modl.DbMap
allianceTeamMap *modl.DbMap
lowerThirdMap *modl.DbMap
path string
db *sql.DB
eventSettingsMap *modl.DbMap
matchMap *modl.DbMap
matchResultMap *modl.DbMap
rankingMap *modl.DbMap
teamMap *modl.DbMap
allianceTeamMap *modl.DbMap
lowerThirdMap *modl.DbMap
sponsorSlideshowMap *modl.DbMap
}
// Opens the SQLite database at the given path, creating it if it doesn't exist, and runs any pending
@@ -79,4 +80,7 @@ func (database *Database) mapTables() {
database.lowerThirdMap = modl.NewDbMap(database.db, dialect)
database.lowerThirdMap.AddTableWithName(LowerThird{}, "lower_thirds").SetKeys(true, "Id")
database.sponsorSlideshowMap = modl.NewDbMap(database.db, dialect)
database.sponsorSlideshowMap.AddTableWithName(SponsorSlideshow{}, "sponsor_slideshow").SetKeys(true, "Id")
}

View File

@@ -0,0 +1,12 @@
-- +goose Up
CREATE TABLE sponsor_slideshow (
id INTEGER PRIMARY KEY,
subtitle VARCHAR(255),
line1 VARCHAR(255),
line2 VARCHAR(255),
image VARCHAR(255),
priority VARCHAR(255)
);
-- +goose Down
DROP TABLE sponsor_slideshow;

View File

@@ -0,0 +1,74 @@
// Copyright 2014 Team 254. All Rights Reserved.
// Author: nick@team254.com (Nick Eyre)
//
// Web routes for managing sponsor slideshow.
package main
import (
"html/template"
"net/http"
"strconv"
)
// Shows the lower third configuration page.
func SponsorSlideshowGetHandler(w http.ResponseWriter, r *http.Request) {
template, err := template.ParseFiles("templates/sponsor_slideshow.html", "templates/base.html")
if err != nil {
handleWebErr(w, err)
return
}
sponsorSlideshow, err := db.GetAllSponsorSlideshows()
if err != nil {
handleWebErr(w, err)
return
}
data := struct {
*EventSettings
SponsorSlideshow []SponsorSlideshow
}{eventSettings, sponsorSlideshow}
err = template.ExecuteTemplate(w, "base", data)
if err != nil {
handleWebErr(w, err)
return
}
}
// Saves the new or modified lower third to the database and triggers showing it on the audience display.
func SponsorSlideshowPostHandler(w http.ResponseWriter, r *http.Request) {
sponsorSlideshowId, _ := strconv.Atoi(r.PostFormValue("id"))
sponsorSlideshow, err := db.GetSponsorSlideshowById(sponsorSlideshowId)
if err != nil {
handleWebErr(w, err)
return
}
if r.PostFormValue("action") == "delete" {
err := db.DeleteSponsorSlideshow(sponsorSlideshow)
if err != nil {
handleWebErr(w, err)
return
}
} else {
if sponsorSlideshow == nil {
sponsorSlideshow = &SponsorSlideshow{Subtitle: r.PostFormValue("subtitle"),
Line1: r.PostFormValue("line1"),
Line2: r.PostFormValue("line2"),
Image: r.PostFormValue("image"),
Priority: r.PostFormValue("priority")}
err = db.CreateSponsorSlideshow(sponsorSlideshow)
} else {
sponsorSlideshow.Subtitle = r.PostFormValue("subtitle")
sponsorSlideshow.Line1 = r.PostFormValue("line1")
sponsorSlideshow.Line2 = r.PostFormValue("line2")
sponsorSlideshow.Image = r.PostFormValue("image")
sponsorSlideshow.Priority = r.PostFormValue("priority")
err = db.SaveSponsorSlideshow(sponsorSlideshow)
}
if err != nil {
handleWebErr(w, err)
return
}
}
http.Redirect(w, r, "/setup/sponsor_slideshow", 302)
}

49
sponsor_slideshow.go Normal file
View File

@@ -0,0 +1,49 @@
// Copyright 2014 Team 254. All Rights Reserved.
// Author: nick@team254.com (Nick Eyre)
//
// Model and datastore CRUD methods for the sponsor slideshow.
package main
type SponsorSlideshow struct {
Id int
Subtitle string
Line1 string
Line2 string
Image string
Priority string
}
func (database *Database) CreateSponsorSlideshow(sponsorSlideshow *SponsorSlideshow) error {
return database.sponsorSlideshowMap.Insert(sponsorSlideshow)
}
func (database *Database) GetSponsorSlideshowById(id int) (*SponsorSlideshow, error) {
sponsorSlideshow := new(SponsorSlideshow)
err := database.sponsorSlideshowMap.Get(sponsorSlideshow, id)
if err != nil && err.Error() == "sql: no rows in result set" {
sponsorSlideshow = nil
err = nil
}
return sponsorSlideshow, err
}
func (database *Database) SaveSponsorSlideshow(sponsorSlideshow *SponsorSlideshow) error {
_, err := database.sponsorSlideshowMap.Update(sponsorSlideshow)
return err
}
func (database *Database) DeleteSponsorSlideshow(sponsorSlideshow *SponsorSlideshow) error {
_, err := database.sponsorSlideshowMap.Delete(sponsorSlideshow)
return err
}
func (database *Database) TruncateSponsorSlideshows() error {
return database.sponsorSlideshowMap.TruncateTables()
}
func (database *Database) GetAllSponsorSlideshows() ([]SponsorSlideshow, error) {
var sponsorSlideshows []SponsorSlideshow
err := database.teamMap.Select(&sponsorSlideshows, "SELECT * FROM sponsor_slideshow ORDER BY id")
return sponsorSlideshows, err
}

View File

@@ -311,6 +311,7 @@ html {
left: 0;
bottom: 0;
right: 0;
height: 96px;
}
#sponsor h2 {
font-size: 7em;

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="538.895px" height="158.068px" viewBox="0 0 538.895 158.068" enable-background="new 0 0 538.895 158.068"
xml:space="preserve">
<path fill="#231F20" d="M63.03,119.722c-4.913-4.879-8.922-10.564-11.979-16.831C39.216,98.092,30.783,86.21,30.783,71.247
c0-12.011,8.489-23.219,20.389-27.82c3.364-6.244,7.866-11.956,13.436-16.903c5.148-4.573,11.071-8.324,17.446-11.167
c-6.127-1.825-12.573-2.81-19.128-2.81C30.181,12.547,0,36.69,0,71.096c0,36.82,26.406,62.775,62.926,62.775
c6.09,0,11.89-0.738,17.341-2.112C73.85,128.712,68.024,124.683,63.03,119.722z"/>
<path fill="#0070FF" d="M111.004,105.794c-17.652,0-32.139-14.336-32.139-33.954c0-15.693,14.486-30.028,32.139-30.028
c9.09,0,17.33,3.804,23.196,9.629l20.222-22.203c-11.604-10.164-27.192-16.098-43.418-16.098c-32.742,0-62.924,24.145-62.924,58.548
c0,36.819,26.408,62.774,62.924,62.774c18.367,0,34.17-6.568,45.348-17.777l-22.529-20.648
C127.982,102.156,119.896,105.794,111.004,105.794z"/>
<g>
<path fill="#231F20" d="M314.029,31.231V2.148h19.589v75.358h-19.589V46.623h-28.284v30.883h-19.59V2.148h19.59v29.083H314.029z"/>
<path fill="#231F20" d="M365.997,18.739v12.593h22.088v16.59h-22.088v12.993h23.289v16.591h-42.877V2.148h42.877v16.591H365.997z"
/>
<path fill="#231F20" d="M460.642,60.915v16.591h-64.564l35.682-58.767h-29.584V2.148h62.365l-35.479,58.767H460.642z"/>
<path fill="#231F20" d="M463.738,2.148h23.486l14.092,20.489l14.092-20.489h23.486L511.11,41.326v36.18h-19.59v-36.18
L463.738,2.148z"/>
<path fill="#231F20" d="M229.816,60.613c-11.548,0-21.025-9.378-21.025-22.212c0-10.267,9.477-19.644,21.025-19.644
c5.947,0,11.338,2.488,15.175,6.3l13.229-14.525C250.629,3.882,240.432,0,229.816,0c-21.419,0-41.165,15.795-41.165,38.302
c0,24.088,17.276,41.067,41.165,41.067c12.016,0,22.354-4.297,29.667-11.63L244.744,54.23
C240.924,58.233,235.634,60.613,229.816,60.613z"/>
</g>
<g>
<path fill="#0070FF" d="M302.366,118.643V91.158h4.122v64.762h-4.122v-33.412h-39.338v33.412h-4.123V91.158h4.123v27.485H302.366z"
/>
<path fill="#0070FF" d="M326.763,134.619l-8.675,21.301h-4.209l27.143-66.909l27.055,66.909h-4.208l-8.675-21.301H326.763z
M341.021,99.49l-12.713,31.264h25.338L341.021,99.49z"/>
<path fill="#0070FF" d="M431.75,102.324h-0.172l-22.59,55.744l-22.503-55.744h-0.172l-11.08,53.596h-3.951l13.915-66.909
l23.791,58.922l23.879-58.922l13.914,66.909h-3.951L431.75,102.324z"/>
<path fill="#0070FF" d="M458.639,155.919h-4.123V91.158h11.424c5.067,0,9.963,0.344,14,3.779c3.951,3.35,5.841,8.246,5.841,13.313
c0,4.639-1.804,9.792-5.411,12.799c-3.951,3.436-9.362,4.123-14.43,4.123h-7.301V155.919z M466.111,121.306
c4.209,0,7.988-0.516,11.338-3.521c2.834-2.578,4.209-5.928,4.209-9.707c0-4.036-1.633-8.073-4.896-10.479
c-3.264-2.49-7.387-2.576-11.338-2.576h-6.785v26.283H466.111z"/>
<path fill="#0070FF" d="M525.035,101.808c-2.921-4.639-7.044-7.645-13.143-7.645c-6.957,0-12.196,5.583-12.196,12.369
c0,6.785,6.442,9.619,11.682,12.11l12.368,6.442c4.896,3.092,7.387,7.043,7.387,12.883c0,10.736-9.791,18.811-20.271,18.811
c-9.019,0-16.062-5.583-19.067-13.914l3.607-1.633c2.748,6.872,7.816,11.682,15.632,11.682c8.503,0,15.976-6.699,15.976-15.289
c0-7.301-7.473-10.393-13.055-13.141l-11.682-6.185c-4.295-2.663-6.699-6.442-6.699-11.595c0-9.621,6.871-16.406,16.576-16.406
c6.528,0,12.713,3.521,15.805,9.105L525.035,101.808z"/>
<path fill="#0070FF" d="M250.643,146.207l-2.961-2.715c-5.404,5.756-13.045,9.422-21.415,9.422
c-16.147,0-29.461-13.398-29.461-29.375c0-15.976,13.313-29.375,29.461-29.375c8.11,0,15.538,3.419,20.908,8.857l2.689-2.953
c-6.118-6.02-14.504-9.77-23.597-9.77c-18.295,0-33.583,15.031-33.583,33.24s15.375,33.24,33.583,33.24
C235.756,156.779,244.468,152.692,250.643,146.207z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -314,6 +314,53 @@ var transitionSponsorToScore = function(callback) {
});
};
// Load and Prioritize Sponsor Data
var sponsors;
var sponsorIndex = [];
var initializeSponsorDisplay = function() {
$.getJSON("/api/sponsors", function(data) {
sponsors = data;
// Invert Priorities
$.each(sponsors, function(index){
var priorityCount = 10 / sponsors[index]['Priority'];
for(i=0; i<priorityCount; i++){
sponsorIndex.push(index);
}
});
// Load Tiles
loadNextSponsor(true);
if(sponsors.length > 1)
loadNextSponsor();
$('.carousel#sponsor').on('slid.bs.carousel', function(){
loadNextSponsor();
$('#sponsorContainer').children()[0].remove();
});
});
}
var lastSponsor;
var loadNextSponsor = function(active) {
// Don't load same sponsor twice in a row
var currentSponsor = sponsorIndex[Math.round(Math.random()*sponsorIndex.length)];
while(currentSponsor == lastSponsor){
currentSponsor = sponsorIndex[Math.round(Math.random()*sponsorIndex.length)];
}
lastSponsor = currentSponsor;
currentSponsor = sponsors[currentSponsor];
if(active == true)
active = 'active'
else
active = '';
if(currentSponsor['Image'].length)
$('#sponsorContainer').append('<div class="item '+active+'"><img src="/static/img/sponsors/'+currentSponsor['Image']+'" /><h1>'+currentSponsor['Subtitle']+'</h1></div>');
else
$('#sponsorContainer').append('<div class="item '+active+'"><h2>'+currentSponsor['Line1']+'<br />'+currentSponsor['Line2']+'</h2><h1>'+currentSponsor['Subtitle']+'</h1></div>');
}
$(function() {
// Set up the websocket back to the server.
websocket = new CheesyWebsocket("/displays/audience/websocket", {
@@ -328,6 +375,8 @@ $(function() {
lowerThird: function(event) { handleLowerThird(event.data); }
});
initializeSponsorDisplay();
// Map how to transition from one screen to another. Missing links between screens indicate that first we
// must transition to the blank screen and then to the target screen.
transitionMap = {

View File

@@ -100,59 +100,7 @@
</div>
</div>
<div id="sponsor" class="carousel slide" data-ride="carousel" data-pause="false" data-interval="10000">
<div class="carousel-inner">
<div class="item active">
<img src="/static/img/logo-white.svg" />
<h1>&nbsp;</h1>
</div>
<div class="item">
<img src="/static/img/sponsors/vex.svg" />
<h1>Gold Sponsor</h1>
</div>
<div class="item">
<img src="/static/img/sponsors/bcp.svg" />
<h1>Host Facility</h1>
</div>
<div class="item">
<img src="/static/img/sponsors/suitable.svg" />
<h1>Silver Sponsor</h1>
</div>
<div class="item">
<h2>The Kasle<br />Family</h2>
<h1>Bronze Sponsor</h1>
</div>
<div class="item">
<h2>The Magarelli Family</h2>
<h1>Silver Sponsor</h1>
</div>
<div class="item">
<img src="/static/img/sponsors/vex.svg" />
<h1>Gold Sponsor</h1>r
</div>
<div class="item">
<img src="/static/img/sponsors/morganstanley.svg" />
<h1>Bronze Sponsor</h1>
</div>
<div class="item">
<h2>The Ramstad<br />Family</h2>
<h1>Bronze Sponsor</h1>
</div>
<div class="item">
<img src="/static/img/sponsors/bcp.svg" />
<h1>Host Facility</h1>
</div>
<div class="item">
<img src="/static/img/sponsors/vex.svg" />
<h1>Gold Sponsor</h1>r
</div>
<div class="item">
<img src="/static/img/sponsors/suitable.svg" />
<h1>Silver Sponsor</h1>r
</div>
<div class="item">
<h2>Tod<br />Francis</h2>
<h1>Bronze Sponsor</h1>
</div>
<div class="carousel-inner" id="sponsorContainer">
</div>
</div>
</div>

View File

@@ -26,6 +26,7 @@
<li><a href="/setup/schedule">Match Scheduling</a></li>
<li><a href="/setup/alliance_selection">Alliance Selection</a></li>
<li><a href="/setup/lower_thirds">Lower Thirds</a></li>
<li><a href="/setup/sponsor_slideshow">Sponsor Slideshow</a></li>
</ul>
</li>
<li class="dropdown">

View File

@@ -0,0 +1,85 @@
{{define "title"}}Sponsor Slideshow Configuration{{end}}
{{define "body"}}
<div class="row">
<div class="col-lg-8 col-lg-offset-2">
<div class="well">
<legend>Sponsor Slideshow Configuration</legend>
<p>Place images in /static/img/sponsors/</p>
{{range $sponsorSlideshow := .SponsorSlideshow}}
<form class="form-horizontal existing" action="/setup/sponsor_slideshow" method="POST" data-priority="{{$sponsorSlideshow.Priority}}">
<div class="form-group">
<div class="col-lg-7">
<input type="hidden" name="id" value="{{$sponsorSlideshow.Id}}" />
<input type="text" class="form-control" name="image" value="{{$sponsorSlideshow.Image}}" placeholder="Image File Name" />
<div class='form-inline'>
<input type="text" class="form-control" name="line1" value="{{$sponsorSlideshow.Line1}}" placeholder="Line 1 Text" />
<input type="text" class="form-control" name="line2" value="{{$sponsorSlideshow.Line2}}" placeholder="Line 2 Text" />
</div>
<input type="text" class="form-control" name="subtitle" value="{{$sponsorSlideshow.Subtitle}}" placeholder="Subtitle Text" />
<label class="radio-inline">
<input type="radio" name="priority" value="1"> 1 (Show Often)
</label>
<label class="radio-inline">
<input type="radio" name="priority" value="2"> 2
</label>
<label class="radio-inline">
<input type="radio" name="priority" value="3"> 3
</label>
<label class="radio-inline">
<input type="radio" name="priority" value="4"> 4
</label>
<label class="radio-inline">
<input type="radio" name="priority" value="5"> 5 (Show Rarely)
</label>
</div>
<div class="col-lg-5">
<button type="submit" class="btn btn-info btn-lower-third" name="action" value="save">Save</button>
<br />
<button type="submit" class="btn btn-primary btn-lower-third" name="action" value="delete">Delete</button>
</div>
</div>
</form>
{{end}}
<form class="form-horizontal" action="/setup/sponsor_slideshow" method="POST">
<div class="form-group">
<div class="col-lg-7">
<input type="text" class="form-control" name="image" placeholder="Image File Name" />
<div class='form-inline'>
<input type="text" class="form-control" name="line1" placeholder="Line 1 Text" />
<input type="text" class="form-control" name="line2" placeholder="Line 2 Text" />
</div>
<input type="text" class="form-control" name="subtitle" placeholder="Subtitle Text" />
<label class="radio-inline">
<input type="radio" name="priority" value="1"> 1 (Show Often)
</label>
<label class="radio-inline">
<input type="radio" name="priority" value="2"> 2
</label>
<label class="radio-inline">
<input type="radio" name="priority" value="3"> 3
</label>
<label class="radio-inline">
<input type="radio" name="priority" value="4"> 4
</label>
<label class="radio-inline">
<input type="radio" name="priority" value="5"> 5 (Show Rarely)
</label>
</div>
<div class="col-lg-5">
<button type="submit" class="btn btn-info btn-lower-third" name="action" value="save">Save</button>
</div>
</div>
</form>
</div>
</div>
</div>
{{end}}
{{define "script"}}
<script type='text/javascript'>
$(function(){
$('form.existing').each(function(){
$(this).find('input[name=priority][value='+$(this).attr('data-priority')+']').attr('checked','true');
});
});
</script>
{{end}}

3
web.go
View File

@@ -126,6 +126,9 @@ func newHandler() http.Handler {
router.HandleFunc("/setup/field/reload_displays", FieldReloadDisplaysHandler).Methods("GET")
router.HandleFunc("/setup/lower_thirds", LowerThirdsGetHandler).Methods("GET")
router.HandleFunc("/setup/lower_thirds", LowerThirdsPostHandler).Methods("POST")
router.HandleFunc("/setup/sponsor_slideshow", SponsorSlideshowGetHandler).Methods("GET")
router.HandleFunc("/setup/sponsor_slideshow", SponsorSlideshowPostHandler).Methods("POST")
router.HandleFunc("/api/sponsors", SponsorsApiHandler).Methods("GET")
router.HandleFunc("/match_play", MatchPlayHandler).Methods("GET")
router.HandleFunc("/match_play/{matchId}/load", MatchPlayLoadHandler).Methods("GET")
router.HandleFunc("/match_play/{matchId}/show_result", MatchPlayShowResultHandler).Methods("GET")