clean
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
|||
backend/static
|
||||
sse/be/static
|
||||
docker/topscorer.tar
|
||||
|
|
5
backend.old/.gitignore
vendored
|
@ -1,5 +0,0 @@
|
|||
.DS_Store
|
||||
/static
|
||||
.env
|
||||
.env.*
|
||||
go.sum
|
|
@ -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": ""
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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": ""
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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": ""
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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": ""
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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": ""
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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": ""
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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": ""
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
)
|
|
@ -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]
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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"})
|
||||
// }
|
|
@ -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"})
|
||||
// }
|
|
@ -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
|
@ -1,5 +0,0 @@
|
|||
static
|
||||
DB
|
||||
backend
|
||||
Heat
|
||||
.vscode
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
)
|
106
backend/go.sum
|
@ -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=
|
179
backend/heats.go
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
package main
|
||||
|
||||
func main() {
|
||||
|
||||
app := InitHttp()
|
||||
|
||||
app.Run()
|
||||
}
|
|
@ -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"})
|
||||
}
|
|
@ -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"})
|
||||
}
|
|
@ -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
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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.old/.gitignore
vendored
|
@ -1,10 +0,0 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
|
@ -1 +0,0 @@
|
|||
engine-strict=true
|
|
@ -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
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-svelte"],
|
||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||
}
|
|
@ -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.
|
|
@ -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"
|
||||
}
|
1588
frontend.old/pnpm-lock.yaml
generated
|
@ -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>
|
|
@ -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> -->
|
|
@ -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
|
||||
> -->
|
|
@ -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> -->
|
Before Width: | Height: | Size: 9.6 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 3.2 KiB |
|
@ -1 +0,0 @@
|
|||
// place files you want to import through the `$lib` alias in this folder.
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
export const prerender = true;
|
||||
export const ssr = false;
|
||||
export const trailingSlash = 'always';
|
|
@ -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>
|
||||
|
||||
|
||||
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
Before Width: | Height: | Size: 1.5 KiB |
|
@ -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;
|
|
@ -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 *'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
|
@ -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
|
|
@ -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
|
@ -1,10 +0,0 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
|
@ -1 +0,0 @@
|
|||
engine-strict=true
|
|
@ -1,4 +0,0 @@
|
|||
# Ignore files for PNPM, NPM and YARN
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
yarn.lock
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-svelte"],
|
||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||
}
|
|
@ -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.
|
|
@ -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
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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> -->
|
|
@ -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
|
||||
> -->
|
|
@ -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> -->
|
Before Width: | Height: | Size: 9.6 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 3.2 KiB |
|
@ -1 +0,0 @@
|
|||
// place files you want to import through the `$lib` alias in this folder.
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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",
|
||||
]);
|
|
@ -1,11 +0,0 @@
|
|||
import { readable } from "svelte/store"
|
||||
|
||||
export const colors = readable([
|
||||
"black",
|
||||
"blue",
|
||||
"red",
|
||||
"green",
|
||||
"yellow",
|
||||
"pink",
|
||||
"white",
|
||||
]);
|
|
@ -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()
|
|
@ -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",
|
||||
]);
|
|
@ -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)
|
|
@ -1,3 +0,0 @@
|
|||
export const prerender = true;
|
||||
export const ssr = false;
|
||||
export const trailingSlash = 'always';
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|