This commit is contained in:
Miki 2024-01-11 15:34:52 +00:00
parent e4ac456c11
commit aa59f81115
106 changed files with 13 additions and 9222 deletions

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
backend/static
sse/be/static
docker/topscorer.tar

Binary file not shown.

View file

@ -1,5 +0,0 @@
.DS_Store
/static
.env
.env.*
go.sum

View file

@ -1,21 +0,0 @@
{
"name": "Qualifying",
"category": "Under 18 Men",
"number": 1,
"timer": 20,
"status": "idle",
"surfers": [
{
"name": "Uno",
"color": "red",
"priority": "",
"score": ""
},
{
"name": "Due",
"color": "blue",
"priority": "",
"score": ""
}
]
}

View file

@ -1,33 +0,0 @@
{
"name": "Quarterfinal",
"category": "Under 14 Men",
"number": 1,
"timer": 20,
"status": "idle",
"surfers": [
{
"name": "Uno",
"color": "green",
"priority": "",
"score": ""
},
{
"name": "Due",
"color": "yellow",
"priority": "",
"score": ""
},
{
"name": "Tre",
"color": "orange",
"priority": "",
"score": ""
},
{
"name": "Quattro",
"color": "red",
"priority": "",
"score": ""
}
]
}

View file

@ -1,27 +0,0 @@
{
"name": "Quarterfinal",
"category": "Under 14 Men",
"number": 2,
"timer": 20,
"status": "idle",
"surfers": [
{
"name": "Cinque",
"color": "red",
"priority": "",
"score": ""
},
{
"name": "Sei",
"color": "blue",
"priority": "",
"score": ""
},
{
"name": "Sette",
"color": "violet",
"priority": "",
"score": ""
}
]
}

View file

@ -1,21 +0,0 @@
{
"name": "Semifinal",
"category": "Under 12 Men",
"number": 1,
"timer": 20,
"status": "running",
"surfers": [
{
"name": "Uno",
"color": "blue",
"priority": "",
"score": ""
},
{
"name": "Due",
"color": "yellow",
"priority": "",
"score": ""
}
]
}

View file

@ -1,21 +0,0 @@
{
"name": "Semifinal",
"category": "Under 12 Women",
"number": 1,
"timer": 20,
"status": "idle",
"surfers": [
{
"name": "Uno",
"color": "red",
"priority": "",
"score": ""
},
{
"name": "Due",
"color": "green",
"priority": "",
"score": ""
}
]
}

View file

@ -1,21 +0,0 @@
{
"name": "Semifinal",
"category": "Under 12 Men",
"number": 2,
"timer": 20,
"status": "ended",
"surfers": [
{
"name": "Tre",
"color": "green",
"priority": "",
"score": ""
},
{
"name": "Quattro",
"color": "yellow",
"priority": "",
"score": ""
}
]
}

View file

@ -1,21 +0,0 @@
{
"name": "Semifinal",
"category": "Under 12 Women",
"number": 2,
"timer": 20,
"status": "idle",
"surfers": [
{
"name": "Tre",
"color": "green",
"priority": "",
"score": ""
},
{
"name": "Quattro",
"color": "blue",
"priority": "",
"score": ""
}
]
}

View file

@ -1,32 +0,0 @@
package main
func (w *Webapp) initApi() {
stream := NewServer()
w.Stream = stream
http_api := w.Engine.Group("/api")
http_api.GET("/priority", w.GetPriority)
http_api.POST("/priority", w.SetPriority)
// SSE
http_api.GET("/sse", HeadersMiddleware(), stream.serveHTTP(), stream.retvalSSE())
http_api.POST("/startheat", w.StartHeatTimer)
http_api.GET("/stopheat", w.StopHeatTimer)
http_api.POST("/saveheat", w.SaveHeat)
http_api.POST("/deleteheat", w.DeleteHeat)
http_api.GET("/loadheats", w.LoadHeats)
http_api.GET("/runningheat", w.LoadRunning)
// // Surfers
// http_api.GET("/surfers", w.GetSurfers)
// http_api.POST("/updatesurfer", w.UpdateSurfer)
// http_api.POST("/deletesurfer", w.DeleteSurfer)
// // Users
// http_api.GET("/users", w.GetUsers)
// http_api.POST("/updateuser", w.UpdateUser)
// http_api.POST("/deleteuser", w.DeleteUser)
}

Binary file not shown.

View file

@ -1,37 +0,0 @@
package main
type Color int
const (
Gray Color = iota
Black
Blue
Red
Yellow
Green
White
Magenta
)
func (c Color) String() string {
switch c {
case Gray:
return "gray"
case Black:
return "black"
case Blue:
return "blue"
case Red:
return "red"
case Yellow:
return "yellow"
case Green:
return "green"
case White:
return "white"
case Magenta:
return "magenta"
}
return "gray"
}

View file

@ -1,44 +0,0 @@
package main
import (
"fmt"
"log"
scribble "github.com/nanobox-io/golang-scribble"
)
type DB struct {
Db *scribble.Driver
}
func InitDb(dbAddress string) *DB {
var err error
var db *DB
db.Db, err = scribble.New("", nil)
if err != nil {
fmt.Println("Error", err)
}
log.Printf("App: %+v", db)
return db
}
func (db *DB) Write(table string, key string, value interface{}) error {
err := db.Db.Write(table, key, value)
if err != nil {
fmt.Println("Error", err)
return err
}
return nil
}
func (db *DB) Read(table string, key string, value interface{}) error {
err := db.Db.Read(table, key, value)
if err != nil {
fmt.Println("Error", err)
return err
}
return nil
}

View file

