diff --git a/arena.go b/arena.go index f9b59d4..c76aa5a 100644 --- a/arena.go +++ b/arena.go @@ -12,8 +12,9 @@ import ( ) const ( - arenaLoopPeriodMs = 10 - dsPacketPeriodMs = 250 + arenaLoopPeriodMs = 10 + dsPacketPeriodMs = 250 + matchEndScoreDwellSec = 3 ) // Progression of match states. @@ -56,30 +57,34 @@ type RealtimeScore struct { } type Arena struct { - AllianceStations map[string]*AllianceStation - MatchState int - CanStartMatch bool - matchTiming MatchTiming - currentMatch *Match - redRealtimeScore *RealtimeScore - blueRealtimeScore *RealtimeScore - matchStartTime time.Time - lastDsPacketTime time.Time - matchStateNotifier *Notifier - matchTimeNotifier *Notifier - robotStatusNotifier *Notifier - matchLoadTeamsNotifier *Notifier - scoringStatusNotifier *Notifier - realtimeScoreNotifier *Notifier - scorePostedNotifier *Notifier - audienceDisplayNotifier *Notifier - playSoundNotifier *Notifier - audienceDisplayScreen string - allianceStationDisplays map[string]string - lastMatchState int - lastMatchTimeSec float64 - savedMatch *Match - savedMatchResult *MatchResult + AllianceStations map[string]*AllianceStation + MatchState int + CanStartMatch bool + matchTiming MatchTiming + currentMatch *Match + redRealtimeScore *RealtimeScore + blueRealtimeScore *RealtimeScore + matchStartTime time.Time + lastDsPacketTime time.Time + matchStateNotifier *Notifier + matchTimeNotifier *Notifier + robotStatusNotifier *Notifier + matchLoadTeamsNotifier *Notifier + scoringStatusNotifier *Notifier + realtimeScoreNotifier *Notifier + scorePostedNotifier *Notifier + audienceDisplayNotifier *Notifier + playSoundNotifier *Notifier + allianceStationDisplayNotifier *Notifier + allianceSelectionNotifier *Notifier + lowerThirdNotifier *Notifier + audienceDisplayScreen string + allianceStationDisplays map[string]string + allianceStationDisplayScreen string + lastMatchState int + lastMatchTimeSec float64 + savedMatch *Match + savedMatchResult *MatchResult } var mainArena Arena // Named thusly to avoid polluting the global namespace with something more generic. @@ -108,6 +113,9 @@ func (arena *Arena) Setup() { arena.scorePostedNotifier = NewNotifier() arena.audienceDisplayNotifier = NewNotifier() arena.playSoundNotifier = NewNotifier() + arena.allianceStationDisplayNotifier = NewNotifier() + arena.allianceSelectionNotifier = NewNotifier() + arena.lowerThirdNotifier = NewNotifier() // Load empty match as current. arena.MatchState = PRE_MATCH @@ -120,6 +128,7 @@ func (arena *Arena) Setup() { arena.savedMatch = &Match{} arena.savedMatchResult = &MatchResult{} arena.allianceStationDisplays = make(map[string]string) + arena.allianceStationDisplayScreen = "blank" } // Loads a team into an alliance station, cleaning up the previous team there if there is one. @@ -402,8 +411,12 @@ func (arena *Arena) Update() { auto = false enabled = false sendDsPacket = true - arena.audienceDisplayScreen = "blank" - arena.audienceDisplayNotifier.Notify(nil) + go func() { + // Leave the scores on the screen briefly at the end of the match. + time.Sleep(time.Second * matchEndScoreDwellSec) + arena.audienceDisplayScreen = "blank" + arena.audienceDisplayNotifier.Notify(nil) + }() arena.playSoundNotifier.Notify("match-end") } } diff --git a/database.go b/database.go index 7f7d6f8..2931279 100644 --- a/database.go +++ b/database.go @@ -23,6 +23,7 @@ type Database struct { rankingMap *modl.DbMap teamMap *modl.DbMap allianceTeamMap *modl.DbMap + lowerThirdMap *modl.DbMap } // Opens the SQLite database at the given path, creating it if it doesn't exist, and runs any pending @@ -75,4 +76,7 @@ func (database *Database) mapTables() { database.allianceTeamMap = modl.NewDbMap(database.db, dialect) database.allianceTeamMap.AddTableWithName(AllianceTeam{}, "alliance_teams").SetKeys(true, "Id") + + database.lowerThirdMap = modl.NewDbMap(database.db, dialect) + database.lowerThirdMap.AddTableWithName(LowerThird{}, "lower_thirds").SetKeys(true, "Id") } diff --git a/db/migrations/20140811222034_CreateLowerThirds.sql b/db/migrations/20140811222034_CreateLowerThirds.sql new file mode 100644 index 0000000..a5f6d72 --- /dev/null +++ b/db/migrations/20140811222034_CreateLowerThirds.sql @@ -0,0 +1,9 @@ +-- +goose Up +CREATE TABLE lower_thirds ( + id INTEGER PRIMARY KEY, + toptext VARCHAR(255), + bottomtext VARCHAR(255) +); + +-- +goose Down +DROP TABLE lower_thirds; diff --git a/displays.go b/displays.go index c992e33..868ad83 100644 --- a/displays.go +++ b/displays.go @@ -60,6 +60,10 @@ func AudienceDisplayWebsocketHandler(w http.ResponseWriter, r *http.Request) { defer close(scorePostedListener) playSoundListener := mainArena.playSoundNotifier.Listen() defer close(playSoundListener) + allianceSelectionListener := mainArena.allianceSelectionNotifier.Listen() + defer close(allianceSelectionListener) + lowerThirdListener := mainArena.lowerThirdNotifier.Listen() + defer close(lowerThirdListener) // Send the various notifications immediately upon connection. var data interface{} @@ -113,6 +117,11 @@ func AudienceDisplayWebsocketHandler(w http.ResponseWriter, r *http.Request) { log.Printf("Websocket error: %s", err) return } + err = websocket.Write("allianceSelection", cachedAlliances) + if err != nil { + log.Printf("Websocket error: %s", err) + return + } // Spin off a goroutine to listen for notifications and pass them on through the websocket. go func() { @@ -173,6 +182,18 @@ func AudienceDisplayWebsocketHandler(w http.ResponseWriter, r *http.Request) { } messageType = "playSound" message = sound + case _, ok := <-allianceSelectionListener: + if !ok { + return + } + messageType = "allianceSelection" + message = cachedAlliances + case lowerThird, ok := <-lowerThirdListener: + if !ok { + return + } + messageType = "lowerThird" + message = lowerThird } err = websocket.Write(messageType, message) if err != nil { @@ -772,9 +793,14 @@ func AllianceStationDisplayHandler(w http.ResponseWriter, r *http.Request) { return } + displayId := "" + if _, ok := r.URL.Query()["displayId"]; ok { + displayId = r.URL.Query()["displayId"][0] + } data := struct { *EventSettings - }{eventSettings} + DisplayId string + }{eventSettings, displayId} err = template.ExecuteTemplate(w, "alliance_station_display.html", data) if err != nil { handleWebErr(w, err) @@ -797,10 +823,13 @@ func AllianceStationDisplayWebsocketHandler(w http.ResponseWriter, r *http.Reque station = "" mainArena.allianceStationDisplays[displayId] = station } - defer delete(mainArena.allianceStationDisplays, displayId) + allianceStationDisplayListener := mainArena.allianceStationDisplayNotifier.Listen() + defer close(allianceStationDisplayListener) matchLoadTeamsListener := mainArena.matchLoadTeamsNotifier.Listen() defer close(matchLoadTeamsListener) + robotStatusListener := mainArena.robotStatusNotifier.Listen() + defer close(robotStatusListener) matchTimeListener := mainArena.matchTimeNotifier.Listen() defer close(matchTimeListener) realtimeScoreListener := mainArena.realtimeScoreNotifier.Listen() @@ -808,6 +837,11 @@ func AllianceStationDisplayWebsocketHandler(w http.ResponseWriter, r *http.Reque // Send the various notifications immediately upon connection. var data interface{} + err = websocket.Write("setAllianceStationDisplay", mainArena.allianceStationDisplayScreen) + if err != nil { + log.Printf("Websocket error: %s", err) + return + } err = websocket.Write("matchTiming", mainArena.matchTiming) if err != nil { log.Printf("Websocket error: %s", err) @@ -851,6 +885,12 @@ func AllianceStationDisplayWebsocketHandler(w http.ResponseWriter, r *http.Reque var messageType string var message interface{} select { + case _, ok := <-allianceStationDisplayListener: + if !ok { + return + } + messageType = "setAllianceStationDisplay" + message = mainArena.allianceStationDisplayScreen case _, ok := <-matchLoadTeamsListener: if !ok { return @@ -864,6 +904,12 @@ func AllianceStationDisplayWebsocketHandler(w http.ResponseWriter, r *http.Reque "R2": mainArena.AllianceStations["R2"].team, "R3": mainArena.AllianceStations["R3"].team, "B1": mainArena.AllianceStations["B1"].team, "B2": mainArena.AllianceStations["B2"].team, "B3": mainArena.AllianceStations["B3"].team}} + case _, ok := <-robotStatusListener: + if !ok { + return + } + messageType = "status" + message = mainArena case matchTimeSec, ok := <-matchTimeListener: if !ok { return diff --git a/lower_third.go b/lower_third.go new file mode 100644 index 0000000..f32876e --- /dev/null +++ b/lower_third.go @@ -0,0 +1,46 @@ +// Copyright 2014 Team 254. All Rights Reserved. +// Author: pat@patfairbank.com (Patrick Fairbank) +// +// Model and datastore CRUD methods for the text on a lower third slide. + +package main + +type LowerThird struct { + Id int + TopText string + BottomText string +} + +func (database *Database) CreateLowerThird(lowerThird *LowerThird) error { + return database.lowerThirdMap.Insert(lowerThird) +} + +func (database *Database) GetLowerThirdById(id int) (*LowerThird, error) { + lowerThird := new(LowerThird) + err := database.lowerThirdMap.Get(lowerThird, id) + if err != nil && err.Error() == "sql: no rows in result set" { + lowerThird = nil + err = nil + } + return lowerThird, err +} + +func (database *Database) SaveLowerThird(lowerThird *LowerThird) error { + _, err := database.lowerThirdMap.Update(lowerThird) + return err +} + +func (database *Database) DeleteLowerThird(lowerThird *LowerThird) error { + _, err := database.lowerThirdMap.Delete(lowerThird) + return err +} + +func (database *Database) TruncateLowerThirds() error { + return database.lowerThirdMap.TruncateTables() +} + +func (database *Database) GetAllLowerThirds() ([]LowerThird, error) { + var lowerThirds []LowerThird + err := database.teamMap.Select(&lowerThirds, "SELECT * FROM lower_thirds ORDER BY id") + return lowerThirds, err +} diff --git a/match_play.go b/match_play.go index be212df..12e17d9 100644 --- a/match_play.go +++ b/match_play.go @@ -161,6 +161,8 @@ func MatchPlayWebsocketHandler(w http.ResponseWriter, r *http.Request) { defer close(audienceDisplayListener) scoringStatusListener := mainArena.scoringStatusNotifier.Listen() defer close(scoringStatusListener) + allianceStationDisplayListener := mainArena.allianceStationDisplayNotifier.Listen() + defer close(allianceStationDisplayListener) // Send the various notifications immediately upon connection. var data interface{} @@ -196,6 +198,11 @@ func MatchPlayWebsocketHandler(w http.ResponseWriter, r *http.Request) { log.Printf("Websocket error: %s", err) return } + err = websocket.Write("setAllianceStationDisplay", mainArena.allianceStationDisplayScreen) + if err != nil { + log.Printf("Websocket error: %s", err) + return + } // Spin off a goroutine to listen for notifications and pass them on through the websocket. go func() { @@ -232,6 +239,12 @@ func MatchPlayWebsocketHandler(w http.ResponseWriter, r *http.Request) { BlueScoreReady bool }{mainArena.redRealtimeScore.FoulsCommitted && mainArena.blueRealtimeScore.FoulsCommitted, mainArena.redRealtimeScore.TeleopCommitted, mainArena.blueRealtimeScore.TeleopCommitted} + case _, ok := <-allianceStationDisplayListener: + if !ok { + return + } + messageType = "setAllianceStationDisplay" + message = mainArena.allianceStationDisplayScreen } err = websocket.Write(messageType, message) if err != nil { @@ -340,6 +353,15 @@ func MatchPlayWebsocketHandler(w http.ResponseWriter, r *http.Request) { mainArena.audienceDisplayScreen = screen mainArena.audienceDisplayNotifier.Notify(nil) continue + case "setAllianceStationDisplay": + screen, ok := data.(string) + if !ok { + websocket.WriteError(fmt.Sprintf("Failed to parse '%s' message.", messageType)) + continue + } + mainArena.allianceStationDisplayScreen = screen + mainArena.allianceStationDisplayNotifier.Notify(nil) + continue default: websocket.WriteError(fmt.Sprintf("Invalid message type '%s'.", messageType)) continue diff --git a/setup_alliance_selection.go b/setup_alliance_selection.go index 19822d4..59a0e8e 100644 --- a/setup_alliance_selection.go +++ b/setup_alliance_selection.go @@ -72,6 +72,8 @@ func AllianceSelectionPostHandler(w http.ResponseWriter, r *http.Request) { } } } + + mainArena.allianceSelectionNotifier.Notify(nil) http.Redirect(w, r, "/setup/alliance_selection", 302) } @@ -109,6 +111,8 @@ func AllianceSelectionStartHandler(w http.ResponseWriter, r *http.Request) { for i, ranking := range rankings { cachedRankedTeams[i] = &RankedTeam{i + 1, ranking.TeamId, false} } + + mainArena.allianceSelectionNotifier.Notify(nil) http.Redirect(w, r, "/setup/alliance_selection", 302) } @@ -121,6 +125,7 @@ func AllianceSelectionResetHandler(w http.ResponseWriter, r *http.Request) { cachedAlliances = [][]*AllianceTeam{} cachedRankedTeams = []*RankedTeam{} + mainArena.allianceSelectionNotifier.Notify(nil) http.Redirect(w, r, "/setup/alliance_selection", 302) } diff --git a/setup_lower_thirds.go b/setup_lower_thirds.go new file mode 100644 index 0000000..e77c214 --- /dev/null +++ b/setup_lower_thirds.go @@ -0,0 +1,78 @@ +// Copyright 2014 Team 254. All Rights Reserved. +// Author: pat@patfairbank.com (Patrick Fairbank) +// +// Web routes for managing lower thirds. + +package main + +import ( + "html/template" + "net/http" + "strconv" +) + +// Shows the lower third configuration page. +func LowerThirdsGetHandler(w http.ResponseWriter, r *http.Request) { + template, err := template.ParseFiles("templates/lower_thirds.html", "templates/base.html") + if err != nil { + handleWebErr(w, err) + return + } + lowerThirds, err := db.GetAllLowerThirds() + if err != nil { + handleWebErr(w, err) + return + } + data := struct { + *EventSettings + LowerThirds []LowerThird + }{eventSettings, lowerThirds} + 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 LowerThirdsPostHandler(w http.ResponseWriter, r *http.Request) { + lowerThirdId, _ := strconv.Atoi(r.PostFormValue("id")) + lowerThird, err := db.GetLowerThirdById(lowerThirdId) + if err != nil { + handleWebErr(w, err) + return + } + if r.PostFormValue("action") == "delete" { + err := db.DeleteLowerThird(lowerThird) + if err != nil { + handleWebErr(w, err) + return + } + } else { + // Save the lower third even if the show or hide buttons were clicked. + if lowerThird == nil { + lowerThird = &LowerThird{TopText: r.PostFormValue("topText"), + BottomText: r.PostFormValue("bottomText")} + err = db.CreateLowerThird(lowerThird) + } else { + lowerThird.TopText = r.PostFormValue("topText") + lowerThird.BottomText = r.PostFormValue("bottomText") + err = db.SaveLowerThird(lowerThird) + } + if err != nil { + handleWebErr(w, err) + return + } + + if r.PostFormValue("action") == "show" { + mainArena.lowerThirdNotifier.Notify(lowerThird) + mainArena.audienceDisplayScreen = "lowerThird" + mainArena.audienceDisplayNotifier.Notify(nil) + } else if r.PostFormValue("action") == "hide" { + mainArena.audienceDisplayScreen = "blank" + mainArena.audienceDisplayNotifier.Notify(nil) + } + } + + http.Redirect(w, r, "/setup/lower_thirds", 302) +} diff --git a/static/css/alliance_station_display.css b/static/css/alliance_station_display.css index 75702aa..a45a9af 100644 --- a/static/css/alliance_station_display.css +++ b/static/css/alliance_station_display.css @@ -8,28 +8,60 @@ body { background-color: #000; font-family: "FuturaLTBold"; } +#match { + position: absolute; + width: 100%; + height: 100%; +} #displayId { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + margin: auto auto; + height: 1px; + line-height: 1px; + text-align: center; color: #ff0; font-size: 500px; } #teamId { + position: absolute; + top: 7%; + left: 0; + right: 0; + margin: 0 auto; font-size: 500px; line-height: 500px; + text-align: center; margin: 50px 0px; + color: #fff; } #teamName { + position: absolute; + bottom: 0; + left: 0; + right: 0; + margin: 0 auto; + height: 200px; + line-height: 200px; + text-align: center; font-family: "FuturaLT"; font-size: 120px; -} -[data-alliance=R] { - color: #f00; -} -[data-alliance=B] { - color: #00f; -} -.match-info { color: #fff; +} +#matchInfo { + position: absolute; + bottom: 0; + left: 0; + right: 0; + margin: 0 auto; + height: 200px; + line-height: 200px; + text-align: center; font-size: 190px; + color: #fff; } #redScore { color: #f00; @@ -37,3 +69,21 @@ body { #blueScore { color: #00f; } +#logo { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + width: 90%; + margin: auto auto; +} +[data-alliance=R], [data-status=R] { + background-color: #f00; +} +[data-alliance=B], [data-status=B] { + background-color: #00f; +} +[data-status=bypass] { + background-color: #999; +} diff --git a/static/css/audience_display.css b/static/css/audience_display.css index a21e048..e5d7e6a 100644 --- a/static/css/audience_display.css +++ b/static/css/audience_display.css @@ -5,7 +5,8 @@ html { overflow: hidden; } #centering { - position: absolute; left: 50%; + position: absolute; + left: 50%; bottom: -340px; } #matchOverlay { @@ -275,3 +276,61 @@ html { font-size: 28px; color: #fff; } +#allianceSelectionCentering { + position: absolute; + left: 50%; + height: 100%; + display: table; +} +#allianceSelection { + position: relative; + left: -50%; + display: table-cell; + vertical-align: middle; +} +#allianceSelectionTable { + background-color: #006; + border-radius: 20px; + text-align: center; + font-family: "FuturaLTBold"; + font-size: 70px; +} +.alliance-cell { + padding: 0px 40px; + font-family: "FuturaLT"; + color: #999; +} +.selection-cell { + width: 250px; + color: #fff; +} +#lowerThird { + display: none; + position: absolute; + left: -1000px; + bottom: 100px; + background-color: #fff; + border: 1px solid #000; + color: #000; + font-size: 30px; + width: 800px; + height: 87px; +} +#lowerThirdLogo { + margin: 20px; + height: 45px; + float: left; +} +#lowerThirdTop { + display: none; + font-family: "FuturaLTBold"; +} +#lowerThirdBottom { + display: none; + font-family: "FuturaLT"; +} +#lowerThirdSingle { + display: none; + font-family: "FuturaLTBold"; + line-height: 87px; +} \ No newline at end of file diff --git a/static/css/cheesy-arena.css b/static/css/cheesy-arena.css index f2d5b46..b4ff139 100644 --- a/static/css/cheesy-arena.css +++ b/static/css/cheesy-arena.css @@ -88,3 +88,6 @@ .scoring-message { color: #f00; } +.btn-lower-third { + width: 80px; +} diff --git a/static/css/pit_display.css b/static/css/pit_display.css index ec4aec2..e27a71f 100644 --- a/static/css/pit_display.css +++ b/static/css/pit_display.css @@ -15,7 +15,7 @@ body { background: -webkit-linear-gradient(top, #003375 1%, #3C679D 100%); /* Chrome10+,Safari5.1+ */ background: -webkit-linear-gradient(top, #003375 1%, #3C679D 100%); /* Chrome10+,Safari5.1+ */ background-repeat: no-repeat; - font-family: sans-serif; + font-family: "FuturaLT"; } #column { width: 80%; @@ -26,7 +26,7 @@ body { padding: 20px 0px; line-height: 50px; font-size: 40px; - font-weight: bold; + font-family: "FuturaLTBold"; color: #fff; } #logo { diff --git a/static/img/logo-black.png b/static/img/logo-black.png new file mode 100644 index 0000000..d641498 Binary files /dev/null and b/static/img/logo-black.png differ diff --git a/static/img/logo-colored.png b/static/img/logo-colored.png new file mode 100644 index 0000000..f3f85f7 Binary files /dev/null and b/static/img/logo-colored.png differ diff --git a/static/js/alliance_station_display.js b/static/js/alliance_station_display.js index b3548d8..4d8a2b0 100644 --- a/static/js/alliance_station_display.js +++ b/static/js/alliance_station_display.js @@ -3,11 +3,27 @@ // // Client-side methods for the alliance station display. -// A unique id to differentiate this station's display from its peers. -var displayId; var allianceStation = ""; +var blinkInterval; var websocket; +var handleSetAllianceStationDisplay = function(targetScreen) { + switch (targetScreen) { + case "logo": + $("#match").hide(); + $("#logo").show(); + break; + case "blank": + $("#match").hide(); + $("#logo").hide(); + break; + case "match": + $("#match").show(); + $("#logo").hide(); + break; + } +}; + var handleSetMatch = function(data) { if (allianceStation != "" && data.AllianceStation == "") { // The client knows better what display this should be; let the server know. @@ -22,22 +38,49 @@ var handleSetMatch = function(data) { if (team == null) { $("#teamId").text(""); $("#teamName").text(""); + $("#teamName").attr("data-alliance", ""); } else { - $("#teamId").attr("data-alliance", allianceStation[0]); $("#teamName").attr("data-alliance", allianceStation[0]); $("#teamId").text(data.Teams[allianceStation].Id); $("#teamName").text(data.Teams[allianceStation].Nickname); } - $("#displayIdRow").hide(); - $("#teamIdRow").show(); - $("#teamNameRow").show(); + $("#displayId").hide(); + $("#teamId").show(); + $("#teamName").show(); } else { // Show the display ID so that someone can assign it to a station from the configuration interface. $("#teamId").text(""); $("#teamName").text(""); - $("#displayIdRow").show(); - $("#teamIdRow").hide(); - $("#teamNameRow").hide(); + $("#displayId").show(); + $("#teamId").hide(); + $("#teamName").hide(); + } +}; + +var handleStatus = function(data) { + stationStatus = data.AllianceStations[allianceStation]; + var blink = false; + if (stationStatus.Bypass) { + $("#match").attr("data-status", "bypass"); + } else if (stationStatus.DsConn) { + if (!stationStatus.DsConn.DriverStationStatus.DsLinked) { + $("#match").attr("data-status", allianceStation[0]); + } else if (!stationStatus.DsConn.DriverStationStatus.RobotLinked) { + blink = true; + if (!blinkInterval) { + blinkInterval = setInterval(function() { + var status = $("#match").attr("data-status"); + $("#match").attr("data-status", (status == "") ? allianceStation[0] : ""); + }, 250); + } + } + } else { + $("#match").attr("data-status", ""); + } + + if (!blink && blinkInterval) { + clearInterval(blinkInterval); + blinkInterval = null; } }; @@ -51,11 +94,11 @@ var handleMatchTime = function(data) { $("#matchTime").text(countdownString); if (matchState == "PRE_MATCH" || matchState == "POST_MATCH") { - $("#teamNameRow").show(); - $("#matchInfoRow").hide(); + $("#teamName").show(); + $("#matchInfo").hide(); } else { - $("#teamNameRow").hide(); - $("#matchInfoRow").show(); + $("#teamName").hide(); + $("#matchInfo").show(); } }); }; @@ -66,12 +109,17 @@ var handleRealtimeScore = function(data) { }; $(function() { - displayId = Math.floor(Math.random() * 10000); + if (displayId == "") { + displayId = Math.floor(Math.random() * 10000); + window.location = "/displays/alliance_station?displayId=" + displayId; + } $("#displayId").text(displayId); // Set up the websocket back to the server. websocket = new CheesyWebsocket("/displays/alliance_station/websocket?displayId=" + displayId, { + setAllianceStationDisplay: function(event) { handleSetAllianceStationDisplay(event.data); }, setMatch: function(event) { handleSetMatch(event.data); }, + status: function(event) { handleStatus(event.data); }, matchTiming: function(event) { handleMatchTiming(event.data); }, matchTime: function(event) { handleMatchTime(event.data); }, realtimeScore: function(event) { handleRealtimeScore(event.data); } diff --git a/static/js/audience_display.js b/static/js/audience_display.js index 0433b01..4679fd7 100644 --- a/static/js/audience_display.js +++ b/static/js/audience_display.js @@ -6,6 +6,7 @@ var websocket; var transitionMap; var currentScreen = "blank"; +var allianceSelectionTemplate = Handlebars.compile($("#allianceSelectionTemplate").html()); var handleSetAudienceDisplay = function(targetScreen) { if (targetScreen == currentScreen) { @@ -87,6 +88,30 @@ var handlePlaySound = function(sound) { $("#" + sound)[0].play(); }; +var handleAllianceSelection = function(alliances) { + if (alliances) { + $.each(alliances, function(k, v) { + v.Index = k + 1; + }); + $("#allianceSelection").html(allianceSelectionTemplate(alliances)); + } +}; + +var handleLowerThird = function(data) { + if (data.BottomText == "") { + $("#lowerThirdTop").hide(); + $("#lowerThirdBottom").hide(); + $("#lowerThirdSingle").text(data.TopText); + $("#lowerThirdSingle").show(); + } else { + $("#lowerThirdSingle").hide(); + $("#lowerThirdTop").text(data.TopText); + $("#lowerThirdBottom").text(data.BottomText); + $("#lowerThirdTop").show(); + $("#lowerThirdBottom").show(); + } +}; + var transitionBlankToIntro = function(callback) { $("#centering").transition({queue: false, bottom: "0px"}, 500, "ease", function() { $(".teams").transition({queue: false, width: "75px"}, 100, "linear", function() { @@ -209,6 +234,34 @@ var transitionScoreToBlank = function(callback) { }); } +var transitionBlankToAllianceSelection = function(callback) { + $("#allianceSelectionCentering").show(); + if (callback) { + callback(); + } +}; + +var transitionAllianceSelectionToBlank = function(callback) { + $("#allianceSelectionCentering").hide(); + if (callback) { + callback(); + } +}; + +var transitionBlankToLowerThird = function(callback) { + $("#lowerThird").show(); + $("#lowerThird").transition({queue: false, left: "150px"}, 750, "ease", callback); +}; + +var transitionLowerThirdToBlank = function(callback) { + $("#lowerThird").transition({queue: false, left: "-1000px"}, 1000, "ease", function() { + $("#lowerThird").hide(); + if (callback) { + callback(); + } + }); +}; + $(function() { // Set up the websocket back to the server. websocket = new CheesyWebsocket("/displays/audience/websocket", { @@ -218,7 +271,9 @@ $(function() { matchTime: function(event) { handleMatchTime(event.data); }, realtimeScore: function(event) { handleRealtimeScore(event.data); }, setFinalScore: function(event) { handleSetFinalScore(event.data); }, - playSound: function(event) { handlePlaySound(event.data); } + playSound: function(event) { handlePlaySound(event.data); }, + allianceSelection: function(event) { handleAllianceSelection(event.data); }, + lowerThird: function(event) { handleLowerThird(event.data); } }); // Map how to transition from one screen to another. Missing links between screens indicate that first we @@ -228,7 +283,9 @@ $(function() { intro: transitionBlankToIntro, match: transitionBlankToInMatch, score: transitionBlankToScore, - logo: transitionBlankToLogo + logo: transitionBlankToLogo, + allianceSelection: transitionBlankToAllianceSelection, + lowerThird: transitionBlankToLowerThird }, intro: { blank: transitionIntroToBlank, @@ -245,6 +302,12 @@ $(function() { logo: { blank: transitionLogoToBlank, score: transitionLogoToScore + }, + allianceSelection: { + blank: transitionAllianceSelectionToBlank + }, + lowerThird: { + blank: transitionLowerThirdToBlank } } }); diff --git a/static/js/match_play.js b/static/js/match_play.js index cd5a309..d4a9e04 100644 --- a/static/js/match_play.js +++ b/static/js/match_play.js @@ -34,6 +34,10 @@ var setAudienceDisplay = function() { websocket.send("setAudienceDisplay", $("input[name=audienceDisplay]:checked").val()); }; +var setAllianceStationDisplay = function() { + websocket.send("setAllianceStationDisplay", $("input[name=allianceStationDisplay]:checked").val()); +}; + var confirmCommit = function(isReplay) { if (isReplay || !scoreIsReady) { // Show the appropriate message(s) in the confirmation dialog. @@ -108,6 +112,7 @@ var handleMatchTime = function(data) { }; var handleSetAudienceDisplay = function(data) { + $("input[name=audienceDisplay]:checked").prop("checked", false); $("input[name=audienceDisplay][value=" + data + "]").prop("checked", true); }; @@ -118,6 +123,11 @@ var handleScoringStatus = function(data) { $("#blueScoreStatus").attr("data-ready", data.BlueScoreReady); }; +var handleSetAllianceStationDisplay = function(data) { + $("input[name=allianceStationDisplay]:checked").prop("checked", false); + $("input[name=allianceStationDisplay][value=" + data + "]").prop("checked", true); +}; + $(function() { // Activate tooltips above the status headers. $("[data-toggle=tooltip]").tooltip({"placement": "top"}); @@ -128,6 +138,7 @@ $(function() { matchTiming: function(event) { handleMatchTiming(event.data); }, matchTime: function(event) { handleMatchTime(event.data); }, setAudienceDisplay: function(event) { handleSetAudienceDisplay(event.data); }, - scoringStatus: function(event) { handleScoringStatus(event.data); } + scoringStatus: function(event) { handleScoringStatus(event.data); }, + setAllianceStationDisplay: function(event) { handleSetAllianceStationDisplay(event.data); } }); }); diff --git a/templates/alliance_station_display.html b/templates/alliance_station_display.html index 26aafe0..1afabae 100644 --- a/templates/alliance_station_display.html +++ b/templates/alliance_station_display.html @@ -8,20 +8,21 @@ - - - -
- +
@@ -59,7 +59,7 @@
- +
@@ -98,6 +98,27 @@
+ +
+ +
+
+
+
+