Merge branch 'master' into room-access-restrictions
# Conflicts: # main.go
This commit is contained in:
commit
34d8fbf20c
35
chatroom.go
35
chatroom.go
|
@ -30,6 +30,8 @@ type ChatRoom struct {
|
|||
|
||||
modPasswords []string // single-use mod passwords
|
||||
modPasswordsMtx sync.Mutex
|
||||
|
||||
isShutdown bool
|
||||
}
|
||||
|
||||
//initializing the chatroom
|
||||
|
@ -53,6 +55,11 @@ func newChatRoom() (*ChatRoom, error) {
|
|||
}
|
||||
|
||||
func (cr *ChatRoom) JoinTemp(conn *chatConnection) (string, error) {
|
||||
// Don't allow new joins when the server is closing.
|
||||
if cr.isShutdown {
|
||||
return "", fmt.Errorf("Server is shutting down")
|
||||
}
|
||||
|
||||
defer cr.clientsMtx.Unlock()
|
||||
cr.clientsMtx.Lock()
|
||||
|
||||
|
@ -77,6 +84,11 @@ func (cr *ChatRoom) JoinTemp(conn *chatConnection) (string, error) {
|
|||
//registering a new client
|
||||
//returns pointer to a Client, or Nil, if the name is already taken
|
||||
func (cr *ChatRoom) Join(name, uid string) (*Client, error) {
|
||||
// Don't allow new joins when the server is closing.
|
||||
if cr.isShutdown {
|
||||
return nil, fmt.Errorf("Server is shutting down")
|
||||
}
|
||||
|
||||
defer cr.clientsMtx.Unlock()
|
||||
cr.clientsMtx.Lock()
|
||||
|
||||
|
@ -500,3 +512,26 @@ func (cr *ChatRoom) changeName(oldName, newName string, forced bool) error {
|
|||
|
||||
return fmt.Errorf("Client not found with name %q", oldName)
|
||||
}
|
||||
|
||||
// Shutdown the chatroom. First, dissallow new joins by setting
|
||||
// isShutdown, then close each client connection. This would be
|
||||
// a good place to put a final command that gets sent to the client.
|
||||
func (cr *ChatRoom) Shutdown() {
|
||||
cr.isShutdown = true
|
||||
common.LogInfoln("ChatRoom is shutting down.")
|
||||
|
||||
cr.clientsMtx.Lock()
|
||||
defer cr.clientsMtx.Unlock()
|
||||
|
||||
for uuid, client := range cr.clients {
|
||||
common.LogDebugf("Closing connection for %s", uuid)
|
||||
client.conn.Close()
|
||||
}
|
||||
|
||||
for uuid, conn := range cr.tempConn {
|
||||
common.LogDebugf("Closing connection for temp %s", uuid)
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
common.LogInfoln("ChatRoom Shutdown() finished")
|
||||
}
|
||||
|
|
|
@ -7,39 +7,36 @@ import (
|
|||
"github.com/lucasb-eyer/go-colorful"
|
||||
)
|
||||
|
||||
// Colors holds all the valid html color names for MovieNight
|
||||
// the values in colors must be lowercase so it matches with the color input
|
||||
// this saves from having to call strings.ToLower(color) every time to check
|
||||
var colors = []string{
|
||||
var Colors = []string{
|
||||
"aliceblue", "antiquewhite", "aqua", "aquamarine", "azure",
|
||||
"beige", "bisque", "black", "blanchedalmond", "blue",
|
||||
"blueviolet", "brown", "burlywood", "cadetblue", "chartreuse",
|
||||
"chocolate", "coral", "cornflowerblue", "cornsilk", "crimson",
|
||||
"cyan", "darkblue", "darkcyan", "darkgoldenrod", "darkgray",
|
||||
"darkgrey", "darkgreen", "darkkhaki", "darkmagenta", "darkolivegreen",
|
||||
"darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen",
|
||||
"darkslateblue", "darkslategray", "darkslategrey", "darkturquoise", "darkviolet",
|
||||
"deeppink", "deepskyblue", "dimgray", "dimgrey", "dodgerblue",
|
||||
"firebrick", "floralwhite", "forestgreen", "fuchsia", "gainsboro",
|
||||
"ghostwhite", "gold", "goldenrod", "gray", "grey",
|
||||
"green", "greenyellow", "honeydew", "hotpink", "indianred",
|
||||
"indigo", "ivory", "khaki", "lavender", "lavenderblush",
|
||||
"lawngreen", "lemonchiffon", "lightblue", "lightcoral", "lightcyan",
|
||||
"lightgoldenrodyellow", "lightgray", "lightgrey", "lightgreen", "lightpink",
|
||||
"lightsalmon", "lightseagreen", "lightskyblue", "lightslategray", "lightslategrey",
|
||||
"lightsteelblue", "lightyellow", "lime", "limegreen", "linen",
|
||||
"magenta", "maroon", "mediumaquamarine", "mediumblue", "mediumorchid",
|
||||
"beige", "bisque", "blanchedalmond", "blueviolet", "brown",
|
||||
"burlywood", "cadetblue", "chartreuse", "chocolate", "coral",
|
||||
"cornflowerblue", "cornsilk", "crimson", "cyan", "darkcyan",
|
||||
"darkgoldenrod", "darkgray", "darkkhaki", "darkmagenta", "darkolivegreen",
|
||||
"darkorange", "darkorchid", "darksalmon", "darkseagreen", "darkslateblue",
|
||||
"darkslategray", "darkturquoise", "darkviolet", "deeppink", "deepskyblue",
|
||||
"dimgray", "dodgerblue", "firebrick", "floralwhite", "forestgreen",
|
||||
"fuchsia", "gainsboro", "ghostwhite", "gold", "goldenrod",
|
||||
"gray", "greenyellow", "honeydew", "hotpink", "indigo",
|
||||
"ivory", "khaki", "lavender", "lavenderblush", "lawngreen",
|
||||
"lemonchiffon", "lightblue", "lightcoral", "lightcyan", "lightgoldenrodyellow",
|
||||
"lightgrey", "lightgreen", "lightpink", "lightsalmon", "lightseagreen",
|
||||
"lightskyblue", "lightslategray", "lightsteelblue", "lightyellow", "lime",
|
||||
"limegreen", "linen", "magenta", "mediumaquamarine", "mediumorchid",
|
||||
"mediumpurple", "mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise",
|
||||
"mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin",
|
||||
"navajowhite", "navy", "oldlace", "olive", "olivedrab",
|
||||
"orange", "orangered", "orchid", "palegoldenrod", "palegreen",
|
||||
"paleturquoise", "palevioletred", "papayawhip", "peachpuff", "peru",
|
||||
"pink", "plum", "powderblue", "purple", "rebeccapurple",
|
||||
"red", "rosybrown", "royalblue", "saddlebrown", "salmon",
|
||||
"sandybrown", "seagreen", "seashell", "sienna", "silver",
|
||||
"skyblue", "slateblue", "slategray", "slategrey", "snow",
|
||||
"springgreen", "steelblue", "tan", "teal", "thistle",
|
||||
"tomato", "turquoise", "violet", "wheat", "white",
|
||||
"whitesmoke", "yellow", "yellowgreen",
|
||||
"mediumvioletred", "mintcream", "mistyrose", "moccasin", "navajowhite",
|
||||
"oldlace", "olive", "olivedrab", "orange", "orangered",
|
||||
"orchid", "palegoldenrod", "palegreen", "paleturquoise", "palevioletred",
|
||||
"papayawhip", "peachpuff", "peru", "pink", "plum",
|
||||
"powderblue", "purple", "rebeccapurple", "red", "rosybrown",
|
||||
"royalblue", "saddlebrown", "salmon", "sandybrown", "seagreen",
|
||||
"seashell", "sienna", "silver", "skyblue", "slateblue",
|
||||
"slategray", "snow", "springgreen", "steelblue", "tan",
|
||||
"teal", "thistle", "tomato", "turquoise", "violet",
|
||||
"wheat", "white", "whitesmoke", "yellow", "yellowgreen",
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -50,7 +47,7 @@ var (
|
|||
// It also accepts hex codes in the form of #RGB and #RRGGBB
|
||||
func IsValidColor(s string) bool {
|
||||
s = strings.ToLower(s)
|
||||
for _, c := range colors {
|
||||
for _, c := range Colors {
|
||||
if s == c {
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var data_good = map[string]string{
|
||||
"one": `<img src="/emotes/one.png" height="28px" title="one" />`,
|
||||
"two": `<img src="/emotes/two.png" height="28px" title="two" />`,
|
||||
"three": `<img src="/emotes/three.gif" height="28px" title="three" />`,
|
||||
|
||||
":one:": `<img src="/emotes/one.png" height="28px" title="one" />`,
|
||||
":two:": `<img src="/emotes/two.png" height="28px" title="two" />`,
|
||||
":three:": `<img src="/emotes/three.gif" height="28px" title="three" />`,
|
||||
|
||||
"[one]": `<img src="/emotes/one.png" height="28px" title="one" />`,
|
||||
"[two]": `<img src="/emotes/two.png" height="28px" title="two" />`,
|
||||
"[three]": `<img src="/emotes/three.gif" height="28px" title="three" />`,
|
||||
|
||||
":one: two [three]": `<img src="/emotes/one.png" height="28px" title="one" /> <img src="/emotes/two.png" height="28px" title="two" /> <img src="/emotes/three.gif" height="28px" title="three" />`,
|
||||
|
||||
"nope one what": `nope <img src="/emotes/one.png" height="28px" title="one" /> what`,
|
||||
"nope :two: what": `nope <img src="/emotes/two.png" height="28px" title="two" /> what`,
|
||||
"nope [three] what": `nope <img src="/emotes/three.gif" height="28px" title="three" /> what`,
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
Emotes = map[string]string{
|
||||
"one": "one.png",
|
||||
"two": "two.png",
|
||||
"three": "three.gif",
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestEmotes_ParseEmotes(t *testing.T) {
|
||||
for input, expected := range data_good {
|
||||
got := ParseEmotes(input)
|
||||
if got != expected {
|
||||
t.Errorf("%s failed to parse into %q. Received: %q", input, expected, got)
|
||||
}
|
||||
}
|
||||
}
|
136
main.go
136
main.go
|
@ -1,11 +1,13 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/nareix/joy4/format"
|
||||
|
@ -14,9 +16,10 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
addr string
|
||||
sKey string
|
||||
stats = newStreamStats()
|
||||
addr string
|
||||
sKey string
|
||||
stats = newStreamStats()
|
||||
chatServer *http.Server
|
||||
)
|
||||
|
||||
func setupSettings() error {
|
||||
|
@ -51,9 +54,6 @@ func main() {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
exit := make(chan bool)
|
||||
go handleInterrupt(exit)
|
||||
|
||||
// Load emotes before starting server.
|
||||
var err error
|
||||
if chat, err = newChatRoom(); err != nil {
|
||||
|
@ -77,54 +77,88 @@ func main() {
|
|||
common.LogInfoln("RoomAccess: ", settings.RoomAccess)
|
||||
common.LogInfoln("RoomAccessPin: ", settings.RoomAccessPin)
|
||||
|
||||
go startServer()
|
||||
go startRmtpServer()
|
||||
|
||||
<-exit
|
||||
}
|
||||
|
||||
func startRmtpServer() {
|
||||
server := &rtmp.Server{
|
||||
HandlePlay: handlePlay,
|
||||
HandlePublish: handlePublish,
|
||||
}
|
||||
err := server.ListenAndServe()
|
||||
if err != nil {
|
||||
// If the server cannot start, don't pretend we can continue.
|
||||
panic("Error trying to start rtmp server: " + err.Error())
|
||||
|
||||
// Define this here so we can set some timeouts and things.
|
||||
chatServer := &http.Server{
|
||||
Addr: addr,
|
||||
ReadTimeout: 10 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
MaxHeaderBytes: 1 << 20,
|
||||
}
|
||||
}
|
||||
|
||||
func startServer() {
|
||||
// Chat websocket
|
||||
http.HandleFunc("/ws", wsHandler)
|
||||
http.HandleFunc("/static/js/", wsStaticFiles)
|
||||
http.HandleFunc("/static/css/", wsStaticFiles)
|
||||
http.HandleFunc("/static/img/", wsImages)
|
||||
http.HandleFunc("/static/main.wasm", wsWasmFile)
|
||||
http.HandleFunc("/emotes/", wsEmotes)
|
||||
http.HandleFunc("/favicon.ico", wsStaticFiles)
|
||||
http.HandleFunc("/chat", handleIndexTemplate)
|
||||
http.HandleFunc("/video", handleIndexTemplate)
|
||||
http.HandleFunc("/help", handleHelpTemplate)
|
||||
http.HandleFunc("/pin", handlePin)
|
||||
|
||||
http.HandleFunc("/", handleDefault)
|
||||
|
||||
err := http.ListenAndServe(addr, nil)
|
||||
if err != nil {
|
||||
// If the server cannot start, don't pretend we can continue.
|
||||
panic("Error trying to start chat/http server: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func handleInterrupt(exit chan bool) {
|
||||
ch := make(chan os.Signal)
|
||||
signal.Notify(ch, os.Interrupt)
|
||||
<-ch
|
||||
common.LogInfoln("Closing server")
|
||||
if settings.StreamStats {
|
||||
stats.Print()
|
||||
}
|
||||
exit <- true
|
||||
|
||||
chatServer.RegisterOnShutdown(func() { chat.Shutdown() })
|
||||
// rtmp.Server does not implement .RegisterOnShutdown()
|
||||
//server.RegisterOnShutdown(func() { common.LogDebugln("server shutdown callback called.") })
|
||||
|
||||
// These have been moved back to annon functitons so I could use
|
||||
// `server`, `chatServer`, and `exit` in them without needing to
|
||||
// pass them as parameters.
|
||||
|
||||
// Signal handler
|
||||
exit := make(chan bool)
|
||||
go func() {
|
||||
ch := make(chan os.Signal)
|
||||
signal.Notify(ch, os.Interrupt)
|
||||
<-ch
|
||||
common.LogInfoln("Closing server")
|
||||
if settings.StreamStats {
|
||||
stats.Print()
|
||||
}
|
||||
|
||||
if err := chatServer.Shutdown(context.Background()); err != nil {
|
||||
common.LogErrorf("Error shutting down chat server: %v", err)
|
||||
}
|
||||
|
||||
common.LogInfoln("Shutdown() sent. Sending exit.")
|
||||
exit <- true
|
||||
}()
|
||||
|
||||
// Chat and HTTP server
|
||||
go func() {
|
||||
// Use a ServeMux here instead of the default, global,
|
||||
// http handler. It's a good idea when we're starting more
|
||||
// than one server.
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/ws", wsHandler)
|
||||
mux.HandleFunc("/static/js/", wsStaticFiles)
|
||||
mux.HandleFunc("/static/css/", wsStaticFiles)
|
||||
mux.HandleFunc("/static/img/", wsImages)
|
||||
mux.HandleFunc("/static/main.wasm", wsWasmFile)
|
||||
mux.HandleFunc("/emotes/", wsEmotes)
|
||||
mux.HandleFunc("/favicon.ico", wsStaticFiles)
|
||||
mux.HandleFunc("/chat", handleIndexTemplate)
|
||||
mux.HandleFunc("/video", handleIndexTemplate)
|
||||
mux.HandleFunc("/help", handleHelpTemplate)
|
||||
mux.HandleFunc("/pin", handlePin)
|
||||
|
||||
mux.HandleFunc("/", handleDefault)
|
||||
|
||||
chatServer.Handler = mux
|
||||
err := chatServer.ListenAndServe()
|
||||
// http.ErrServerClosed is returned when server.Shuddown()
|
||||
// is called.
|
||||
if err != http.ErrServerClosed {
|
||||
// If the server cannot start, don't pretend we can continue.
|
||||
panic("Error trying to start chat/http server: " + err.Error())
|
||||
}
|
||||
common.LogDebugln("ChatServer closed.")
|
||||
}()
|
||||
|
||||
// RTMP server
|
||||
go func() {
|
||||
err := server.ListenAndServe()
|
||||
// http.ErrServerClosed is returned when server.Shuddown()
|
||||
// is called.
|
||||
if err != http.ErrServerClosed {
|
||||
// If the server cannot start, don't pretend we can continue.
|
||||
panic("Error trying to start rtmp server: " + err.Error())
|
||||
}
|
||||
common.LogDebugln("RTMP server closed.")
|
||||
}()
|
||||
|
||||
<-exit
|
||||
}
|
||||
|
|
|
@ -42,6 +42,23 @@ span.svmsg {
|
|||
color: #ea6260;
|
||||
}
|
||||
|
||||
input[type=text] {
|
||||
background: transparent;
|
||||
border: var(--var-border);
|
||||
border-radius: var(--var-border-radius);
|
||||
color: var(--var-message-color);
|
||||
padding: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#colorInputDiv {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#colorInput {
|
||||
border: 2px solid var(--var-message-color);
|
||||
}
|
||||
|
||||
.root {
|
||||
max-width: var(--var-max-width);
|
||||
max-height: var(--var-max-height);
|
||||
|
@ -223,15 +240,6 @@ span.svmsg {
|
|||
margin: 5px auto;
|
||||
}
|
||||
|
||||
#name {
|
||||
background: transparent;
|
||||
border: var(--var-border);
|
||||
border-radius: var(--var-border-radius);
|
||||
color: var(--var-message-color);
|
||||
padding: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#chat {
|
||||
display: grid;
|
||||
grid-template-rows: 1.5em 2em 1fr 6em 2.5em 1em;
|
||||
|
@ -295,6 +303,12 @@ span.svmsg {
|
|||
color: #b1b1b1;
|
||||
}
|
||||
|
||||
#colorName {
|
||||
font-weight: bold;
|
||||
background: var(--var-background-color);
|
||||
padding: -10px;
|
||||
}
|
||||
|
||||
#colorSubmit:disabled {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -158,6 +158,10 @@ function help() {
|
|||
}
|
||||
|
||||
function showColors(show) {
|
||||
if (show === undefined) {
|
||||
show = $("#hiddencolor").css("display") === "none";
|
||||
}
|
||||
|
||||
$("#hiddencolor").css("display", show ? "block" : "");
|
||||
}
|
||||
|
||||
|
@ -188,11 +192,22 @@ function updateColor() {
|
|||
|
||||
function changeColor() {
|
||||
if (isValidColor(colorAsHex())) {
|
||||
sendMessage("/color " + colorAsHex());
|
||||
showColors(false);
|
||||
sendColor(colorAsHex());
|
||||
}
|
||||
}
|
||||
|
||||
function colorSelectChange() {
|
||||
let val = $("#colorSelect").val()
|
||||
if (val !== "") {
|
||||
sendColor(val);
|
||||
}
|
||||
}
|
||||
|
||||
function sendColor(color) {
|
||||
sendMessage("/color " + color);
|
||||
showColors(false);
|
||||
}
|
||||
|
||||
function setTimestamp(v) {
|
||||
showTimestamp(v)
|
||||
document.cookie = "timestamp=" + v
|
||||
|
@ -268,37 +283,3 @@ window.addEventListener("load", () => {
|
|||
// Make sure name is focused on start
|
||||
$("#name").focus();
|
||||
});
|
||||
|
||||
function pleaseremovethis() {
|
||||
colors = ["aliceblue", "antiquewhite", "aqua", "aquamarine", "azure",
|
||||
"beige", "bisque", "black", "blanchedalmond", "blue",
|
||||
"blueviolet", "brown", "burlywood", "cadetblue", "chartreuse",
|
||||
"chocolate", "coral", "cornflowerblue", "cornsilk", "crimson",
|
||||
"cyan", "darkblue", "darkcyan", "darkgoldenrod", "darkgray",
|
||||
"darkgrey", "darkgreen", "darkkhaki", "darkmagenta", "darkolivegreen",
|
||||
"darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen",
|
||||
"darkslateblue", "darkslategray", "darkslategrey", "darkturquoise", "darkviolet",
|
||||
"deeppink", "deepskyblue", "dimgray", "dimgrey", "dodgerblue",
|
||||
"firebrick", "floralwhite", "forestgreen", "fuchsia", "gainsboro",
|
||||
"ghostwhite", "gold", "goldenrod", "gray", "grey",
|
||||
"green", "greenyellow", "honeydew", "hotpink", "indianred",
|
||||
"indigo", "ivory", "khaki", "lavender", "lavenderblush",
|
||||
"lawngreen", "lemonchiffon", "lightblue", "lightcoral", "lightcyan",
|
||||
"lightgoldenrodyellow", "lightgray", "lightgrey", "lightgreen", "lightpink",
|
||||
"lightsalmon", "lightseagreen", "lightskyblue", "lightslategray", "lightslategrey",
|
||||
"lightsteelblue", "lightyellow", "lime", "limegreen", "linen",
|
||||
"magenta", "maroon", "mediumaquamarine", "mediumblue", "mediumorchid",
|
||||
"mediumpurple", "mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise",
|
||||
"mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin",
|
||||
"navajowhite", "navy", "oldlace", "olive", "olivedrab",
|
||||
"orange", "orangered", "orchid", "palegoldenrod", "palegreen",
|
||||
"paleturquoise", "palevioletred", "papayawhip", "peachpuff", "peru",
|
||||
"pink", "plum", "powderblue", "purple", "rebeccapurple",
|
||||
"red", "rosybrown", "royalblue", "saddlebrown", "salmon",
|
||||
"sandybrown", "seagreen", "seashell", "sienna", "silver",
|
||||
"skyblue", "slateblue", "slategray", "slategrey", "snow",
|
||||
"springgreen", "steelblue", "tan", "teal", "thistle",
|
||||
"tomato", "turquoise", "violet", "wheat", "white",
|
||||
"whitesmoke", "yellow", "yellowgreen",]
|
||||
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
<div id="chatButtons">
|
||||
<input type="button" class="button pretty-button" onclick="auth();" value="Auth" />
|
||||
<input type="button" class="button pretty-button" onclick="help();" value="Help" />
|
||||
<input type="button" class="button pretty-button" onclick="showColors(true);" value="Color" />
|
||||
<input type="button" class="button pretty-button" onclick="showColors();" value="Color" />
|
||||
<input type="button" class="button pretty-button" onclick="nick();" value="Nick" />
|
||||
{{if .Video}}
|
||||
<input type="button" class="button pretty-button" onclick="initPlayer();" value="Reload Player" />
|
||||
|
@ -75,7 +75,10 @@
|
|||
<input id="colorBlue" type="range" min="0" max="255" value="0" oninput="updateColor();" />
|
||||
<span id="colorBlueLabel"></span>
|
||||
</div>
|
||||
<div id="colorName" class="range-div" style="font-weight: bold;">
|
||||
<div class="range-div">
|
||||
<select id="colorSelect" onchange="colorSelectChange();"></select>
|
||||
</div>
|
||||
<div id="colorName" class="range-div">
|
||||
NAME
|
||||
</div>
|
||||
<div id="colorWarning" class="range-div contrast">
|
||||
|
|
10
wasm/main.go
10
wasm/main.go
|
@ -189,6 +189,16 @@ func main() {
|
|||
js.Set("debugValues", js.CallbackOf(debugValues))
|
||||
js.Set("showTimestamp", js.CallbackOf(showTimestamp))
|
||||
|
||||
go func() {
|
||||
time.Sleep(time.Second * 1)
|
||||
inner := `<option value=""></option>`
|
||||
for _, c := range common.Colors {
|
||||
inner += fmt.Sprintf(`<option value="%s">%s</option>\n`, c, c)
|
||||
}
|
||||
|
||||
js.Get("colorSelect").Set("innerHTML", inner)
|
||||
}()
|
||||
|
||||
// This is needed so the goroutine does not end
|
||||
for {
|
||||
// heatbeat to keep connection alive to deal with nginx
|
||||
|
|
Loading…
Reference in New Issue