@ -1,39 +0,0 @@
module backend
go 1.21.4
require (
github.com/gin-contrib/cors v1.5.0
github.com/gin-gonic/gin v1.9.1
github.com/nanobox-io/golang-scribble v0.0.0-20190309225732-aa3e7c118975
)
require (
github.com/bytedance/sonic v1.10.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.15.5 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/jcelliott/lumber v0.0.0-20160324203708-dd349441af25 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
golang.org/x/arch v0.5.0 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/net v0.16.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View file

@ -1,12 +0,0 @@
package main
import (
"crypto/sha256"
"fmt"
)
func CalcId(str string, len int) string {
id := sha256.Sum256([]byte(str))
return fmt.Sprintf("%X", id)[0:len]
}

View file

@ -1,195 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
scribble "github.com/nanobox-io/golang-scribble"
)
type Surfer struct {
Name string `json:"name"`
Color string `json:"color"`
Priority string `json:"priority"`
Score string `json:"score"`
}
type Heat struct {
Name string `json:"name"`
Category string `json:"category"`
Number int `json:"number"`
Timer int `json:"timer"`
Status string `json:"status"`
Surfers []Surfer `json:"surfers"`
}
func heatName(heat Heat) string {
return fmt.Sprintf("%s.%d.%s", heat.Name, heat.Number, heat.Category)
}
func (w *Webapp) SaveHeat(c *gin.Context) {
var heat Heat
err := c.ShouldBind(&heat)
if err != nil {
log.Printf("req error: %+v", err)
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
log.Printf("heat: %+v", heat)
heat.Status = "idle"
err = w.DB.Write("Heat", heatName(heat), heat)
if err != nil {
log.Printf("set error: %+v", err)
c.JSON(http.StatusInternalServerError, gin.H{"status": fmt.Sprintf("Error: %+v", err)})
return
}
c.JSON(http.StatusOK, gin.H{"status": "saved"})
}
func (w *Webapp) DeleteHeat(c *gin.Context) {
var heat Heat
err := c.ShouldBind(&heat)
if err != nil {
log.Printf("req error: %+v", err)
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
log.Printf("heat: %+v", heat)
err = w.DB.Delete("Heat", heatName(heat))
if err != nil {
log.Printf("set error: %+v", err)
c.JSON(http.StatusInternalServerError, gin.H{"status": fmt.Sprintf("Error: %+v", err)})
return
}
c.JSON(http.StatusOK, gin.H{"status": "deleted"})
}
func (w *Webapp) LoadRunning(c *gin.Context) {
heats := loadHeats(w.DB)
for _, heat := range heats {
if heat.Status == "running" {
c.JSON(http.StatusOK, heat)
return
}
}
c.JSON(http.StatusNoContent, "")
}
func (w *Webapp) LoadHeats(c *gin.Context) {
heats := loadHeats(w.DB)
c.JSON(http.StatusOK, heats)
log.Printf("heats: %+v", heats)
}
func (w *Webapp) StartHeatTimer(c *gin.Context) {
var msg Message
var err error
var timer time.Duration
if w.Stream.Start {
c.JSON(http.StatusOK, w.Stream.Duration)
return
}
err = c.ShouldBind(&msg)
if err != nil {
log.Printf("req error: %+v", err)
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
timer, err = time.ParseDuration(msg.Duration)
if err != nil {
log.Printf("req error: %+v", err)
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
w.Stream.Duration = timer
w.Stream.Start = true
startHeat(w.DB, *w.Stream.Heat)
log.Printf("start timer %s - received %s", w.Stream.Duration, msg.Duration)
c.JSON(http.StatusOK, w.Stream.Duration)
}
func (w *Webapp) StopHeatTimer(c *gin.Context) {
if !w.Stream.Start {
c.JSON(http.StatusOK, w.Stream.Duration)
return
}
stopHeat(w.DB, *w.Stream.Heat)
w.Stream.Start = false
w.Stream.Duration = 0
log.Printf("start timer %s", w.Stream.Duration)
c.JSON(http.StatusOK, w.Stream.Duration)
}
func loadHeats(db *scribble.Driver) []Heat {
records, err := db.ReadAll("Heat")
if err != nil {
fmt.Printf("read error: %+v", err)
}
heats := make([]Heat, 0)
for _, record := range records {
var heat Heat
err = json.Unmarshal([]byte(record), &heat)
if err != nil {
fmt.Printf("decode error: %+v", err)
}
heats = append(heats, heat)
}
return heats
}
func startHeat(db *scribble.Driver, heat Heat) error {
log.Printf("heat: %+v", heat)
heat.Status = "running"
err := db.Write("Heat", heatName(heat), heat)
if err != nil {
log.Printf("set error: %+v", err)
return err
}
return nil
}
func stopHeat(db *scribble.Driver, heat Heat) error {
log.Printf("heat: %+v", heat)
heat.Status = "ended"
err := db.Write("Heat", heatName(heat), heat)
if err != nil {
log.Printf("set error: %+v", err)
return err
}
return nil
}

View file

@ -1,21 +0,0 @@
package main
import (
"os"
)
func main() {
var port string
Db := InitDb(os.Getenv("DB"))
webapp := InitHttp(Db)
if p := os.Getenv("PORT"); p == "" {
port = "8080"
} else {
port = p
}
webapp.Engine.Run(":" + port)
}

View file

@ -1,28 +0,0 @@
package main
type Mode int
const (
Priority Mode = iota
Start
Stop
Time
UpdateHeat
)
func (t Mode) String() string {
switch t {
case Priority:
return "priority"
case Stop:
return "stop"
case Time:
return "time"
case Start:
return "start"
case UpdateHeat:
return "updateHeat"
}
return "priority"
}

View file

@ -1,37 +0,0 @@
package main
import (
"log"
"net/http"
"github.com/gin-gonic/gin"
)
// ///////// Priority
func (w *Webapp) GetPriority(c *gin.Context) {
log.Printf("send priority %s", w.Stream.StatusPriority)
c.JSON(http.StatusOK, w.Stream.StatusPriority)
}
func (w *Webapp) SetPriority(c *gin.Context) {
var msg Message
var err error
log.Printf("set priority %s", c.Request.Body)
err = c.ShouldBind(&msg)
if err != nil {
log.Printf("req error: %+v", err)
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, "OK")
log.Printf("msg %+v", msg)
w.Stream.StatusPriority = msg.Priority
w.Stream.SendPriority(msg.Priority)
}

View file

@ -1,216 +0,0 @@
package main
import (
"fmt"
"io"
"time"
"github.com/gin-gonic/gin"
"log"
)
type SurferLive struct {
Name string `json:"name"`
Color string `json:"color"`
Score string `json:"score"`
Priority string `json:"priority"`
}
type Message struct {
Surfers []SurferLive `json:"surfers"`
Priority []string `json:"priority"`
Heat Heat `json:"heat"`
Duration string `json:"duration"`
Source string `json:"source"`
Msg string `json:"msg"`
Mode string `json:"mode"`
}
type Client struct {
Chan ClientChan
IP IPAddress
Mode Mode
}
type ClientChan chan Message
type IPAddress string
type SseStream struct {
// Events are pushed to this channel by the main events-gathering routine
Message chan Message
// New client connections
NewClients chan Client //chan string
// Closed client connections
ClosedClients chan ClientChan
// Total client connections
TotalClients map[ClientChan]IPAddress //bool
StatusPriority []string
Duration time.Duration
Start bool
Heat *Heat
}
// Initialize event and Start procnteessing requests
func NewServer() (sse *SseStream) {
sse = &SseStream{
Message: make(chan Message),
NewClients: make(chan Client),
ClosedClients: make(chan ClientChan),
TotalClients: make(map[ClientChan]IPAddress),
Start: false,
Heat: &Heat{},
}
go sse.listen()
go sse.timer()
return
}
// It Listens all incoming requests from clients.
// Handles addition and removal of clients and broadcast messages to clients.
func (stream *SseStream) listen() {
for {
select {
// Add new available client
case client := <-stream.NewClients:
stream.TotalClients[client.Chan] = client.IP
log.Printf("Client added. %d - %s registered clients", len(stream.TotalClients), client.IP)
// Remove closed client
case client := <-stream.ClosedClients:
ip := stream.TotalClients[client]
delete(stream.TotalClients, client)
close(client)
log.Printf("Removed client. %d - %s registered clients", len(stream.TotalClients), ip)
// Broadcast message to client
case eventMsg := <-stream.Message:
for clientMessageChan := range stream.TotalClients {
clientMessageChan <- eventMsg
log.Printf("Message %+v sent to %s", eventMsg, stream.TotalClients[clientMessageChan])
}
}
}
}
func HeadersMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("Content-Type", "text/event-stream")
c.Writer.Header().Set("Cache-Control", "no-cache")
c.Writer.Header().Set("Connection", "keep-alive")
c.Writer.Header().Set("Transfer-Encoding", "chunked")
c.Next()
}
}
func (stream *SseStream) serveHTTP() gin.HandlerFunc {
return func(c *gin.Context) {
// Initialize client channel
clientChan := make(ClientChan)
cli := Client{
Chan: clientChan,
IP: IPAddress(c.ClientIP()),
Mode: Priority,
}
// Send new connection to event server
stream.NewClients <- cli
defer func() {
// Send closed connection to event server
stream.ClosedClients <- clientChan
}()
c.Set("clientChan", clientChan)
c.Next()
}
}
func (stream *SseStream) retvalSSE() gin.HandlerFunc {
return func(c *gin.Context) {
if len(stream.StatusPriority) > 0 {
stream.SendPriority(stream.StatusPriority)
log.Printf("update priority %+v", stream.StatusPriority)
}
v, ok := c.Get("clientChan")
if !ok {
return
}
clientChan, ok := v.(ClientChan)
if !ok {
return
}
c.Stream(func(w io.Writer) bool {
// Stream message to client from message channel
if msg, ok := <-clientChan; ok {
c.SSEvent("message", msg)
return true
}
return false
})
}
}
func (stream *SseStream) SendPriority(pri []string) {
stream.Message <- Message{
Priority: pri,
Mode: Priority.String(),
}
}
func (stream *SseStream) timer() {
for {
if stream.Start {
timer := time.NewTimer(stream.Duration)
select {
case <-timer.C:
stream.Start = false
msg := Message{
Msg: "stop",
Mode: Stop.String(),
}
stream.Message <- msg
log.Printf("stop timer %+v", stream.Duration)
stream.Heat.Status = "ended"
continue
default:
if len(stream.TotalClients) > 0 {
if stream.Duration >= 0 {
currentTimer := fmt.Sprintf("%v", formatTime(stream.Duration))
msg := Message{
Duration: currentTimer,
Mode: Time.String(),
}
// Send current time to clients message channel
stream.Message <- msg
stream.Duration = stream.Duration - time.Second
time.Sleep(time.Second * 1)
} else {
timer.Stop()
}
}
}
}
}
}
func formatTime(d time.Duration) string {
minutes := int(d.Minutes()) % 60
seconds := int(d.Seconds()) % 60
return fmt.Sprintf("%02d:%02d", minutes, seconds)
}

View file

@ -1,117 +0,0 @@
package main
// type Surfer struct {
// Dbid string `json:"dbid"`
// Id string `json:"id"`
// Firstname string `json:"firstname"`
// Lastname string `json:"lastname"`
// }
///////////// Surfers
// func (w *Webapp) GetSurfers(c *gin.Context) {
// var cursor uint64
// var surfersKeys []string
// cursor = 0
// log.Printf("start scanning %+v", c.ClientIP())
// for {
// var keys []string
// var err error
// log.Printf("scan: cursor = %d", cursor)
// keys, cursor, err = w.DB.Redis.Scan(w.DB.Ctx, cursor, "surfer:*", 10).Result()
// if err != nil {
// panic(err)
// }
// log.Printf("ret scan: cursor = %d", cursor)
// log.Printf("scan: %+v", keys)
// surfersKeys = append(surfersKeys, keys...)
// if cursor == 0 {
// log.Printf("end scan: cursor = %d", cursor)
// break
// }
// }
// var surfers []Surfer
// for u := range surfersKeys {
// surf := Surfer{}
// ret := w.DB.Redis.HMGet(w.DB.Ctx, surfersKeys[u], "firstname", "lastname", "id", "sex", "birthdate", "stance", "hometown")
// ret.Scan(&surf)
// surf.Dbid = strings.Split(surfersKeys[u], ":")[1]
// log.Printf("surfer: %+v", surf)
// surfers = append(surfers, surf)
// }
// slices.SortFunc(surfers,
// func(a, b Surfer) int {
// return cmp.Compare(a.Id, b.Id)
// })
// log.Printf("surfers: %+v", surfers)
// c.JSON(http.StatusOK, surfers)
// }
// func (w *Webapp) DeleteSurfer(c *gin.Context) {
// var surfer Surfer
// var err error
// err = c.ShouldBind(&surfer)
// if err != nil {
// c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
// return
// }
// id := CalcId(surfer.Firstname+surfer.Lastname, _MaxLen)
// log.Printf("deleting: %+v", "surfer:"+id)
// res, err := w.DB.Redis.HGetAll(w.DB.Ctx, "surfer:"+id).Result()
// if err != nil || len(res) == 0 {
// log.Printf("del error: %+v", err)
// c.JSON(http.StatusNotFound, gin.H{"status": "Not Found"})
// return
// }
// log.Printf("found: %+v", res)
// err = w.DB.Redis.Del(w.DB.Ctx, "surfer:"+id).Err()
// if err != nil {
// log.Printf("del error: %+v", err)
// }
// log.Printf("del: %+v", surfer)
// c.JSON(http.StatusOK, gin.H{"status": "deleted"})
// }
// func (w *Webapp) UpdateSurfer(c *gin.Context) {
// var surfer Surfer
// var err error
// err = c.ShouldBind(&surfer)
// if err != nil {
// log.Printf("req error: %+v", err)
// c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
// return
// }
// surfer.Dbid = CalcId(surfer.Firstname+surfer.Lastname, _MaxLen)
// err = w.DB.Redis.HSet(w.DB.Ctx, "surfer:"+surfer.Dbid, surfer).Err()
// if err != nil {
// log.Printf("set error: %+v", err)
// }
// log.Printf("new: %+v", surfer)
// c.JSON(http.StatusOK, gin.H{"status": "added"})
// }

View file

@ -1,135 +0,0 @@
package main
// type User struct {
// Dbid string `json:"dbid"`
// Id string `json:"id"`
// Username string `json:"username"`
// Password string `json:"password"`
// Email string `json:"email"`
// Group string `json:"group"`
// Enabled bool `json:"enabled"`
// }
///////////////// Users
// func (w *Webapp) GetUsers(c *gin.Context) {
// var cursor uint64
// var users []string
// cursor = 0
// log.Printf("start scanning %+v", c.ClientIP())
// for {
// var keys []string
// var err error
// keys, cursor, err = w.DB.Redis.Scan(w.DB.Ctx, cursor, "user:*", 10).Result()
// if err != nil {
// panic(err)
// }
// log.Printf("scan: %+v", keys)
// users = append(users, keys...)
// if cursor == 0 {
// break
// }
// }
// var retval []User
// for u := range users {
// usr := User{}
// hmget := w.DB.Redis.HMGet(w.DB.Ctx, users[u], "username", "email", "group", "enabled")
// hmget.Scan(&usr)
// log.Printf("user: %+v", usr)
// retval = append(retval, usr)
// }
// slices.SortFunc(retval,
// func(a, b User) int {
// return cmp.Compare(a.Username, b.Username)
// })
// c.JSON(http.StatusOK, retval)
// }
// func (w *Webapp) UpdateUser(c *gin.Context) {
// var user User
// if err := c.ShouldBind(&user); err != nil {
// log.Printf("req error: %+v", err)
// c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
// return
// }
// user.Dbid = CalcId(user.Username, 8)
// ret := w.DB.Redis.HExists(w.DB.Ctx, "user:"+user.Dbid, "username").Val()
// if !ret {
// log.Printf("Exists: %+v", ret)
// err := w.DB.Redis.HSet(w.DB.Ctx, "user:"+user.Dbid, user).Err()
// if err != nil {
// log.Printf("set error: %+v", err)
// }
// } else {
// err := w.DB.Redis.HSet(w.DB.Ctx, "user:"+user.Dbid, "email", user.Email, "group", user.Group, "enabled", user.Enabled).Err()
// if err != nil {
// log.Printf("set error: %+v", err)
// }
// }
// log.Printf("new: %+v", user)
// c.JSON(http.StatusOK, gin.H{"status": "added"})
// }
// // func UpdateUser(c *gin.Context) {
// // var user common.User
// // if err := c.ShouldBind(&user); err != nil {
// // c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
// // return
// // }
// // id := common.Id(user.Username, 8)
// // if err := db.Db.Redis.HSet(db.Db.Ctx, "user:"+id, "email", user.Email, "group", user.Group, "enabled", user.Enabled).Err(); err != nil {
// // log.Fatalf("set error: %+v", err)
// // }
// // log.Printf("update: %+v", user)
// // c.JSON(http.StatusOK, gin.H{"status": "updated"})
// // }
// func (w *Webapp) DeleteUser(c *gin.Context) {
// var user User
// if err := c.ShouldBind(&user); err != nil {
// c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
// return
// }
// id := CalcId(user.Username, 8)
// log.Printf("deleting: %+v", "user:"+id)
// res, err := w.DB.Redis.HGetAll(w.DB.Ctx, "user:"+id).Result()
// if err != nil || len(res) == 0 {
// log.Printf("del error: %+v", err)
// c.JSON(http.StatusNotFound, gin.H{"status": "Not Found"})
// return
// }
// log.Printf("found: %+v", res)
// if err := w.DB.Redis.Del(w.DB.Ctx, "user:"+id).Err(); err != nil {
// log.Printf("del error: %+v", err)
// }
// log.Printf("del: %+v", user)
// c.JSON(http.StatusOK, gin.H{"status": "deleted"})
// }

View file

@ -1,65 +0,0 @@
package main
import (
"log"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
scribble "github.com/nanobox-io/golang-scribble"
)
type Webapp struct {
Engine *gin.Engine
Stream *SseStream
DB *scribble.Driver
}
// const _MaxLen = 8
func InitHttp(d *scribble.Driver) *Webapp {
router := gin.Default()
router.Use(cors.Default())
wapp := &Webapp{
Engine: router,
DB: d,
}
wapp.initApi()
// wapp.initAuth()
displayH := router.Group("/displayh")
displayH.Static("/", "./static/displayh")
displayV := router.Group("/displayv")
displayV.Static("/", "./static/displayv")
priority := router.Group("/priority")
priority.Static("/", "./static/priority")
mobile := router.Group("/mobile")
mobile.Static("/", "./static/mobile")
setup := router.Group("/setup")
setup.Static("/", "./static/setup")
draws := router.Group("/draws")
draws.Static("/", "./static/draws")
sapp := router.Group("/_app")
sapp.Static("/", "./static/_app")
static := router.Group("/static")
static.Static("/", "./static/static")
router.StaticFile("/", "./static/index.html")
router.StaticFile("/favicon.png", "./static/favicon.png")
router.ForwardedByClientIP = true
router.SetTrustedProxies([]string{"127.0.0.1"})
log.Printf("WebApp: %+v", wapp)
return wapp
}

5
backend/.gitignore vendored
View file

@ -1,5 +0,0 @@
static
DB
backend
Heat
.vscode

View file

@ -1,93 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"log"
scribble "github.com/nanobox-io/golang-scribble"
)
type DB struct {
Db *scribble.Driver
}
func InitDb(dbPath string) *DB {
var err error
var db = &DB{}
if dbPath == "" {
dbPath = "DB"
}
db.Db, err = scribble.New(dbPath, nil)
if err != nil {
fmt.Println("Error", err)
}
log.Printf("App: %+v", db)
return db
}
func (db *DB) Write(table string, key string, value interface{}) error {
err := db.Db.Write(table, key, value)
if err != nil {
fmt.Println("Error", err)
}
return err
}
func (db *DB) Read(table string, key string, value interface{}) error {
err := db.Db.Read(table, key, value)
if err != nil {
fmt.Println("Error", err)
}
return err
}
func (db *DB) Delete(table string, key string) error {
err := db.Db.Delete(table, key)
if err != nil {
fmt.Println("Error", err)
}
return err
}
func (db *DB) loadHeats() []Heat {
records, err := db.Db.ReadAll("Heat")
if err != nil {
fmt.Printf("read error: %+v", err)
}
heats := make([]Heat, 0)
for _, record := range records {
var heat Heat
err = json.Unmarshal([]byte(record), &heat)
if err != nil {
fmt.Printf("decode error: %+v", err)
}
heats = append(heats, heat)
}
return heats
}
func (db *DB) loadSurfers() []Athlete {
records, err := db.Db.ReadAll("Surfers")
if err != nil {
fmt.Printf("read error: %+v", err)
}
athletes := make([]Athlete, 0)
for _, record := range records {
var athlete Athlete
err = json.Unmarshal([]byte(record), &athlete)
if err != nil {
fmt.Printf("decode error: %+v", err)
}
athletes = append(athletes, athlete)
}
return athletes
}

View file

@ -1,39 +0,0 @@
module backend
go 1.21.5
require (
github.com/gin-contrib/cors v1.5.0
github.com/gin-gonic/gin v1.9.1
github.com/nanobox-io/golang-scribble v0.0.0-20190309225732-aa3e7c118975
)
require (
github.com/bytedance/sonic v1.10.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.15.5 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/jcelliott/lumber v0.0.0-20160324203708-dd349441af25 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
golang.org/x/arch v0.5.0 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/net v0.16.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View file

@ -1,106 +0,0 @@
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
github.com/bytedance/sonic v1.10.1 h1:7a1wuFXL1cMy7a3f7/VFcEtriuXQnUBhtoVfOZiaysc=
github.com/bytedance/sonic v1.10.1/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo=
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/gin-contrib/cors v1.5.0 h1:DgGKV7DDoOn36DFkNtbHrjoRiT5ExCe+PC9/xp7aKvk=
github.com/gin-contrib/cors v1.5.0/go.mod h1:TvU7MAZ3EwrPLI2ztzTt3tqgvBCq+wn8WpZmfADjupI=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.15.5 h1:LEBecTWb/1j5TNY1YYG2RcOUN3R7NLylN+x8TTueE24=
github.com/go-playground/validator/v10 v10.15.5/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/jcelliott/lumber v0.0.0-20160324203708-dd349441af25 h1:EFT6MH3igZK/dIVqgGbTqWVvkZ7wJ5iGN03SVtvvdd8=
github.com/jcelliott/lumber v0.0.0-20160324203708-dd349441af25/go.mod h1:sWkGw/wsaHtRsT9zGQ/WyJCotGWG/Anow/9hsAcBWRw=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/nanobox-io/golang-scribble v0.0.0-20190309225732-aa3e7c118975 h1:zm/Rb2OsnLWCY88Njoqgo4X6yt/lx3oBNWhepX0AOMU=
github.com/nanobox-io/golang-scribble v0.0.0-20190309225732-aa3e7c118975/go.mod h1:4Mct/lWCFf1jzQTTAaWtOI7sXqmG+wBeiBfT4CxoaJk=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y=
golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos=
golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

View file

@ -1,179 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
scribble "github.com/nanobox-io/golang-scribble"
)
type Surfer struct {
Name string `json:"name"`
Category string `json:"category"`
Color string `json:"color"`
Priority string `json:"priority"`
Score string `json:"score"`
}
type Heat struct {
Round string `json:"round"`
Category string `json:"category"`
Number int `json:"number"`
Timer int `json:"timer"`
Status string `json:"status"`
Surfers []Surfer `json:"surfers"`
}
func heatName(heat Heat) string {
str := fmt.Sprintf("%s.%d.%s", heat.Round, heat.Number, heat.Category)
str = strings.ReplaceAll(str, " ", "_")
return str
}
func (app *App) SaveHeat(c *gin.Context) {
var heat Heat
// body, _ := io.ReadAll(c.Request.Body)
// log.Printf("save: %+v", string(body))
err := c.ShouldBind(&heat)
if err != nil {
log.Printf("req error: %+v", err)
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
log.Printf("heat: %+v", heat)
heat.Status = "idle"
err = app.DB.Write("Heat", heatName(heat), heat)
if err != nil {
log.Printf("set error: %+v", err)
c.JSON(http.StatusInternalServerError, gin.H{"status": fmt.Sprintf("Error: %+v", err)})
return
}
c.JSON(http.StatusOK, gin.H{"status": "saved"})
}
func (app *App) LoadHeats(c *gin.Context) {
heats := app.DB.loadHeats()
c.JSON(http.StatusOK, heats)
log.Printf("heats: %+v", heats)
}
func (app *App) DeleteHeat(c *gin.Context) {
var heat Heat
err := c.ShouldBind(&heat)
if err != nil {
log.Printf("req error: %+v", err)
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
log.Printf("heat: %+v", heat)
err = app.DB.Delete("Heat", heatName(heat))
if err != nil {
log.Printf("set error: %+v", err)
c.JSON(http.StatusInternalServerError, gin.H{"status": fmt.Sprintf("Error: %+v", err)})
return
}
c.JSON(http.StatusOK, gin.H{"status": "deleted"})
}
func (app *App) StartHeatTimer(c *gin.Context) {
var msg Message
var err error
var timer time.Duration
if app.Stream.HeatRunning {
c.JSON(http.StatusOK, "running")
return
}
err = c.ShouldBind(&msg)
if err != nil {
log.Printf("req error: %+v", err)
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
heat := &Heat{}
json.Unmarshal([]byte(msg.Data), heat)
log.Printf("msg: %+v", msg)
log.Printf("heat: %+v", heat)
timer, err = time.ParseDuration(strconv.Itoa(heat.Timer) + "m")
if err != nil {
log.Printf("req error: %+v", err)
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
app.Stream.HeatTimer = timer
app.Stream.HeatRunning = true
// app.startHeat()
log.Printf("start timer %s - received %s", app.Stream.HeatTimer, heat.Timer)
c.JSON(http.StatusOK, app.Stream.HeatRunning)
}
// func (app *App) StopHeatTimer(c *gin.Context) {
// if !app.Stream.Start {
// c.JSON(http.StatusOK, app.Stream.Duration)
// return
// }
// stopHeat(app.DB, *app.Stream.Heat)
// app.Stream.Start = false
// app.Stream.Duration = 0
// log.Printf("start timer %s", app.Stream.Duration)
// c.JSON(http.StatusOK, app.Stream.Duration)
// }
func (app *App) startHeat(heat Heat) error {
log.Printf("heat: %+v", heat)
heat.Status = "running"
err := app.DB.Write("Heat", heatName(heat), heat)
if err != nil {
log.Printf("set error: %+v", err)
return err
}
return nil
}
func stopHeat(db *scribble.Driver, heat Heat) error {
log.Printf("heat: %+v", heat)
heat.Status = "ended"
err := db.Write("Heat", heatName(heat), heat)
if err != nil {
log.Printf("set error: %+v", err)
return err
}
return nil
}

View file

@ -1,98 +0,0 @@
package main
import (
"os"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
type App struct {
Engine *gin.Engine
Stream *SseStream
DB *DB
}
func InitHttp() *App {
app := &App{
Engine: nil,
Stream: nil,
DB: nil,
}
app.DB = InitDb(os.Getenv("DB"))
app.Stream = InitSse()
app.Engine = gin.Default()
app.Engine.Use(cors.Default())
app.RegisterWebRoutes()
app.RegisterApiRoutes()
return app
}
func (app *App) Run() {
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
app.Engine.Run(":" + port)
}
func (app *App) RegisterWebRoutes() {
display := app.Engine.Group("/display")
display.Static("/", "./static/display")
priority := app.Engine.Group("/priority")
priority.Static("/", "./static/priority")
mobile := app.Engine.Group("/mobile")
mobile.Static("/", "./static/mobile")
setup := app.Engine.Group("/setup")
setup.Static("/", "./static/setup")
surfers := app.Engine.Group("/surfers")
surfers.Static("/", "./static/surfers")
draws := app.Engine.Group("/draws")
draws.Static("/", "./static/draws")
sapp := app.Engine.Group("/_app")
sapp.Static("/", "./static/_app")
static := app.Engine.Group("/static")
static.Static("/", "./static/static")
app.Engine.StaticFile("/", "./static/index.html")
app.Engine.StaticFile("/favicon.png", "./static/favicon.png")
app.Engine.ForwardedByClientIP = true
app.Engine.SetTrustedProxies([]string{"127.0.0.1"})
}
func (app *App) RegisterApiRoutes() {
api := app.Engine.Group("/api")
// api.GET("/priority", app.GetPriority)
// api.POST("/priority", app.SetPriority)
api.GET("/sse", app.Stream.SseHeadersMiddleware(), app.Stream.Stream)
api.POST("/msg", app.Stream.SendMsg)
api.POST("/startheat", app.StartHeatTimer)
// api.GET("/stopheat", app.StopHeatTimer)
api.POST("/saveheat", app.SaveHeat)
api.POST("/deleteheat", app.DeleteHeat)
api.GET("/loadheats", app.LoadHeats)
// api.GET("/runningheat", app.LoadRunning)
api.GET("/loadsurfers", app.LoadSurfers)
api.POST("/savesurfer", app.SaveSurfer)
api.POST("/deletesurfer", app.DeleteSurfer)
}

View file

@ -1,8 +0,0 @@
package main
func main() {
app := InitHttp()
app.Run()
}

View file

@ -1,126 +0,0 @@
package main
import (
"io"
"log"
"net/http"
"slices"
"strconv"
"time"
"github.com/gin-gonic/gin"
)
var events = []string{"message", "timer", "priority", "cmd"}
type Message struct {
Event string `json:"event"`
Data string `json:"data"`
Cmd string `json:"cmd"`
Id string `json:"id"`
}
type ClientChan chan Message
type Client struct {
Id string `json:"id"`
Ip string `json:"ip"`
Chan ClientChan `json:"chan"`
Events []string `json:"events"`
}
type SseStream struct {
Clients []Client `json:"clients"`
MsgId map[string]int `json:"msgid"`
Events []string `json:"events"`
HeatRunning bool `json:"heat_running"`
HeatTimer time.Duration `json:"heat_timer"`
}
func InitSse() *SseStream {
sse := &SseStream{
Clients: make([]Client, 0),
MsgId: map[string]int{},
Events: events,
HeatRunning: false,
HeatTimer: 0,
}
for i := range events {
sse.MsgId[events[i]] = 0
}
return sse
}
func (sse *SseStream) SseHeadersMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("Content-Type", "text/event-stream")
c.Writer.Header().Set("Cache-Control", "no-cache")
c.Writer.Header().Set("Connection", "keep-alive")
c.Writer.Header().Set("Transfer-Encoding", "chunked")
c.Next()
}
}
func (sse *SseStream) Stream(c *gin.Context) {
client := &Client{
Ip: c.Request.RemoteAddr,
Chan: make(ClientChan),
Events: c.QueryArray("event"),
}
log.Printf("events: %+v", client.Events)
client.Id = generateClientId(client.Ip, client.Events, 8)
sse.AddClient(*client)
defer func() {
sse.RemoveClient(client)
close(client.Chan)
log.Printf("Client %s disconnected", client.Ip)
}()
log.Printf("Client %s connected", client.Ip)
c.Stream(func(w io.Writer) bool {
msg, ok := <-client.Chan
if !ok {
return false
}
c.SSEvent(msg.Event, msg)
return true
})
}
func (sse *SseStream) AddClient(client Client) {
sse.Clients = append(sse.Clients, client)
}
func (sse *SseStream) RemoveClient(client *Client) {
for i, c := range sse.Clients {
if c.Id == client.Id {
sse.Clients = append(sse.Clients[:i], sse.Clients[i+1:]...)
return
}
}
}
func (sse *SseStream) Send(msg Message) {
sse.MsgId[msg.Event]++
msg.Id = strconv.Itoa(sse.MsgId[msg.Event])
for _, client := range sse.Clients {
if slices.Contains(client.Events, msg.Event) || slices.Contains(client.Events, "*") {
client.Chan <- msg
log.Printf("sent: %+v -> %+v", msg, client.Ip)
}
}
}
func (sse *SseStream) SendMsg(c *gin.Context) {
var msg Message
c.ShouldBind(&msg)
sse.Send(msg)
c.JSON(http.StatusOK, gin.H{"status": "msg"})
}

View file

@ -1,75 +0,0 @@
package main
import (
"fmt"
"log"
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
type Athlete struct {
Name string `json:"name"`
Category string `json:"category"`
}
func surferName(athlete Athlete) string {
str := fmt.Sprintf("%s-%s", athlete.Name, athlete.Category)
str = strings.ReplaceAll(str, " ", "_")
return str
}
func (app *App) LoadSurfers(c *gin.Context) {
surfers := app.DB.loadSurfers()
c.JSON(http.StatusOK, surfers)
log.Printf("surfers: %+v", surfers)
}
func (app *App) SaveSurfer(c *gin.Context) {
var athlete Athlete
err := c.ShouldBind(&athlete)
if err != nil {
log.Printf("req error: %+v", err)
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
log.Printf("heat: %+v", athlete)
err = app.DB.Write("Surfers", surferName(athlete), athlete)
if err != nil {
log.Printf("set error: %+v", err)
c.JSON(http.StatusInternalServerError, gin.H{"status": fmt.Sprintf("Error: %+v", err)})
return
}
c.JSON(http.StatusOK, gin.H{"status": "saved"})
}
func (app *App) DeleteSurfer(c *gin.Context) {
var athlete Athlete
err := c.ShouldBind(&athlete)
if err != nil {
log.Printf("req error: %+v", err)
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
log.Printf("surfer: %+v", athlete)
err = app.DB.Delete("Surfers", surferName(athlete))
if err != nil {
log.Printf("set error: %+v", err)
c.JSON(http.StatusInternalServerError, gin.H{"status": fmt.Sprintf("Error: %+v", err)})
return
}
c.JSON(http.StatusOK, gin.H{"status": "deleted"})
}

View file

@ -1,14 +0,0 @@
package main
import (
"crypto/sha256"
"fmt"
"strings"
)
func generateClientId(ip string, events []string, len int) string {
str := ip + strings.Join(events, "|")
id := sha256.Sum256([]byte(str))
return fmt.Sprintf("%X", id)[0:len]
}

4
docker/build.sh Normal file → Executable file
View file

@ -1,5 +1,5 @@
#!/bin/bash
docker build -t mikif70/topscorer -f ./Dockerfile ../backend/
docker build -t mikif70/topscorer -f ./Dockerfile ../backend.light/
docker save -o topscorer.tar mikif70/topscorer
docker save -o topscorer.tar mikif70/topscorer

View file

@ -11,6 +11,8 @@
const events = ['priority'];
$: setup_height = 100 / $surfersCount - 2.2 / $surfersCount;
window.document.body.oncontextmenu = function () {
return false;
};
@ -59,13 +61,17 @@
<ul>
{#each Array($surfersCount) as _, id}
<li>
<li style="--height:{setup_height}vh">
{#if $surfers[id].priority == 'P'}
<div class="priority" id="p">{$surfers[id].priority}</div>
<div class="priority" id="p">
{$surfers[id].priority}
</div>
{:else}
<div class="priority" id="n">{$surfers[id].priority}</div>
<div class="priority" id="n">
{$surfers[id].priority}
</div>
{/if}
<div class="color" style="background-color: {$surfers[id].color}"></div>
<div class="color" style:background-color={$surfers[id].color}></div>
</li>
{/each}
</ul>
@ -87,9 +93,6 @@
li {
display: flex;
flex-direction: row;
/* border: 2px solid black; */
/* min-height: 18.8vh; */
/* width: 99.4vw; */
margin-top: 0.2vh;
margin-bottom: 0.2vh;
overflow: hidden;
@ -100,7 +103,6 @@
align-self: center;
min-height: 19vh;
min-width: 19vh;
/* border-right: 2px solid gray; */
margin-right: 0.2vw;
background-color: gray;
}

View file

@ -1,13 +0,0 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

View file

@ -1,15 +0,0 @@
/** @type { import("eslint").Linter.FlatConfig } */
module.exports = {
root: true,
extends: ['eslint:recommended', 'plugin:svelte/recommended', 'prettier'],
parserOptions: {
sourceType: 'module',
ecmaVersion: 2020,
extraFileExtensions: ['.svelte']
},
env: {
browser: true,
es2017: true,
node: true
}
};

View file

@ -1,10 +0,0 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

View file

@ -1 +0,0 @@
engine-strict=true

View file

@ -1,13 +0,0 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

View file

@ -1,8 +0,0 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

View file

@ -1,38 +0,0 @@
# create-svelte
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm create svelte@latest
# create a new project in my-app
npm create svelte@latest my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```bash
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.

View file

@ -1,26 +0,0 @@
{
"name": "frontend",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev --host",
"build": "vite build",
"preview": "vite preview",
"lint": "prettier --check . && eslint .",
"format": "prettier --write ."
},
"devDependencies": {
"@sveltejs/adapter-auto": "^2.1.1",
"@sveltejs/adapter-static": "^2.0.3",
"@sveltejs/kit": "^1.30.3",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.35.1",
"prettier": "^3.1.1",
"prettier-plugin-svelte": "^3.1.2",
"sass": "^1.69.5",
"svelte": "^4.2.8",
"vite": "^4.5.1"
},
"type": "module"
}

File diff suppressed because it is too large Load diff

View file

@ -1,12 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View file

@ -1,24 +0,0 @@
<script>
// import { Button, Tooltip } from 'flowbite-svelte';
export let pill = false;
export let color = 'blue';
export let size = 'sm';
export let href = "/login";
</script>
<button size={size} class="!p-2" {pill} color={color} href={href}>
<svg
class="w-6 h-6 text-gray-100 dark:text-white"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fill="currentColor"
d="M10 0a10 10 0 1 0 10 10A10.011 10.011 0 0 0 10 0Zm0 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6Zm0 13a8.949 8.949 0 0 1-4.951-1.488A3.987 3.987 0 0 1 9 13h2a3.987 3.987 0 0 1 3.951 3.512A8.949 8.949 0 0 1 10 18Z"
/>
</svg>
</button>
<!-- <Tooltip id="type-auto" arrow={false} type="custom" defaultClass="" class="p-1 text-sm bg-blue-700 text-gray-100" >Login</Tooltip> -->

View file

@ -1,43 +0,0 @@
<script>
// import { Button, Tooltip } from 'flowbite-svelte';
export let pill = false;
export let color = 'blue';
export let size = 'sm';
</script>
<button {size} class="!p-2" {pill} {color}>
<svg class="w-6 h-6" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
<path
fill="currentColor"
d="m62.578 41.956 -28.444 21.333a3.556 3.556 0 0 1 -4.267 0l-28.444 -21.333a3.637 3.637 0 0 1 -1.244 -3.982A3.605 3.605 0 0 1 3.556 35.556h7.111V32a3.566 3.566 0 0 1 3.556 -3.556h35.556a3.566 3.566 0 0 1 3.556 3.556v3.556h7.111a3.552 3.552 0 0 1 2.133 6.4Z"
/>
<path
fill="currentColor"
x="3"
y="4"
width="12"
height="2"
rx="1"
ry="1"
d="M14.222 14.222H49.778A3.556 3.556 0 0 1 53.333 17.778V17.778A3.556 3.556 0 0 1 49.778 21.333H14.222A3.556 3.556 0 0 1 10.667 17.778V17.778A3.556 3.556 0 0 1 14.222 14.222z"
/>
<path
fill="currentColor"
x="3"
width="12"
height="2"
rx="1"
ry="1"
d="M14.222 0H49.778A3.556 3.556 0 0 1 53.333 3.556V3.556A3.556 3.556 0 0 1 49.778 7.111H14.222A3.556 3.556 0 0 1 10.667 3.556V3.556A3.556 3.556 0 0 1 14.222 0z"
/></svg
>
</button>
<!-- <Tooltip
id="type-auto"
arrow={false}
type="custom"
defaultClass=""
class="p-1 text-sm bg-blue-700 text-gray-100">Priority</Tooltip
> -->

View file

@ -1,32 +0,0 @@
<script>
// import { Button, Tooltip } from 'flowbite-svelte';
export let pill = false;
export let color = 'blue';
export let size = 'sm';
</script>
<button size={size} class="!p-2" {pill} color={color}>
<svg
class="w-6 h-6"
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"
viewBox="0 0 455 455"
style="enable-background:new 0 0 455 455;"
xml:space="preserve"
fill="currentColor"
>
<g>
<rect x="162" y="323" width="293" height="132" />
<rect x="162" y="161" width="293" height="132" />
<rect width="455" height="131" />
<rect y="161" width="132" height="294" />
</g>
</svg>
</button>
<!-- <Tooltip id="type-auto" arrow={false} type="custom" defaultClass="" class="p-1 text-sm bg-blue-700 text-gray-100" >Score</Tooltip> -->

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -1 +0,0 @@
// place files you want to import through the `$lib` alias in this folder.

View file

@ -1,14 +0,0 @@
import { writable } from 'svelte/store'
export default function () {
const surfers = writable([]);
async function get() {
const response = await fetch(`/api/surfers`)
surfers.set(await response.json())
}
get();
return surfers;
}

View file

@ -1,14 +0,0 @@
import { writable } from 'svelte/store'
export default function () {
const users = writable([]);
async function get() {
const response = await fetch(`/api/users`)
users.set(await response.json())
}
get();
return users;
}

View file

@ -1,3 +0,0 @@
export const prerender = true;
export const ssr = false;
export const trailingSlash = 'always';

View file

@ -1,10 +0,0 @@
<script>
import TopScorer from "$lib/img/topscorer_logo_web.png";
</script>
<div style="background-color: black;">
<img src={TopScorer} alt="TopScorer">
</div>

View file

@ -1,240 +0,0 @@
<script>
// import { page } from '$app/stores';
import { onDestroy, onMount } from 'svelte';
import { dev } from '$app/environment';
if (dev) {
console.log('Dev mode');
} else {
console.log('Not dev mode');
}
let surfers = [
{ name: 'Kanoa Igarashi', color: 'red', score: '4.50', priority: '3' },
{ name: 'Griffin Colapinto', color: 'white', score: '5.60', priority: 'P' },
{ name: 'Jack Robinson', color: 'blue', score: '6.10', priority: '5' },
{ name: 'Gabriel Medina', color: 'green', score: '4.30', priority: '2' },
{ name: 'Italo Ferreira', color: 'black', score: '6.50', priority: '4' }
];
let width;
// $: activeUrl = $page.url.pathname;
let event = 'Semifinal';
let category = 'U16 man';
let heat = 'Heat 1';
const pad2 = (number) => `00${number}`.slice(-2);
$: min = 10;
$: sec = 25;
let end = false;
function updateRemainingTime() {
if ((min === 0) & (sec === 0)) {
clearInterval(timer);
end = true;
} else if (sec === 0) {
min -= 1;
sec = 59;
} else {
sec -= 1;
}
}
updateRemainingTime();
const timer = setInterval(updateRemainingTime, 1000);
function Subscribe() {
const sse = new EventSource(`/api/sse`);
console.log('subscribe');
sse.onmessage = (e) => {
let Msg = JSON.parse(e.data);
console.log(`received: ${JSON.stringify(Msg)}`);
if (Msg.mode === 'priority') {
console.log(`priority: ${Msg.priority}`);
for (let i in surfers) {
surfers[i].priority = Msg.priority[i];
}
}
};
return () => {
sse.close();
console.log(`sse closing ${Date.now()}`);
};
}
onMount(() => {
const unsub = Subscribe();
return unsub;
});
onDestroy(() => {
clearInterval(timer); // Pulisci il timer quando il componente viene distrutto
});
</script>
<svelte:window bind:innerWidth={width} />
<div class="header">
{#if !end}
<div class="timer">{pad2(min)}:{pad2(sec)}</div>
{:else}
<div class="timer" style="color: red">{pad2(min)}:{pad2(sec)}</div>
{/if}
</div>
<div class="container">
{#each surfers as surfer, id}
<div class="box">
<div class="square" style="background-color: {surfer.color};">
{#if surfer.priority != ''}
{#if surfer.priority === 'P'}
{#if surfer.color === 'white'}
<span class="priority_white">{surfer.priority}</span>
{:else}
<span class="priority">{surfer.priority}</span>
{/if}
{:else}
<span class="priority_small">{surfer.priority}</span>
{/if}
{/if}
</div>
<div class="score">{surfer.score}</div>
</div>
{/each}
</div>
<style>
:root {
--backColor: #334;
--textColor: white;
--maxWidth: (100% - 15px);
}
.header {
/* padding: 15px; */
height: 10vh;
background-color: var(--backColor);
padding-left: 10px;
padding-right: 10px;
padding-top: 6px;
padding-bottom: 6px;
margin-top: 2px;
margin-bottom: 2px;
width: calc(var(--maxWidth));
color: var(--textColor);
display: flex;
justify-content: space-between;
}
.header .timer {
font-size: 13vh;
padding-left: 10px;
padding-right: 20px;
font-weight: bold;
flex: 2 2 auto;
align-self: center;
text-align: center;
}
.container {
height: 85vh;
background-color: var(--backColor);
padding-left: 10px;
padding-right: 10px;
padding-top: 4px;
width: calc(var(--maxWidth));
color: var(--textColor);
}
.box {
width: 100%;
display: flex;
align-items: center;
padding: 1px;
margin-bottom: 1px;
}
.priority {
width: 15%;
height: 100%;
border-radius: 20%;
font-size: 8vh;
animation: blink 2s 3;
margin-right: auto;
background-color: white;
color: black;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
.priority_white {
width: 15%;
height: 100%;
border-radius: 20%;
font-size: 8vh;
animation: blink_white 2s 3;
margin-right: auto;
background-color: black;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
padding-bottom: 4px;
}
.priority_small {
width: 10%;
height: 80%;
border-radius: 20%;
font-size: 8vh;
/* animation: blink 2s infinite; */
margin-right: auto;
margin-left: 30px;
background-color: #bbb;
color: black;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
.square {
width: 100%;
height: 16vh;
border-radius: 5px;
margin-right: 15px;
display: flex;
align-items: center;
justify-content: center;
padding-bottom: 2px;
margin-bottom: 2px;
}
.score {
padding-right: 20px;
width: 20%;
font-size: 12vh;
float: right;
text-align: center;
font-weight: bold;
}
@keyframes blink {
0% {
background-color: white;
}
50% {
background-color: rgba(255, 255, 255, 0.6);
}
100% {
background-color: white;
}
}
</style>

View file

@ -1,256 +0,0 @@
<script>
// import { page } from '$app/stores';
import { onDestroy, onMount } from 'svelte';
import { dev } from '$app/environment';
if (dev) {
console.log('Dev mode');
} else {
console.log('Not dev mode');
}
let surfers = [
// { name: 'Kanoa Igarashi', color: 'red', score: '4.50', priority: '3' },
// { name: 'Griffin Colapinto', color: 'white', score: '5.60', priority: 'P' },
// { name: 'Jack Robinson', color: 'blue', score: '6.10', priority: '5' },
// { name: 'Gabriel Medina', color: 'green', score: '4.30', priority: '2' },
// { name: 'Italo Ferreira', color: 'black', score: '6.50', priority: '4' }
];
let width;
// $: activeUrl = $page.url.pathname;
let heat = {};
const pad2 = (number) => `00${number}`.slice(-2);
$: min = 0;
$: sec = 0;
let end = false;
loadRunning();
async function loadRunning() {
const res = await fetch(`/api/runningheat`);
const data = await res.json();
heat = data;
console.log(`retval: ${JSON.stringify(heat)}`);
if (heat != null) {
console.log(`heat: ${JSON.stringify(heat)}`);
min = heat.timer;
surfers = heat.surfers;
}
}
// function updateRemainingTime() {
// if ((min === 0) & (sec === 0)) {
// clearInterval(timer);
// end = true;
// } else if (sec === 0) {
// min -= 1;
// sec = 59;
// } else {
// sec -= 1;
// }
// }
// updateRemainingTime();
// const timer = setInterval(updateRemainingTime, 1000);
function Subscribe() {
const sse = new EventSource(`/api/sse`);
console.log('subscribe');
sse.onmessage = (e) => {
let Msg = JSON.parse(e.data);
console.log(`received: ${JSON.stringify(Msg)}`);
if (Msg.mode === 'priority') {
console.log(`priority: ${Msg.priority}`);
for (let i in surfers) {
surfers[i].priority = Msg.priority[i];
}
} else if (Msg.mode === 'time') {
// console.log(`duration: ${Msg.duration}`);
let min_sec = Msg.duration.split(":");
min = min_sec[0];
sec = min_sec[1];
// console.log(`min & sec = ${min} & ${sec}`);
if (!start) {
start = true;
}
} else if (Msg.mode === 'stop') {
console.log(`stop duration: ${Msg.duration}`);
end = true;
start = false;
}
};
return () => {
sse.close();
console.log(`sse closing ${Date.now()}`);
};
}
onMount(() => {
if (!dev) {
const unsub = Subscribe();
return unsub;
}
});
onDestroy(() => {
clearInterval(timer); // Pulisci il timer quando il componente viene distrutto
});
</script>
<svelte:window bind:innerWidth={width} />
<div class="header">
{#if !end}
<div class="timer">{pad2(min)}:{pad2(sec)}</div>
{:else}
<div class="timer" style="color: red">{pad2(min)}:{pad2(sec)}</div>
{/if}
</div>
<div class="container">
{#each surfers as surfer, id}
<div class="box">
<div class="square" style="background-color: {surfer.color};">
{#if surfer.priority != ''}
{#if surfer.priority === 'P'}
{#if surfer.color === 'white'}
<span class="priority_white">{surfer.priority}</span>
{:else}
<span class="priority">{surfer.priority}</span>
{/if}
{:else}
<span class="priority_small">{surfer.priority}</span>
{/if}
{/if}
</div>
<div class="score">{surfer.score}</div>
</div>
{/each}
</div>
<style>
:root {
--backColor: #334;
--textColor: white;
--maxWidth: (100% - 15px);
}
.header {
/* padding: 15px; */
height: 10vh;
background-color: var(--backColor);
padding-left: 10px;
padding-right: 10px;
padding-top: 6px;
padding-bottom: 6px;
margin-top: 2px;
margin-bottom: 2px;
width: calc(var(--maxWidth));
color: var(--textColor);
display: flex;
justify-content: space-between;
}
.header .timer {
font-size: 13vh;
padding-left: 10px;
padding-right: 20px;
font-weight: bold;
flex: 2 2 auto;
align-self: center;
text-align: center;
}
.container {
height: 85vh;
background-color: var(--backColor);
padding-left: 10px;
padding-right: 10px;
padding-top: 4px;
width: calc(var(--maxWidth));
color: var(--textColor);
}
.priority {
width: 90%;
height: 20%;
border-radius: 20%;
font-size: 8vh;
animation: blink 2s 3;
margin-top: 50%;
background-color: white;
color: black;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
.priority_white {
width: 90%;
height: 20%;
border-radius: 20%;
font-size: 8vh;
animation: blink_white 2s 3;
margin-top: 50%;
background-color: black;
color: white;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
font-weight: bold;
padding-bottom: 4px;
}
.priority_small {
width: 80%;
height: 15%;
border-radius: 20%;
font-size: 8vh;
/* animation: blink 2s infinite; */
margin-top: 60%;
margin-left: 20px;
background-color: #bbb;
color: black;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
.score {
padding-right: 20px;
width: 20%;
font-size: 12vh;
float: right;
text-align: center;
font-weight: bold;
}
.box {
height: 100%;
width: 19%;
float: left;
margin-left: 1px;
margin-right: 1px;
}
.square {
height: 100%;
width: 100%;
float: left;
align-items: center;
justify-content: center;
text-align: center;
}
</style>

View file

@ -1,96 +0,0 @@
<script>
import Logo from "$lib/img/topscorer_logo_web.png"
$: heats = [];
loadHeats();
async function loadHeats() {
const res = await fetch(`/api/loadheats`);
const data = await res.json();
for (let i in data) {
heats = [...heats, data[i]];
console.log(`${i} retval: ${JSON.stringify(data[i])}`);
}
}
</script>
<div class="header">
<img class="img" src={Logo} alt="logo">
<span class="title" style="color: aliceblue;">Heat Draws</span>
</div>
<hr>
{#each heats as heat}
<table>
<tr>
{#if heat.status === 'running'}
<th colspan="2" class="running">
{heat.name} ({heat.number}) {heat.category}
</th>
{:else if heat.status === 'ended'}
<th colspan="2" class="ended">
{heat.name} ({heat.number}) {heat.category}
</th>
{:else}
<th colspan="2">
{heat.name} ({heat.number}) {heat.category}
</th>
{/if}
</tr>
{#each heat.surfers as surfer }
<tr>
<td>
{surfer.name}
</td>
<td style="background-color: {surfer.color};">
{surfer.color}
</td>
</tr>
{/each}
</table>
<hr>
{/each}
<style>
.header {
background-color: black;
display: flex;
width: 100%;
justify-content: space-between;
text-align: center;
align-items: center;
}
.header .img {
height: 3rem;
}
.header .title {
font-size: 3rem;
margin: 0 auto;
}
table, th, td {
border: 1px solid black;
border-collapse: collapse;
}
th.running {
background-color: green;
animation: blinker 2s linear infinite;
}
th.ended {
background-color: lightcoral;
text-decoration: line-through 1px lightyellow;
}
@keyframes blinker {
50% {
opacity: 0.5;
}
}
</style>

View file

@ -1,353 +0,0 @@
<script>
// import { page } from '$app/stores';
import { onDestroy, onMount } from 'svelte';
import { dev } from '$app/environment';
if (dev) {
console.log('Dev mode');
} else {
console.log('Not dev mode');
}
let surfers = [
{ name: 'Kanoa Igarashi', color: 'red', score: '4.50', priority: '3' },
{ name: 'Griffin Colapinto', color: 'white', score: '5.60', priority: 'P' },
{ name: 'Jack Robinson', color: 'blue', score: '6.10', priority: '5' },
{ name: 'Gabriel Medina', color: 'green', score: '4.30', priority: '2' },
{ name: 'Italo Ferreira', color: 'black', score: '6.50', priority: '4' }
];
let width;
// $: activeUrl = $page.url.pathname;
let event = 'Semifinal';
let category = 'U16 man';
let heat = 'Heat 1';
const pad2 = (number) => `00${number}`.slice(-2);
$: min = 10;
$: sec = 25;
let end = false;
function updateRemainingTime() {
if ((min === 0) & (sec === 0)) {
clearInterval(timer);
end = true;
} else if (sec === 0) {
min -= 1;
sec = 59;
} else {
sec -= 1;
}
}
updateRemainingTime();
const timer = setInterval(updateRemainingTime, 1000);
function Subscribe() {
const sse = new EventSource(`/api/sse`);
console.log('subscribe');
sse.onmessage = (e) => {
let Msg = JSON.parse(e.data);
console.log(`received: ${JSON.stringify(Msg)}`);
if (Msg.mode === 'priority') {
console.log(`priority: ${Msg.priority}`);
for (let i in surfers) {
surfers[i].priority = Msg.priority[i];
}
}
};
return () => {
sse.close();
console.log(`sse closing ${Date.now()}`);
};
}
onMount(() => {
const unsub = Subscribe();
return unsub;
});
onDestroy(() => {
clearInterval(timer); // Pulisci il timer quando il componente viene distrutto
});
</script>
<svelte:window bind:innerWidth={width} />
<div class="header">
<span class="title">{event}</span>
<span class="title">{category}</span>
{#if !end}
<span class="timer">{pad2(min)}:{pad2(sec)}</span>
{:else}
<span class="timer" style="color: red">{pad2(min)}:{pad2(sec)}</span>
{/if}
<span class="heat">{heat}</span>
</div>
<div class="container">
{#each surfers as surfer, id}
<div class="box">
<div class="square" style="background-color: {surfer.color};">
{#if surfer.priority != ''}
<span class="priority">{surfer.priority}</span>
{/if}
</div>
<div class="text">{surfer.name}</div>
<div class="score">{surfer.score}</div>
</div>
{/each}
</div>
{#if width < 768}
<div class="footer">
<div>waiting for scores</div>
<!-- <div class="score">6.00</div>
<div class="score">6.00</div>
<div class="score">6.00</div>
<div class="score">6.00</div>
<div class="score">6.00</div> -->
</div>
{/if}
<style>
:root {
--backColor: #334;
--textColor: white;
--maxWidth: (100% - 15px);
}
.header {
background-color: var(--backColor);
padding-left: 10px;
padding-right: 10px;
padding-top: 6px;
padding-bottom: 6px;
margin-top: 2px;
margin-bottom: 2px;
width: calc(var(--maxWidth));
color: var(--textColor);
display: flex;
justify-content: space-between;
}
.header .title {
font-size: 2vh;
padding-left: 10px;
padding-right: 10px;
font-weight: bold;
flex: 1 1 0;
align-self: center;
}
.header .heat {
font-size: 2vh;
padding-left: 0px;
padding-right: 10px;
font-weight: bold;
flex: 1 1 auto;
align-self: center;
text-align: right;
}
.header .timer {
font-size: 5vh;
padding-left: 10px;
padding-right: 10px;
font-weight: bold;
flex: 2 2 auto;
align-self: center;
text-align: center;
}
.container {
background-color: var(--backColor);
padding: 10px;
width: calc(var(--maxWidth));
color: var(--textColor);
}
.box {
width: 100%;
display: flex;
align-items: center;
padding: 4px;
margin-bottom: 2px;
}
.priority {
width: 60%;
height: 60%;
background-color: white;
color: black;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.8rem;
font-weight: bold;
}
.square {
width: 60px;
height: 60px;
border-radius: 5px;
margin-right: 20px;
display: flex;
align-items: center;
justify-content: center;
position: relative; /* Per il posizionamento del testo */
}
.text {
flex: 1;
font-size: 1.5rem;
font-weight: lighter;
}
.score {
float: right;
padding-right: 10px;
text-align: center;
width: 20%;
font-size: 2.5rem;
font-weight: bold;
}
.footer {
background-color: var(--backColor);
padding: 10px;
width: calc(var(--maxWidth));
color: var(--textColor);
display: flex;
font-size: larger;
font-weight: bold;
align-items: center;
justify-content: space-around;
align-content: center;
margin-top: 2px;
}
/* .footer .score {
float: right;
padding-right: 10px;
text-align: center;
width: 20%;
font-size: 1.5rem;
font-weight: lighter;
} */
/* @media screen and (min-width: 768px) {
.container {
height: 85vh;
}
.priority {
width: 15%;
height: 100%;
border-radius: 20%;
font-size: 8vh;
animation: blink 2s infinite;
margin-right: auto;
}
@keyframes blink {
0% {
background-color: white;
}
50% {
background-color: rgba(255, 255, 255, 0.6);
}
100% {
background-color: white;
}
}
.square {
width: 100%;
height: 15vh;
margin-right: 20px;
}
.score {
padding-right: 40px;
width: 20%;
font-size: 14vh;
}
.text {
flex: 0;
font-size: 0;
}
.header {
padding: 15px;
margin-bottom: 2px;
height: 10vh;
width: calc(100% - 15px);
}
.header .timer {
font-size: 6rem;
padding-left: 10px;
padding-right: 20px;
}
} */
/* @media screen and (min-width: 1280px) {
.priority {
width: 120px;
height: 90%;
font-size: 6rem;
}
.square {
width: 100%;
height: 125px;
margin-right: 20px;
}
.score {
float: right;
padding-right: 40px;
width: 20%;
font-size: 6rem;
}
.header .timer {
font-size: 6rem;
padding-left: 10px;
padding-right: 20px;
}
}
@media screen and (min-width: 1920px) {
.priority {
width: 200px;
font-size: 10rem;
}
.square {
width: 100%;
height: 205px;
border-radius: 5px;
margin-right: 20px;
}
.score {
padding-right: 40px;
width: 20%;
font-size: 11rem;
}
.header .timer {
font-size: 8rem;
padding-left: 10px;
padding-right: 20px;
}
} */
</style>

View file

@ -1,399 +0,0 @@
<script>
// import { page } from '$app/stores';
import { onDestroy, onMount } from 'svelte';
import { dev } from '$app/environment';
if (dev) {
console.log('Dev mode');
} else {
console.log('Not dev mode');
}
let heat_number;
let heats = [];
$: surfers = [];
// { name: 'Kanoa Igarashi', color: 'red', score: '4.50', priority: '3' },
// { name: 'Griffin Colapinto', color: 'white', score: '5.60', priority: 'P' },
// { name: 'Jack Robinson', color: 'blue', score: '6.10', priority: '5' },
// { name: 'Gabriel Medina', color: 'green', score: '4.30', priority: '2' },
// { name: 'Italo Ferreira', color: 'black', score: '6.50', priority: '4' }
// ];
let width;
// $: activeUrl = $page.url.pathname;
const pad2 = (number) => `00${number}`.slice(-2);
$: min = 0;
$: sec = 0;
let end = false;
let start = false;
loadHeats();
function Subscribe() {
const sse = new EventSource(`/api/sse`);
console.log('subscribe');
sse.onmessage = (e) => {
let Msg = JSON.parse(e.data);
console.log(`received: ${JSON.stringify(Msg)}`);
if (Msg.mode === 'priority') {
console.log(`priority: ${Msg.priority}`);
for (let i in surfers) {
surfers[i].priority = Msg.priority[i];
}
} else if (Msg.mode === 'time') {
// console.log(`duration: ${Msg.duration}`);
let min_sec = Msg.duration.split(":");
min = min_sec[0];
sec = min_sec[1];
// console.log(`min & sec = ${min} & ${sec}`);
if (!start) {
start = true;
}
} else if (Msg.mode === 'stop') {
console.log(`stop duration: ${Msg.duration}`);
end = true;
start = false;
}
};
return () => {
sse.close();
console.log(`sse closing ${Date.now()}`);
};
}
async function dblclick(id) {
console.log(`dblclick = ${surfers[id]}`);
if (surfers[id] === 'P') {
console.log('pressed P');
} else {
console.log(`pressed: [${id}] ${surfers[id].priority}`);
}
const res = await fetch(`/api/stopheat`);
const data = await res.json();
console.log(`stop: ${JSON.stringify(data)}`);
}
async function startHeat() {
const res = await fetch(`/api/startheat`, {
method: 'POST',
body: JSON.stringify({
duration: min+"m",
mode: 'time'
}),
headers: {
'Content-Type': 'application/json'
}
});
console.log(`retval: ${JSON.stringify(res)}`);
start = true;
end = false;
}
async function loadHeats() {
const res = await fetch(`/api/loadheats`);
const data = await res.json();
for (let i in data) {
heats[i] = data[i];
console.log(`${i} retval: ${JSON.stringify(data[i])}`);
}
}
function setHeat(id) {
console.log(`setHeat: ${id}`);
if (id === "99") {
min = 0;
surfers = []
return;
}
min = heats[id].timer;
surfers = heats[id].surfers;
}
async function click(id) {
let max = surfers.length;
console.log(surfers[id]);
if (surfers[id].priority === 'P') {
for (let i in surfers) {
if (i != id) {
let pos = parseInt(surfers[i].priority) - 1;
if (pos === 1) {
surfers[i].priority = 'P';
} else {
surfers[i].priority = pos.toString();
}
}
}
surfers[id].priority = max.toString();
} else if (surfers[id].priority === '') {
console.log(`priority empty; pressed: [${id}] ${surfers[id].priority}`);
for (let i in surfers) {
console.log(`looping(${id}): ${i} - ${surfers[i].priority}`);
if (i != id) {
if (surfers[i].priority === '') {
console.log(`empty: [${i}] ${surfers[i].priority}`);
continue;
} else {
console.log(`not empty: [${i}] ${surfers[i].priority}`);
let pos = parseInt(surfers[i].priority) - 1;
if (pos === 1) {
surfers[i].priority = 'P';
} else {
surfers[i].priority = pos.toString();
}
}
}
}
surfers[id].priority = max.toString();
} else {
console.log(`pressed: [${id}] ${surfers[id].priority}`);
let oldpos = parseInt(surfers[id].priority);
for (let i in surfers) {
if (i != id) {
console.log(`pos: [${i}] ${surfers[i].priority} ${surfers[i].priority > oldpos}`);
if (surfers[i].priority != 'P' && surfers[i].priority > oldpos) {
let pos = parseInt(surfers[i].priority);
if (pos > oldpos) {
surfers[i].priority = (pos - 1).toString();
console.log(`newpos: ${surfers[i].priority}`);
}
}
} else {
surfers[i].priority = max.toString();
console.log(`last: [${i}] ${surfers[i].priority}`);
}
}
}
const res = await fetch(`/api/priority`, {
method: 'POST',
body: JSON.stringify({
priority: surfers.map((obj) => obj.priority),
mode: 'priority'
}),
headers: {
'Content-Type': 'application/json'
}
});
console.log(
JSON.stringify({
priority: surfers.map((obj) => obj.priority),
mode: 'priority'
})
);
console.log(`retval: ${JSON.stringify(res)}`);
}
onMount(() => {
const unsub = Subscribe();
return unsub;
});
// onDestroy(() => {
// clearInterval(timer); // Pulisci il timer quando il componente viene distrutto
// });
</script>
<svelte:window bind:innerWidth={width} />
<div class="header">
<!-- <button class="button" on:click={() => loadHeats()} disabled={start}>Load</button> -->
<select name="heats" id="heats" bind:value={heat_number} on:change={() => setHeat(heat_number)}>
<option value="99">Select Heat</option>
{#each heats as heat, id}
<option value={id}>{heat.name} {heat.number} {heat.category}</option>
{/each}
</select>
{#if !end}
<div class="timer">{pad2(min)}:{pad2(sec)}</div>
{:else}
<div class="timer" style="color: red">{pad2(min)}:{pad2(sec)}</div>
{/if}
<button class="button" on:click={() => startHeat()} disabled={start}>Start</button>
</div>
<div class="container">
{#each surfers as surfer, id}
<div class="box">
<div
class="square"
{id}
style="background-color: {surfer.color};"
on:click={() => click(id)}
on:contextmenu={(e) => {
e.preventDefault();
dblclick(id);
}}
on:keypress={console.log('keypress')}
role="button"
tabindex={id}
>
{#if surfer.priority != ''}
{#if surfer.priority === 'P'}
{#if surfer.color === 'white'}
<span class="priority_white">{surfer.priority}</span>
{:else}
<span class="priority">{surfer.priority}</span>
{/if}
{:else}
<span class="priority_small">{surfer.priority}</span>
{/if}
{/if}
</div>
<div class="score">{surfer.score}</div>
</div>
{/each}
</div>
<style>
:root {
--backColor: #334;
--textColor: white;
--maxWidth: (100% - 15px);
}
.header {
/* padding: 15px; */
height: 10vh;
background-color: var(--backColor);
padding-left: 10px;
padding-right: 10px;
padding-top: 6px;
padding-bottom: 6px;
margin-top: 2px;
margin-bottom: 2px;
width: calc(var(--maxWidth));
color: var(--textColor);
display: flex;
justify-content: space-between;
align-items: center;
}
.header .timer {
font-size: 13vh;
padding-left: 10px;
padding-right: 20px;
font-weight: bold;
flex: 2 2 auto;
align-self: center;
text-align: center;
}
.header .button {
height: 60%;
align-items: center;
background-color: yellow;
border-radius: 10%;
}
.container {
height: 85vh;
background-color: var(--backColor);
padding-left: 10px;
padding-right: 10px;
padding-top: 4px;
width: calc(var(--maxWidth));
color: var(--textColor);
}
.box {
width: 100%;
display: flex;
align-items: center;
padding: 1px;
margin-bottom: 1px;
}
.priority {
width: 15%;
height: 100%;
border-radius: 20%;
font-size: 8vh;
/* animation: blink 2s 2; */
margin-right: auto;
background-color: white;
color: black;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
.priority_white {
width: 15%;
height: 100%;
border-radius: 20%;
font-size: 8vh;
/* animation: blink_white 2s 3; */
margin-right: auto;
background-color: black;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
padding-bottom: 4px;
}
.priority_small {
width: 13%;
height: 85%;
border-radius: 20%;
font-size: 8vh;
/* animation: blink 2s 3; */
margin-right: auto;
margin-left: 30px;
background-color: #ccc;
color: black;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
.square {
width: 100%;
height: 16vh;
border-radius: 5px;
margin-right: 15px;
display: flex;
align-items: center;
justify-content: center;
padding-bottom: 2px;
margin-bottom: 2px;
}
.score {
padding-right: 20px;
width: 20%;
font-size: 12vh;
float: right;
text-align: center;
font-weight: bold;
}
@keyframes blink {
0% {
background-color: white;
}
50% {
background-color: rgba(255, 255, 255, 0.6);
}
100% {
background-color: white;
}
}
@keyframes blink_white {
0% {
background-color: black;
}
50% {
background-color: rgba(0, 0, 0, 0.6);
}
100% {
background-color: black;
}
}
</style>

View file

@ -1,376 +0,0 @@
<script>
import { onMount } from 'svelte';
import Logo from "$lib/img/topscorer_logo_web.png"
let colors = [
"black",
"blue",
"red",
"green",
"yellow",
"pink",
"white",
];
let rounds = [
"Qualifying",
"Opening",
"Elimination",
"Round of 48",
"Round of 32",
"Round of 16",
"Quarterfinal",
"Semifinal",
"Final",
];
let categories = [
"Under 12 Women",
"Under 12 Men",
"Under 14 Women",
"Under 14 Men",
"Under 16 Women",
"Under 16 Men",
"Under 18 Women",
"Under 18 Men",
];
$: surfers = 2;
$: heats = [];
let surfer_list = [];
$: heat = {};
resetHeat();
loadHeats();
function resetHeat() {
surfers = 2;
surfer_list = new Array();
surfer_list.push({
name: '',
color: '',
score: '',
priority: ''
});
surfer_list.push({
name: '',
color: '',
score: '',
priority: ''
});
heat = {
number: 1,
name: '',
category: '',
timer: 20,
surfers: surfer_list
}
}
async function loadHeats() {
const res = await fetch(`/api/loadheats`);
const data = await res.json();
for (let i in data) {
heats[i] = data[i];
console.log(`${i} retval: ${JSON.stringify(data[i])}`);
}
}
async function deleteHeat(id) {
const res = await fetch(`/api/deleteheat`, {
method: 'POST',
body: JSON.stringify(heats[id]),
headers: {
'Content-Type': 'application/json'
}
});
console.log(`retval: ${JSON.stringify(res)}`);
console.log(JSON.stringify(heats[id]));
resetHeat();
loadHeats();
}
function setHeat(id) {
resetHeat();
console.log(`setHeat: ${id}`);
console.log(heats[id]);
heat.number = heats[id].number;
heat.name = heats[id].name;
heat.category = heats[id].category;
heat.timer = heats[id].timer;
surfer_list = heats[id].surfers;
surfers = surfer_list.length;
}
function addSurfers() {
surfers++;
surfer_list.push({
name: '',
color: '',
score: '',
priority: ''
});
}
function removeSurfers() {
surfers--;
if (surfers < 2) surfers = 2;
surfer_list.pop();
}
async function save() {
if(hasDuplicateColors(surfer_list)) {
alert('Colors must be unique');
return;
}
if(surfer_list.length < 2) {
alert('Must have at least 2 surfers');
return;
}
if(heat.name === '') {
alert('Must have a name');
return;
}
if(heat.category === '') {
alert('Must have a category');
return;
}
if(heat.number === '') {
alert('Must have a number');
return;
}
if(heat.timer === '') {
alert('Must have a timer');
return;
}
heat.surfers = surfer_list;
const res = await fetch(`/api/saveheat`, {
method: 'POST',
body: JSON.stringify(heat),
headers: {
'Content-Type': 'application/json'
}
});
console.log(`retval: ${JSON.stringify(res)}`);
console.log(JSON.stringify(heat));
resetHeat();
loadHeats();
}
function hasDuplicateColors(arr) {
const colors = [];
console.log(JSON.stringify(arr));
for(let i = 0; i < arr.length; i++) {
const color = arr[i].color;
if(colors.includes(color)) {
console.log(`duplicate color: ${color}`);
return true;
}
colors.push(color);
}
return false;
}
function capitalize(element, elementName) {
element[elementName] = element[elementName].charAt(0).toUpperCase() + element[elementName].slice(1);
console.log(`element: ${element[elementName]}`);
}
onMount(() => {
resetHeat();
loadHeats();
});
</script>
<div class="header">
<img class="img" src={Logo} alt="logo">
<span class="title" style="color: aliceblue;">Heat setup</span>
</div>
<div class="container">
<div class="heat">
<label class="label" for="heat">Heat</label>
<select name="heat" id="heat" bind:value={heat.name}>
{#each rounds as round}
<option value={round}>{round}</option>
{/each}
</select>
<!-- <input bind:value={heat.name} on:change={capitalize(heat, "name")} id="name" type="text"> -->
<label class="label" for="number">Number</label>
<input bind:value={heat.number} id="number" type="number" min="1" max="20">
<label class="label" for="category">Category</label>
<select name="category" id="category" bind:value={heat.category}>
{#each categories as category}
<option value={category}>{category}</option>
{/each}
</select>
<!-- <input bind:value={heat.category} on:change={capitalize(heat, "category")} id="category" type="text"> -->
<label class="label" for="timer">Duration</label>
<input bind:value={heat.timer} id="timer" type="number" min="5" max="60" step="5"> <!-- on:keydown={(event) => {event.preventDefault()}} -->
</div>
<hr>
<button class="plus" on:click={() => {addSurfers();}}>+</button>
<span class="surfers">{surfers}</span>
<button class="plus" on:click={() => {removeSurfers();}}>-</button>
{#each Array(surfers) as _, surfer}
<div class="surfer">
<label class="label" for="name{surfer}">Name</label>
<input bind:value={surfer_list[surfer].name} on:change={capitalize(surfer_list[surfer], "name")} id="name{surfer}" type="text">
<label class="label" for="color{surfer}">Color</label>
<select name="color" id="color{surfer}" bind:value={surfer_list[surfer].color} style="background-color: {surfer_list[surfer].color};">
{#each colors as color}
<option value={color} style="background-color: {color};">{color}</option>
{/each}
<!-- <option value="red" style="background-color: red;">Select color</option> -->
<!-- <option value="red" style="background-color: red;">Red</option>
<option value="blue" style="background-color: blue;">Blue</option>
<option value="green" style="background-color: green;">Green</option>
<option value="yellow" style="background-color: yellow;">Yellow</option>
<option value="orange" style="background-color: orange;">Orange</option>
<option value="violet" style="background-color: violet;">Violet</option> -->
</select>
<!-- <input bind:value={surfer_list[surfer].color} type="color" id="color{surfer}"> -->
</div>
{/each}
</div>
<button class="plus" on:click={() => {save();}}>SAVE</button>
<button class="plus" on:click={() => {resetHeat();}}>RESET</button>
<hr>
{#each heats as h, id}
<div class="surfer">
<button on:click={() => {setHeat(id);}}>{h.name} {h.number} {h.category}</button>
<button class="plus" on:click={() => {deleteHeat(id);}}>X</button>
</div>
{/each}
<!-- <hr> -->
<!-- <h2>{JSON.stringify(surfer_list)}</h2> -->
<style>
.container {
display: block;
}
.header {
background-color: black;
display: flex;
width: 100%;
justify-content: space-between;
text-align: center;
align-items: center;
}
.header .img {
height: 3rem;
}
.header .title {
font-size: 3rem;
margin: 0 auto;
}
.label {
border: 2px solid #555;
background-color: gray;
border-radius: 8px;
padding: 2px;
}
.heat {
font-size: 1.3rem;
margin-top: 8px;
margin-bottom: 2px;
width: 100%;
margin-left: auto;
margin-right: auto;
/* color: lightcyan; */
display: inline-block;
}
.heat input {
font-size: 1.2rem;
border-radius: 6px;
margin-left: 0.1rem;
margin-right: 0.1rem;
padding-top: 2px;
padding-bottom: 2px;
}
.heat select {
font-size: 1.2rem;
border-radius: 6px;
margin-left: 0.1rem;
margin-right: 0.1rem;
padding-top: 2px;
padding-bottom: 2px;
}
.surfer {
font-size: 1.3rem;
margin-top: 2px;
margin-bottom: 2px;
/* color: lightcyan; */
display: inline-flex;
width: 100%;
margin-left: auto;
margin-right: auto;
border-radius: 8px;
}
.surfer input {
font-size: 1.2rem;
border-radius: 6px;
margin-left: 0.1rem;
margin-right: 0.1rem;
padding-top: 2px;
padding-bottom: 2px;
}
.surfer select {
font-size: 1.2rem;
}
.plus {
border-radius: 8px;
margin-left: 0.2rem;
margin-right: 0.2rem;
margin-bottom: 8px;
margin-top: 8px;
}
.surfers {
border: 1px solid #111;
background-color: yellow;
border-radius: 8px;
padding: 5px;
}
</style>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -1,22 +0,0 @@
import { vitePreprocess } from '@sveltejs/kit/vite';
// import adapter from '@sveltejs/adapter-auto';
import adapter from '@sveltejs/adapter-static';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter({
pages: '../backend/static',
assets: '../backend/static',
fallback: 'index.html',
precompress: true,
strict: true
})
},
preprocess: [vitePreprocess({})]
};
export default config;

View file

@ -1,14 +0,0 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()],
css: {
preprocessorOptions: {
sass: {
additionalData: '@use "src/variables.sass" as *'
}
}
}
});

View file

@ -1,13 +0,0 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

View file

@ -1,15 +0,0 @@
/** @type { import("eslint").Linter.FlatConfig } */
module.exports = {
root: true,
extends: ['eslint:recommended', 'plugin:svelte/recommended', 'prettier'],
parserOptions: {
sourceType: 'module',
ecmaVersion: 2020,
extraFileExtensions: ['.svelte']
},
env: {
browser: true,
es2017: true,
node: true
}
};

10
frontend/.gitignore vendored
View file

@ -1,10 +0,0 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

View file

@ -1 +0,0 @@
engine-strict=true

View file

@ -1,4 +0,0 @@
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

View file

@ -1,8 +0,0 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

View file

@ -1,38 +0,0 @@
# create-svelte
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm create svelte@latest
# create a new project in my-app
npm create svelte@latest my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```bash
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.

View file

@ -1,26 +0,0 @@
{
"name": "frontend",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev --host",
"build": "vite build",
"preview": "vite preview --host",
"lint": "prettier --check . && eslint .",
"format": "prettier --write ."
},
"devDependencies": {
"@sveltejs/adapter-auto": "^3.1.0",
"@sveltejs/adapter-static": "^3.0.1",
"@sveltejs/kit": "^2.1.0",
"@sveltejs/vite-plugin-svelte": "^3.0.1",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.35.1",
"prettier": "^3.1.1",
"prettier-plugin-svelte": "^3.1.2",
"svelte": "^4.2.8",
"vite": "^5.0.11"
},
"type": "module"
}

1604
frontend/pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -1,15 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div>%sveltekit.body%</div>
</body>
</html>

View file

@ -1,17 +0,0 @@
<script>
export let handleClick;
export let label;
</script>
<button class="button" on:click={handleClick}>{label}</button>
<style>
.button {
border-radius: 8px;
margin-left: 0.2rem;
margin-right: 0.2rem;
margin-bottom: 8px;
margin-top: 8px;
width: fit-content;
}
</style>

View file

@ -1,30 +0,0 @@
<script>
import { colors } from '$lib/stores/colors.js';
export let label;
export let id;
export let value;
</script>
<label class="label" for={id}>{label}</label>
<select name={id} {id} bind:value style="background-color: {value}">
{#each $colors as color}
<option value={color} style="background-color: {color}">{color}</option>
{/each}
</select>
<style>
.label {
border: 2px solid #555;
background-color: gray;
border-radius: 8px;
padding: 2px;
width: fit-content;
}
select {
font-size: 1.2rem;
width: fit-content;
border-radius: 4px;
}
</style>

View file

@ -1,29 +0,0 @@
<script>
import Logo from "$lib/img/topscorer_logo_web.png"
export let title = "Setup";
</script>
<div class="header">
<a href="/"><img src={Logo} alt="logo"></a>
<span class="title">{title}</span>
</div>
<style>
.header {
background-color: black;
display:grid;
grid-template-columns: auto 2fr;
align-items: center;
}
.header img {
height: 3rem;
}
.header .title {
font-size: 1.5em;
color: aliceblue;
margin: 0 auto;
}
</style>

View file

@ -1,24 +0,0 @@
<script>
// import { Button, Tooltip } from 'flowbite-svelte';
export let pill = false;
export let color = 'blue';
export let size = 'sm';
export let href = "/login";
</script>
<button size={size} class="!p-2" {pill} color={color} href={href}>
<svg
class="w-6 h-6 text-gray-100 dark:text-white"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fill="currentColor"
d="M10 0a10 10 0 1 0 10 10A10.011 10.011 0 0 0 10 0Zm0 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6Zm0 13a8.949 8.949 0 0 1-4.951-1.488A3.987 3.987 0 0 1 9 13h2a3.987 3.987 0 0 1 3.951 3.512A8.949 8.949 0 0 1 10 18Z"
/>
</svg>
</button>
<!-- <Tooltip id="type-auto" arrow={false} type="custom" defaultClass="" class="p-1 text-sm bg-blue-700 text-gray-100" >Login</Tooltip> -->

View file

@ -1,43 +0,0 @@
<script>
// import { Button, Tooltip } from 'flowbite-svelte';
export let pill = false;
export let color = 'blue';
export let size = 'sm';
</script>
<button {size} class="!p-2" {pill} {color}>
<svg class="w-6 h-6" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
<path
fill="currentColor"
d="m62.578 41.956 -28.444 21.333a3.556 3.556 0 0 1 -4.267 0l-28.444 -21.333a3.637 3.637 0 0 1 -1.244 -3.982A3.605 3.605 0 0 1 3.556 35.556h7.111V32a3.566 3.566 0 0 1 3.556 -3.556h35.556a3.566 3.566 0 0 1 3.556 3.556v3.556h7.111a3.552 3.552 0 0 1 2.133 6.4Z"
/>
<path
fill="currentColor"
x="3"
y="4"
width="12"
height="2"
rx="1"
ry="1"
d="M14.222 14.222H49.778A3.556 3.556 0 0 1 53.333 17.778V17.778A3.556 3.556 0 0 1 49.778 21.333H14.222A3.556 3.556 0 0 1 10.667 17.778V17.778A3.556 3.556 0 0 1 14.222 14.222z"
/>
<path
fill="currentColor"
x="3"
width="12"
height="2"
rx="1"
ry="1"
d="M14.222 0H49.778A3.556 3.556 0 0 1 53.333 3.556V3.556A3.556 3.556 0 0 1 49.778 7.111H14.222A3.556 3.556 0 0 1 10.667 3.556V3.556A3.556 3.556 0 0 1 14.222 0z"
/></svg
>
</button>
<!-- <Tooltip
id="type-auto"
arrow={false}
type="custom"
defaultClass=""
class="p-1 text-sm bg-blue-700 text-gray-100">Priority</Tooltip
> -->

View file

@ -1,32 +0,0 @@
<script>
// import { Button, Tooltip } from 'flowbite-svelte';
export let pill = false;
export let color = 'blue';
export let size = 'sm';
</script>
<button size={size} class="!p-2" {pill} color={color}>
<svg
class="w-6 h-6"
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"
viewBox="0 0 455 455"
style="enable-background:new 0 0 455 455;"
xml:space="preserve"
fill="currentColor"
>
<g>
<rect x="162" y="323" width="293" height="132" />
<rect x="162" y="161" width="293" height="132" />
<rect width="455" height="131" />
<rect y="161" width="132" height="294" />
</g>
</svg>
</button>
<!-- <Tooltip id="type-auto" arrow={false} type="custom" defaultClass="" class="p-1 text-sm bg-blue-700 text-gray-100" >Score</Tooltip> -->

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -1 +0,0 @@
// place files you want to import through the `$lib` alias in this folder.

View file

@ -1,33 +0,0 @@
<script>
export let label;
export let value;
export let id;
function capitalize() {
value = value.charAt(0).toUpperCase() + value.slice(1);
console.log(`element: ${value}`);
}
</script>
<label class="label" for={id}>{label}</label>
<input bind:value on:change={capitalize} {id} type="text" />
<style>
.label {
border: 2px solid #555;
background-color: gray;
border-radius: 8px;
padding: 2px;
width: fit-content;
}
input {
font-size: 1.2rem;
border-radius: 6px;
margin-left: 0.1rem;
margin-right: 0.1rem;
padding-top: 2px;
padding-bottom: 2px;
width: fit-content;
}
</style>

View file

@ -1,31 +0,0 @@
<script>
export let label;
export let id;
export let value;
export let min = 1;
export let max;
export let step = 1;
</script>
<label class="label" for={id}>{label}</label>
<input bind:value {id} type="number" {min} {max} {step} />
<style>
.label {
border: 2px solid #555;
background-color: gray;
border-radius: 8px;
padding: 2px;
width: fit-content;
}
input {
font-size: 1.2rem;
border-radius: 6px;
margin-left: 0.1rem;
margin-right: 0.1rem;
padding-top: 2px;
padding-bottom: 2px;
width: fit-content;
}
</style>

View file

@ -1,44 +0,0 @@
<script>
export let label;
export let id;
export let options = [];
export let value;
export let handleSelect;
export let element;
export let option_label;
export let disabled;
console.log(`options: ${JSON.stringify(options)}`);
</script>
<label class="label" for={id}>{label}</label>
<select name={id} {id} bind:value on:change={handleSelect} {disabled}>
{#each options as option}
{#if element}
{#if option_label}
<option value={option[element]}>{option[option_label]}</option>
{:else}
<option value={option[element]}>{option[element]}</option>
{/if}
{:else if option_label}
<option value={option}>{option[option_label]}</option>
{:else}
<option value={option}>{option}</option>
{/if}
{/each}
</select>
<style>
.label {
border: 2px solid #555;
background-color: gray;
border-radius: 8px;
padding: 2px;
width: fit-content;
}
select {
font-size: 1.2rem;
width: fit-content;
}
</style>

View file

@ -1,12 +0,0 @@
import {readable} from "svelte/store"
export const categories = readable([
"Under 12 Women",
"Under 12 Men",
"Under 14 Women",
"Under 14 Men",
"Under 16 Women",
"Under 16 Men",
"Under 18 Women",
"Under 18 Men",
]);

View file

@ -1,11 +0,0 @@
import { readable } from "svelte/store"
export const colors = readable([
"black",
"blue",
"red",
"green",
"yellow",
"pink",
"white",
]);

View file

@ -1,19 +0,0 @@
import { writable } from 'svelte/store';
function createPriority() {
const { subscribe, set, update } = writable({
surfers: [],
end: false,
start: false,
min: 0,
sec: 0,
})
return {
subscribe,
set,
update,
reset: () => set({ surfers: [], end: false, start: false, min: 0, sec: 0 }),
}
}
export const priority = createPriority()

View file

@ -1,13 +0,0 @@
import {readable} from "svelte/store"
export const rounds = readable([
"Qualifying",
"Opening",
"Elimination",
"Round of 48",
"Round of 32",
"Round of 16",
"Quarterfinal",
"Semifinal",
"Final",
]);

View file

@ -1,29 +0,0 @@
import { writable } from 'svelte/store';
function createSurfers() {
const { subscribe, set, update } = writable([
{ color: 'gray', priority: '' },
{ color: 'gray', priority: '' },
{ color: 'gray', priority: '' },
{ color: 'gray', priority: '' },
{ color: 'gray', priority: '' },
{ color: 'gray', priority: '' },
])
return {
subscribe,
set,
update,
reset: () => set([
{ color: 'gray', priority: '' },
{ color: 'gray', priority: '' },
{ color: 'gray', priority: '' },
{ color: 'gray', priority: '' },
{ color: 'gray', priority: '' },
{ color: 'gray', priority: '' },
]),
}
}
export const surfers = createSurfers()
export const surfersCount = writable(4)

View file

@ -1,3 +0,0 @@
export const prerender = true;
export const ssr = false;
export const trailingSlash = 'always';

View file

@ -1,7 +0,0 @@
<h1>Welcome to SvelteKit</h1>
<a href="light/setup">setup</a>
<br />
<a href="light/h">horizontal</a>
<br />
<a href="light/v">vertical</a>

View file

@ -1,118 +0,0 @@
<script>
import { dev } from '$app/environment';
import { onMount } from 'svelte';
import { surfers, surfersCount } from '$lib/stores/surfers.js';
if (dev) {
console.log('Dev mode');
} else {
console.log('Not dev mode');
}
onMount(async () => {
if (!dev) {
const sse = StartSSE();
return sse;
}
});
function StartSSE() {
let url = '/api/sse?';
for (let e in events) {
url += `event=${events[e]}&`;
}
console.log(`sse url: ${url}`);
const sse = new EventSource(url);
console.log(`subscribe: ${sse}`);
sse.onopen = () => {
console.log(`sse open ${now()}`);
};
sse.addEventListener('priority', (e) => {
let Msg = JSON.parse(e.data);
console.log(JSON.stringify(Msg));
});
return () => {
sse.close();
console.log(`sse closing ${Date.now()}`);
};
}
</script>
<ul>
{#each Array($surfersCount) as _, id}
<li>
{#if $surfers[id].priority == 'P'}
<div class="priority" id="p">{$surfers[id].priority}</div>
{:else}
<div class="priority" id="n">{$surfers[id].priority}</div>
{/if}
<div class="color" style="background-color: {$surfers[id].color}"></div>
</li>
{/each}
</ul>
<style>
:global(body) {
overflow-y: hidden;
overflow-x: hidden;
margin: 0.2vmin;
align-content: center;
justify-content: center;
}
a:link,
a:hover,
a:visited,
a:active {
text-decoration: none;
color: black;
}
ul {
margin: 0;
padding: 0;
}
li {
display: flex;
flex-direction: row;
/* border: 2px solid black; */
/* min-height: 18.8vh; */
/* width: 99.4vw; */
margin-top: 0.2vh;
margin-bottom: 0.2vh;
overflow: hidden;
}
.priority {
text-align: center;
align-self: center;
min-height: 19vh;
min-width: 19vh;
/* border-right: 2px solid gray; */
margin-right: 0.2vw;
background-color: gray;
}
.priority#p {
font-size: 16vmin;
background-color: lightgray;
height: 100%;
}
.priority#n {
font-size: 14vmin;
height: 100%;
}
.color {
width: 100vw;
}
</style>

View file

@ -1,343 +0,0 @@
<script>
import { onMount } from 'svelte';
import { surfers, surfersCount } from '$lib/stores/surfers.js';
import { colors } from '$lib/stores/colors.js';
import { dev } from '$app/environment';
if (dev) {
console.log('Dev mode');
} else {
console.log('Not dev mode');
}
const events = ['priority'];
$: start = false;
// $: surfers_tot = 6;
let footer_height = 8;
$: setup_height = ((100 - footer_height) / $surfersCount) - (2.2 / $surfersCount);
$: if (start && $surfers) {
window.sessionStorage.setItem('priority', JSON.stringify($surfers));
window.sessionStorage.setItem('surfers', JSON.stringify($surfersCount));
window.sessionStorage.setItem('status', JSON.stringify(start));
console.log(`saved: ${JSON.stringify($surfers)}`);
}
onMount(async () => {
let ses = window.sessionStorage.getItem('priority');
if (ses) {
$surfers = JSON.parse(ses);
$surfersCount = JSON.parse(window.sessionStorage.getItem('surfers'));
start = JSON.parse(window.sessionStorage.getItem('status'));
console.log(`loaded: ${JSON.stringify($surfers)}`);
}
if (!dev) {
const sse = StartSSE();
return sse;
}
});
function hasDuplicateColors() {
const colors = [];
console.log(JSON.stringify($surfers));
for (let i = 0; i < $surfersCount; i++) {
const color = $surfers[i].color;
if (colors.includes(color)) {
console.log(`duplicate color: ${color}`);
return true;
}
colors.push(color);
}
return false;
}
function ResetPriority() {
for (let i = 0; i < $surfersCount; i++) {
$surfers[i].priority = '';
}
}
function StartHeat() {
if (hasDuplicateColors()) {
alert('Duplicate colors');
return;
}
ResetPriority();
start = true;
}
function StopHeat() {
window.sessionStorage.clear();
start = false;
}
function AddSurfer() {
if ($surfersCount < 6) {
$surfersCount += 1;
}
}
function RemSurfer() {
if ($surfersCount > 2) {
$surfersCount -= 1;
}
}
function ResetSurfer() {
window.sessionStorage.clear();
location.reload();
}
async function ChangePriority(id) {
console.log($surfers[id]);
if ($surfers[id].priority === 'P') {
for (let i=0; i<$surfersCount; i++) {
if (i != id) {
let pos = parseInt($surfers[i].priority) - 1;
if (pos === 1) {
$surfers[i].priority = 'P';
} else {
$surfers[i].priority = pos.toString();
}
}
}
$surfers[id].priority = $surfersCount.toString();
} else if ($surfers[id].priority === '') {
console.log(`priority empty; pressed: [${id}] ${$surfers[id].priority}`);
for (let i=0; i<$surfersCount; i++) {
console.log(`looping(${id}): ${i} - ${$surfers[i].priority}`);
if (i != id) {
if ($surfers[i].priority === '') {
console.log(`empty: [${i}] ${$surfers[i].priority}`);
continue;
} else {
console.log(`not empty: [${i}] ${$surfers[i].priority}`);
let pos = parseInt($surfers[i].priority) - 1;
if (pos === 1) {
$surfers[i].priority = 'P';
} else {
$surfers[i].priority = pos.toString();
}
}
}
$surfers[id].priority = $surfersCount.toString();
}
} else {
console.log(`pressed: [${id}] ${$surfers[id].priority}`);
let oldpos = parseInt($surfers[id].priority);
for (let i=0; i<$surfersCount; i++) {
if (i != id) {
console.log(`pos: [${i}] ${$surfers[i].priority} ${$surfers[i].priority > oldpos}`);
if ($surfers[i].priority != 'P') {
let pos = parseInt($surfers[i].priority);
if (pos > oldpos) {
console.log(`newpos: ${$surfers[i].priority}`);
$surfers[i].priority = (pos - 1).toString();
}
}
} else {
$surfers[i].priority = $surfersCount.toString();
console.log(`last: [${i}] ${$surfers[i].priority}`);
}
}
}
if (!dev) {
const res = await fetch(`/api/priority`, {
method: 'POST',
body: JSON.stringify({
priority: $surfers.map((obj) => obj.priority)
}),
headers: {
'Content-Type': 'application/json'
}
});
console.log(`retval: ${JSON.stringify(res)}`);
}
}
function StartSSE() {
let url = '/api/sse?';
for (let e in events) {
url += `event=${events[e]}&`;
}
console.log(`sse url: ${url}`);
const sse = new EventSource(url);
console.log(`subscribe: ${sse}`);
sse.onopen = () => {
console.log(`sse open ${now()}`);
};
sse.addEventListener('priority', (e) => {
let Msg = JSON.parse(e.data);
console.log(JSON.stringify(Msg));
});
return () => {
sse.close();
console.log(`sse closing ${Date.now()}`);
};
}
</script>
{#if start}
<ul>
{#each Array($surfersCount) as _, id}
<li style="--height:{setup_height}vh">
{#if $surfers[id].priority == 'P'}
<div class="priority" id="p" on:click={() => ChangePriority(id)}>{$surfers[id].priority}</div>
{:else}
<div class="priority" id="n" on:click={() => ChangePriority(id)}>{$surfers[id].priority}</div>
{/if}
<div class="color" style:background-color={$surfers[id].color}>
</div>
</li>
{/each}
</ul>
<div class="footer" style="--height:{footer_height}vh">
<button class="button" on:click={StopHeat}>STOP</button>
</div>
{:else}
<ul>
{#each Array($surfersCount) as _, id}
<li style="--height:{setup_height}vh">
<div class="priority" id="color">
<select name="color{id}" id="{id}" bind:value={$surfers[id].color} style="background-color: {$surfers[id].color};">
{#each $colors as color}
<option value="{color}" style="background-color: {color}"></option>
{/each}
</select>
</div>
<div class="color" style:background-color={$surfers[id].color}></div>
</li>
{/each}
</ul>
<div class="footer" style="--height:{footer_height}vh">
<div class="control">
<button class="button" on:click={AddSurfer}>+</button>
<button class="display">{$surfersCount}</button>
<button class="button" on:click={RemSurfer}>-</button>
</div>
<button class="button" on:click={StartHeat}>START</button>
<div class="command">
<button class="button" on:click={ResetSurfer}>Reset</button>
</div>
</div>
{/if}
<style>
:global(body) {
overflow-y: hidden;
overflow-x: hidden;
margin: 0.2vmin;
align-content: center;
justify-content: center;
}
a:link,
a:hover,
a:visited,
a:active {
text-decoration: none;
color: black;
}
ul {
margin: 0;
padding: 0;
}
li {
display: flex;
flex-direction: row;
margin-top: 0.2vh;
margin-bottom: 0.2vh;
overflow: hidden;
height: var(--height);
}
.priority {
text-align: center;
align-self: center;
min-height: var(--height);
min-width: var(--height);
margin-right: 0.2vw;
background-color: gray;
}
.priority#p {
font-size: 16vmin;
background-color: lightgray;
height: 100%;
}
.priority#n {
font-size: 14vmin;
height: 100%;
}
.priority#color {
background-color: lightgrey;
margin: 0 auto;
display: flex;
justify-content: center;
align-items: center;
}
.priority#color select {
width: 100%;
/* margin-top: 40%; */
font-size: 6vmin;
}
.color {
width: 100vw;
}
.footer {
display: inline-flex;
bottom: 0;
background-color: darkgrey;
/* border: 2px solid black; */
width: 100%;
height: var(--height);
justify-content: center;
align-items: center;
}
.control {
margin-left: 0;
margin-right: auto;
}
.command {
margin-left: auto;
margin-right: 0;
}
.display {
margin-left: 1vmin;
margin-right: 1vmin;
font-size: 2.5vmin;
}
.button {
margin-left: 1vmin;
margin-right: 1vmin;
border-radius: 4px;
border-style: inset;
font-size: 2.5vmin;
}
</style>

View file

@ -1,116 +0,0 @@
<script>
import { dev } from '$app/environment';
import { onMount } from 'svelte';
import { surfers, surfersCount } from '$lib/stores/surfers.js';
if (dev) {
console.log('Dev mode');
} else {
console.log('Not dev mode');
}
onMount(async () => {
if (!dev) {
const sse = StartSSE();
return sse;
}
});
function StartSSE() {
let url = '/api/sse?';
for (let e in events) {
url += `event=${events[e]}&`;
}
console.log(`sse url: ${url}`);
const sse = new EventSource(url);
console.log(`subscribe: ${sse}`);
sse.onopen = () => {
console.log(`sse open ${now()}`);
};
sse.addEventListener('priority', (e) => {
let Msg = JSON.parse(e.data);
console.log(JSON.stringify(Msg));
});
return () => {
sse.close();
console.log(`sse closing ${Date.now()}`);
};
}
</script>
<div class="wall">
{#each Array($surfersCount) as _, id}
<div class="column">
{#if $surfers[id].priority == 'P'}
<div class="priority" id="p">{$surfers[id].priority}</div>
{:else}
<div class="priority" id="n">{$surfers[id].priority}</div>
{/if}
<div class="color" style="background-color: {$surfers[id].color}"></div>
</div>
{/each}
</div>
<style>
:global(body) {
overflow-y: hidden;
}
.wall {
display: flex;
margin: 0;
padding: 0;
}
.column {
display: flex;
flex-direction: column;
flex-grow: 1;
height: 97vh;
margin-right: 0.1vw;
margin-left: 0.1vw;
overflow: hidden;
flex-basis: 0;
/* border: 2px solid black; */
}
.column:first-child {
margin-left: 0;
}
.column:last-child {
margin-right: 0;
}
.priority {
text-align: center;
align-self: center;
line-height: 24vh;
overflow: hidden;
/* border-bottom: 2px solid black; */
}
.priority#p {
font-size: 18vmin;
background-color: lightgray;
width: 100%;
margin-bottom: 0.1vh;
}
.priority#n {
font-size: 14vmin;
background-color: gray;
width: 100%;
margin-bottom: 0.1vh;
}
.color {
flex-grow: 1;
}
</style>

View file

@ -1,102 +0,0 @@
<script>
const lines = 5;
</script>
<div class="container">
<div class="header">
<div class="select">
<select name="heats" id="heats">
<option value="uno">Uno</option>
<option value="due">Due</option>
</select>
</div>
<div class="timer">
<a href="/">00:00</a>
</div>
<div class="button">
<button>START</button>
</div>
</div>
{#each Array(lines) as _}
<div class="line">
<div class="pri">
<h1>P</h1>
</div>
<div class="color">
</div>
</div>
{/each}
</div>
<style>
a:link, a:hover, a:visited, a:active {
text-decoration: none;
color:bisque;
}
.container {
display: grid;
grid-gap: 5px;
grid-auto-flow: row;
}
.header {
display: grid;
grid-template-columns: 1fr 3fr 1fr;
grid-template-areas:
"select timer button";
grid-template-rows: 4rem;
align-items: center;
background-color: darkblue;
}
.select {
grid-area: select;
justify-self: start;
margin-left: 5px;
}
.timer {
grid-area: timer;
text-align: center;
color: beige;
font-size: 3.5rem;
}
.button {
grid-area: button;
justify-self: end;
margin-right: 5px;
}
.line {
display: grid;
grid-gap: 4px;
grid-template-columns: repeat(6, 1fr);
grid-template-rows: 4rem;
grid-template-areas:
"pri color color color color color";
align-items: center;
}
.pri {
grid-area: pri;
width: 100%;
height: 100%;
background-color: red;
text-justify: center;
text-align: center;
font-size: 1.5rem;
border-radius: 10px;
}
.color {
grid-area: color;
width: 100%;
height: 100%;
background-color: blue;
justify-self: end;
}
</style>

Some files were not shown because too many files have changed in this diff Show more