mirror of
https://github.com/Team254/cheesy-arena-lite.git
synced 2026-03-09 13:46:44 -04:00
Added websocket control of lower thirds to improve ease of use.
This commit is contained in:
@@ -2,7 +2,8 @@
|
||||
CREATE TABLE lower_thirds (
|
||||
id INTEGER PRIMARY KEY,
|
||||
toptext VARCHAR(255),
|
||||
bottomtext VARCHAR(255)
|
||||
bottomtext VARCHAR(255),
|
||||
displayorder int
|
||||
);
|
||||
|
||||
-- +goose Down
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
package main
|
||||
|
||||
type LowerThird struct {
|
||||
Id int
|
||||
TopText string
|
||||
BottomText string
|
||||
Id int
|
||||
TopText string
|
||||
BottomText string
|
||||
DisplayOrder int
|
||||
}
|
||||
|
||||
func (database *Database) CreateLowerThird(lowerThird *LowerThird) error {
|
||||
@@ -41,6 +42,6 @@ func (database *Database) TruncateLowerThirds() error {
|
||||
|
||||
func (database *Database) GetAllLowerThirds() ([]LowerThird, error) {
|
||||
var lowerThirds []LowerThird
|
||||
err := database.teamMap.Select(&lowerThirds, "SELECT * FROM lower_thirds ORDER BY id")
|
||||
err := database.lowerThirdMap.Select(&lowerThirds, "SELECT * FROM lower_thirds ORDER BY displayorder")
|
||||
return lowerThirds, err
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ func TestLowerThirdCrud(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
defer db.Close()
|
||||
|
||||
lowerThird := LowerThird{0, "Top Text", "Bottom Text"}
|
||||
lowerThird := LowerThird{0, "Top Text", "Bottom Text", 0}
|
||||
db.CreateLowerThird(&lowerThird)
|
||||
lowerThird2, err := db.GetLowerThirdById(1)
|
||||
assert.Nil(t, err)
|
||||
@@ -52,7 +52,7 @@ func TestTruncateLowerThirds(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
defer db.Close()
|
||||
|
||||
lowerThird := LowerThird{0, "Top Text", "Bottom Text"}
|
||||
lowerThird := LowerThird{0, "Top Text", "Bottom Text", 0}
|
||||
db.CreateLowerThird(&lowerThird)
|
||||
db.TruncateLowerThirds()
|
||||
lowerThird2, err := db.GetLowerThirdById(1)
|
||||
|
||||
@@ -6,9 +6,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"html/template"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Shows the lower third configuration page.
|
||||
@@ -34,45 +37,161 @@ func LowerThirdsGetHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
// The websocket endpoint for the lower thirds client to send control commands.
|
||||
func LowerThirdsWebsocketHandler(w http.ResponseWriter, r *http.Request) {
|
||||
websocket, err := NewWebsocket(w, r)
|
||||
if err != nil {
|
||||
handleWebErr(w, err)
|
||||
return
|
||||
}
|
||||
if r.PostFormValue("action") == "delete" {
|
||||
err := db.DeleteLowerThird(lowerThird)
|
||||
defer websocket.Close()
|
||||
|
||||
// Loop, waiting for commands and responding to them, until the client closes the connection.
|
||||
for {
|
||||
messageType, data, err := websocket.Read()
|
||||
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)
|
||||
if err == io.EOF {
|
||||
// Client has closed the connection; nothing to do here.
|
||||
return
|
||||
}
|
||||
log.Printf("Websocket error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if r.PostFormValue("action") == "show" {
|
||||
switch messageType {
|
||||
case "saveLowerThird":
|
||||
var lowerThird LowerThird
|
||||
err = mapstructure.Decode(data, &lowerThird)
|
||||
if err != nil {
|
||||
websocket.WriteError(err.Error())
|
||||
continue
|
||||
}
|
||||
saveLowerThird(&lowerThird)
|
||||
case "deleteLowerThird":
|
||||
var lowerThird LowerThird
|
||||
err = mapstructure.Decode(data, &lowerThird)
|
||||
if err != nil {
|
||||
websocket.WriteError(err.Error())
|
||||
continue
|
||||
}
|
||||
err = db.DeleteLowerThird(&lowerThird)
|
||||
if err != nil {
|
||||
websocket.WriteError(err.Error())
|
||||
continue
|
||||
}
|
||||
case "showLowerThird":
|
||||
var lowerThird LowerThird
|
||||
err = mapstructure.Decode(data, &lowerThird)
|
||||
if err != nil {
|
||||
websocket.WriteError(err.Error())
|
||||
continue
|
||||
}
|
||||
saveLowerThird(&lowerThird)
|
||||
mainArena.lowerThirdNotifier.Notify(lowerThird)
|
||||
mainArena.audienceDisplayScreen = "lowerThird"
|
||||
mainArena.audienceDisplayNotifier.Notify(nil)
|
||||
} else if r.PostFormValue("action") == "hide" {
|
||||
continue
|
||||
case "hideLowerThird":
|
||||
var lowerThird LowerThird
|
||||
err = mapstructure.Decode(data, &lowerThird)
|
||||
if err != nil {
|
||||
websocket.WriteError(err.Error())
|
||||
continue
|
||||
}
|
||||
saveLowerThird(&lowerThird)
|
||||
mainArena.audienceDisplayScreen = "blank"
|
||||
mainArena.audienceDisplayNotifier.Notify(nil)
|
||||
continue
|
||||
case "reorderLowerThird":
|
||||
args := struct {
|
||||
Id int
|
||||
MoveUp bool
|
||||
}{}
|
||||
err = mapstructure.Decode(data, &args)
|
||||
if err != nil {
|
||||
websocket.WriteError(err.Error())
|
||||
continue
|
||||
}
|
||||
err = reorderLowerThird(args.Id, args.MoveUp)
|
||||
if err != nil {
|
||||
websocket.WriteError(err.Error())
|
||||
continue
|
||||
}
|
||||
default:
|
||||
websocket.WriteError(fmt.Sprintf("Invalid message type '%s'.", messageType))
|
||||
continue
|
||||
}
|
||||
|
||||
// Force a reload of the client to render the updated lower thirds list.
|
||||
err = websocket.Write("reload", nil)
|
||||
if err != nil {
|
||||
log.Printf("Websocket error: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/setup/lower_thirds", 302)
|
||||
}
|
||||
|
||||
func saveLowerThird(lowerThird *LowerThird) error {
|
||||
oldLowerThird, err := db.GetLowerThirdById(lowerThird.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create or update lower third.
|
||||
if oldLowerThird == nil {
|
||||
err = db.CreateLowerThird(lowerThird)
|
||||
} else {
|
||||
err = db.SaveLowerThird(lowerThird)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func reorderLowerThird(id int, moveUp bool) error {
|
||||
lowerThird, err := db.GetLowerThirdById(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the lower third to swap positions with.
|
||||
lowerThirds, err := db.GetAllLowerThirds()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var lowerThirdIndex int
|
||||
for i, third := range lowerThirds {
|
||||
if third.Id == lowerThird.Id {
|
||||
lowerThirdIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if moveUp {
|
||||
lowerThirdIndex--
|
||||
} else {
|
||||
lowerThirdIndex++
|
||||
}
|
||||
if lowerThirdIndex < 0 || lowerThirdIndex == len(lowerThirds) {
|
||||
// The one to move is already at the limit; return an error to prevent a page reload.
|
||||
return fmt.Errorf("Already at the limit.")
|
||||
}
|
||||
adjacentLowerThird, err := db.GetLowerThirdById(lowerThirds[lowerThirdIndex].Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Swap their display orders and save.
|
||||
lowerThird.DisplayOrder, adjacentLowerThird.DisplayOrder =
|
||||
adjacentLowerThird.DisplayOrder, lowerThird.DisplayOrder
|
||||
err = db.SaveLowerThird(lowerThird)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = db.SaveLowerThird(adjacentLowerThird)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestSetupLowerThirds(t *testing.T) {
|
||||
@@ -18,38 +20,48 @@ func TestSetupLowerThirds(t *testing.T) {
|
||||
eventSettings, _ = db.GetEventSettings()
|
||||
mainArena.Setup()
|
||||
|
||||
db.CreateLowerThird(&LowerThird{0, "Top Text 1", "Bottom Text 1"})
|
||||
db.CreateLowerThird(&LowerThird{0, "Top Text 2", "Bottom Text 2"})
|
||||
db.CreateLowerThird(&LowerThird{0, "Top Text 1", "Bottom Text 1", 0})
|
||||
db.CreateLowerThird(&LowerThird{0, "Top Text 2", "Bottom Text 2", 1})
|
||||
db.CreateLowerThird(&LowerThird{0, "Top Text 3", "Bottom Text 3", 2})
|
||||
|
||||
recorder := getHttpResponse("/setup/lower_thirds")
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
assert.Contains(t, recorder.Body.String(), "Top Text 1")
|
||||
assert.Contains(t, recorder.Body.String(), "Bottom Text 2")
|
||||
|
||||
recorder = postHttpResponse("/setup/lower_thirds", "action=delete&id=1")
|
||||
assert.Equal(t, 302, recorder.Code)
|
||||
recorder = getHttpResponse("/setup/lower_thirds")
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
assert.NotContains(t, recorder.Body.String(), "Top Text 1")
|
||||
assert.Contains(t, recorder.Body.String(), "Bottom Text 2")
|
||||
server, wsUrl := startTestServer()
|
||||
defer server.Close()
|
||||
conn, _, err := websocket.DefaultDialer.Dial(wsUrl+"/setup/lower_thirds/websocket", nil)
|
||||
assert.Nil(t, err)
|
||||
defer conn.Close()
|
||||
ws := &Websocket{conn}
|
||||
|
||||
recorder = postHttpResponse("/setup/lower_thirds", "action=save&topText=Text 3&bottomText=")
|
||||
assert.Equal(t, 302, recorder.Code)
|
||||
recorder = getHttpResponse("/setup/lower_thirds")
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
assert.Contains(t, recorder.Body.String(), "Text 3")
|
||||
lowerThird, _ := db.GetLowerThirdById(3)
|
||||
assert.NotNil(t, lowerThird)
|
||||
ws.Write("saveLowerThird", LowerThird{1, "Top Text 4", "Bottom Text 1", 0})
|
||||
time.Sleep(time.Millisecond * 10) // Allow some time for the command to be processed.
|
||||
lowerThird, _ := db.GetLowerThirdById(1)
|
||||
assert.Equal(t, "Top Text 4", lowerThird.TopText)
|
||||
|
||||
recorder = postHttpResponse("/setup/lower_thirds", "action=show&topText=Text 4&bottomText=&id=3")
|
||||
assert.Equal(t, 302, recorder.Code)
|
||||
recorder = getHttpResponse("/setup/lower_thirds")
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
assert.Contains(t, recorder.Body.String(), "Text 4")
|
||||
lowerThird, _ = db.GetLowerThirdById(3)
|
||||
assert.Equal(t, "Text 4", lowerThird.TopText)
|
||||
assert.Equal(t, "", lowerThird.BottomText)
|
||||
ws.Write("deleteLowerThird", LowerThird{1, "Top Text 4", "Bottom Text 1", 0})
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
lowerThird, _ = db.GetLowerThirdById(1)
|
||||
assert.Nil(t, lowerThird)
|
||||
|
||||
recorder = postHttpResponse("/setup/lower_thirds", "action=hide&id=3")
|
||||
assert.Equal(t, 302, recorder.Code)
|
||||
assert.Equal(t, "blank", mainArena.audienceDisplayScreen)
|
||||
ws.Write("showLowerThird", LowerThird{2, "Top Text 5", "Bottom Text 1", 0})
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
lowerThird, _ = db.GetLowerThirdById(2)
|
||||
assert.Equal(t, "Top Text 5", lowerThird.TopText)
|
||||
assert.Equal(t, "lowerThird", mainArena.audienceDisplayScreen)
|
||||
|
||||
ws.Write("hideLowerThird", LowerThird{2, "Top Text 6", "Bottom Text 1", 0})
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
lowerThird, _ = db.GetLowerThirdById(2)
|
||||
assert.Equal(t, "Top Text 6", lowerThird.TopText)
|
||||
assert.Equal(t, "blank", mainArena.audienceDisplayScreen)
|
||||
|
||||
ws.Write("reorderLowerThird", map[string]interface{}{"Id": 2, "moveUp": false})
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
lowerThirds, _ := db.GetAllLowerThirds()
|
||||
assert.Equal(t, 3, lowerThirds[0].Id)
|
||||
assert.Equal(t, 2, lowerThirds[1].Id)
|
||||
}
|
||||
|
||||
@@ -370,12 +370,17 @@ html {
|
||||
float: left;
|
||||
}
|
||||
#lowerThirdTop {
|
||||
position: relative;
|
||||
top: 10px;
|
||||
display: none;
|
||||
font-family: "FuturaLTBold";
|
||||
}
|
||||
#lowerThirdBottom {
|
||||
display: none;
|
||||
font-family: "FuturaLT";
|
||||
font-size: 23px;
|
||||
position: relative;
|
||||
top: 5px;
|
||||
}
|
||||
#lowerThirdSingle {
|
||||
display: none;
|
||||
|
||||
43
static/js/lower_thirds.js
Normal file
43
static/js/lower_thirds.js
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2015 Team 254. All Rights Reserved.
|
||||
// Author: pat@patfairbank.com (Patrick Fairbank)
|
||||
//
|
||||
// Client-side logic for the lower thirds management interface.
|
||||
|
||||
var websocket;
|
||||
|
||||
// Sends a websocket message to save the text for the given lower third.
|
||||
var saveLowerThird = function(button) {
|
||||
console.log(button.form.topText.value);
|
||||
websocket.send("saveLowerThird", constructLowerThird(button));
|
||||
};
|
||||
|
||||
// Sends a websocket message to delete the given lower third.
|
||||
var deleteLowerThird = function(button) {
|
||||
websocket.send("deleteLowerThird", constructLowerThird(button));
|
||||
};
|
||||
|
||||
// Sends a websocket message to show the given lower third.
|
||||
var showLowerThird = function(button) {
|
||||
websocket.send("showLowerThird", constructLowerThird(button));
|
||||
};
|
||||
|
||||
// Sends a websocket message to hide the lower third.
|
||||
var hideLowerThird = function(button) {
|
||||
websocket.send("hideLowerThird", constructLowerThird(button));
|
||||
};
|
||||
|
||||
// Sends a websocket message to reorder the given the lower third.
|
||||
var reorderLowerThird = function(button, moveUp) {
|
||||
websocket.send("reorderLowerThird", { Id: parseInt(button.form.id.value), MoveUp: moveUp })
|
||||
};
|
||||
|
||||
// Gathers the lower third info and constructs a JSON object.
|
||||
var constructLowerThird = function(button) {
|
||||
return { Id: parseInt(button.form.id.value), TopText: button.form.topText.value,
|
||||
BottomText: button.form.bottomText.value, DisplayOrder: parseInt(button.form.displayOrder.value) }
|
||||
}
|
||||
|
||||
$(function() {
|
||||
// Set up the websocket back to the server.
|
||||
websocket = new CheesyWebsocket("/setup/lower_thirds/websocket", {});
|
||||
});
|
||||
@@ -10,34 +10,53 @@
|
||||
<div class="col-lg-6 col-lg-offset-3">
|
||||
<div class="well">
|
||||
<legend>Lower Thirds</legend>
|
||||
{{range $lowerThird := .LowerThirds}}
|
||||
<form class="form-horizontal" action="/setup/lower_thirds" method="POST">
|
||||
{{range $i, $lowerThird := .LowerThirds}}
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<div class="col-lg-7">
|
||||
<div class="col-lg-6">
|
||||
<input type="hidden" name="id" value="{{$lowerThird.Id}}" />
|
||||
<input type="text" class="form-control" name="topText" value="{{$lowerThird.TopText}}"
|
||||
placeholder="Top Text"/>
|
||||
<input type="text" class="form-control" name="bottomText" value="{{$lowerThird.BottomText}}"
|
||||
placeholder="Bottom Text"/>
|
||||
<input type="hidden" name="displayOrder" value="{{$i}}" />
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<button type="submit" class="btn btn-info btn-lower-third" name="action" value="save">Save</button>
|
||||
<button type="submit" class="btn btn-success btn-lower-third" name="action" value="show">Show</button>
|
||||
<div class="col-lg-6">
|
||||
<button type="button" class="btn btn-info btn-lower-third" onclick="saveLowerThird(this);">
|
||||
Save
|
||||
</button>
|
||||
<button type="button" class="btn btn-success btn-lower-third" onclick="showLowerThird(this);">
|
||||
Show
|
||||
</button>
|
||||
<button type="button" class="btn btn-info" onclick="reorderLowerThird(this, true);">
|
||||
<i class="glyphicon glyphicon-arrow-up"></i>
|
||||
</button>
|
||||
<br />
|
||||
<button type="submit" class="btn btn-primary btn-lower-third" name="action" value="delete">Delete</button>
|
||||
<button type="submit" class="btn btn-default btn-lower-third" name="action" value="hide">Hide</button>
|
||||
<button type="button" class="btn btn-primary btn-lower-third" onclick="deleteLowerThird(this);">
|
||||
Delete
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-lower-third" onclick="hideLowerThird(this);">
|
||||
Hide
|
||||
</button>
|
||||
<button type="button" class="btn btn-info" onclick="reorderLowerThird(this, false);">
|
||||
<i class="glyphicon glyphicon-arrow-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{{end}}
|
||||
<form class="form-horizontal" action="/setup/lower_thirds" method="POST">
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<div class="col-lg-7">
|
||||
<div class="col-lg-6">
|
||||
<input type="hidden" name="id" value="0" />
|
||||
<input type="text" class="form-control" name="topText" placeholder="Top or Solo Text" />
|
||||
<input type="text" class="form-control" name="bottomText" placeholder="Bottom Text" />
|
||||
<input type="hidden" name="displayOrder" value="{{len .LowerThirds}}" />
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<button type="submit" class="btn btn-info btn-lower-third" name="action" value="save">Save</button>
|
||||
<div class="col-lg-6">
|
||||
<button type="button" class="btn btn-info btn-lower-third" name="save" onclick="saveLowerThird(this);">
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -46,4 +65,5 @@
|
||||
</div>
|
||||
{{end}}
|
||||
{{define "script"}}
|
||||
<script src="/static/js/lower_thirds.js"></script>
|
||||
{{end}}
|
||||
|
||||
2
web.go
2
web.go
@@ -134,7 +134,7 @@ func newHandler() http.Handler {
|
||||
router.HandleFunc("/setup/field/reload_displays", FieldReloadDisplaysHandler).Methods("GET")
|
||||
router.HandleFunc("/setup/field/lights", FieldLightsPostHandler).Methods("POST")
|
||||
router.HandleFunc("/setup/lower_thirds", LowerThirdsGetHandler).Methods("GET")
|
||||
router.HandleFunc("/setup/lower_thirds", LowerThirdsPostHandler).Methods("POST")
|
||||
router.HandleFunc("/setup/lower_thirds/websocket", LowerThirdsWebsocketHandler).Methods("GET")
|
||||
router.HandleFunc("/setup/sponsor_slides", SponsorSlidesGetHandler).Methods("GET")
|
||||
router.HandleFunc("/setup/sponsor_slides", SponsorSlidesPostHandler).Methods("POST")
|
||||
router.HandleFunc("/api/sponsor_slides", SponsorSlidesApiHandler).Methods("GET")
|
||||
|
||||
Reference in New Issue
Block a user