Create a basic HTML page to permit usage of non-JS devices
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing

This commit is contained in:
nemunaire 2022-12-08 16:44:17 +01:00
parent fa13484718
commit 7df46e03e0
7 changed files with 236 additions and 0 deletions

1
app.go
View File

@ -49,6 +49,7 @@ func NewApp(cfg *config.Config) *App {
// Register routes
ui.DeclareRoutes(router, cfg)
ui.DeclareNoJSRoutes(router, cfg, db, app.ResetTimer)
api.DeclareRoutes(router, cfg, db, app.ResetTimer)
router.GET("/api/version", func(c *gin.Context) {

139
ui/nojs.go Normal file
View File

@ -0,0 +1,139 @@
package ui
import (
"embed"
"fmt"
"html/template"
"net/http"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
"git.nemunai.re/nemunaire/reveil/config"
"git.nemunai.re/nemunaire/reveil/model"
"git.nemunai.re/nemunaire/reveil/player"
)
//go:embed nojs_templates/*
var nojs_tpl embed.FS
func DeclareNoJSRoutes(router *gin.Engine, cfg *config.Config, db *reveil.LevelDBStorage, resetTimer func()) {
templ := template.Must(template.New("").ParseFS(nojs_tpl, "nojs_templates/*.tmpl"))
router.SetHTMLTemplate(templ)
router.GET("/nojs.html", func(c *gin.Context) {
alarm, err := reveil.GetNextAlarm(cfg, db)
if err != nil {
c.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{"errmsg": err.Error()})
return
}
nCycles := int(time.Until(*alarm) / (90 * time.Minute))
nDays := int(time.Until(*alarm) / (24 * time.Hour))
nMinutes := int((time.Until(*alarm) / time.Minute) % 90)
c.HTML(http.StatusOK, "index.tmpl", gin.H{
"nextAlarmDate": alarm.Format("Mon 2"),
"nextAlarmTime": alarm.Format("15:04"),
"sameDay": time.Now().Day() == alarm.Day(),
"nCycles": nCycles,
"nDays": nDays,
"nMinutes": nMinutes,
"isPlaying": player.CommonPlayer != nil,
})
})
router.POST("/nojs.html", func(c *gin.Context) {
var form struct {
Action string `form:"action"`
Time *string `form:"time"`
}
c.Bind(&form)
switch form.Action {
case "new":
if form.Time == nil {
c.HTML(http.StatusBadRequest, "error.tmpl", gin.H{"errmsg": "This time is invalid."})
return
}
alarm := time.Now()
if len(*form.Time) == 2 && (*form.Time)[1] == 'c' {
n, err := strconv.Atoi((*form.Time)[:1])
if err != nil {
c.HTML(http.StatusBadRequest, "error.tmpl", gin.H{"errmsg": fmt.Sprintf("This number of cycle is invalid: %s", err.Error())})
return
}
alarm = alarm.Add((time.Duration(90*n) + 10) * time.Minute)
} else {
tmp := strings.Split(*form.Time, ":")
if len(tmp) != 2 {
c.HTML(http.StatusBadRequest, "error.tmpl", gin.H{"errmsg": "This time is invalid."})
return
}
duration, err := time.ParseDuration(fmt.Sprintf("%sh%sm", tmp[0], tmp[1]))
if err != nil {
c.HTML(http.StatusBadRequest, "error.tmpl", gin.H{"errmsg": fmt.Sprintf("This time is invalid: %s", err.Error())})
return
}
_, offset := alarm.Zone()
alarm = alarm.Truncate(24 * time.Hour).Add(-time.Duration(offset)*time.Second + duration)
}
if time.Now().After(alarm) {
alarm = alarm.Add(24 * time.Hour)
}
if err := reveil.PutAlarmSingle(db, &reveil.AlarmSingle{
Time: alarm,
}); err != nil {
c.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{"errmsg": err.Error()})
return
}
resetTimer()
case "cancel":
err := reveil.DropNextAlarm(cfg, db)
if err != nil {
c.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{"errmsg": err.Error()})
return
}
resetTimer()
case "start":
if player.CommonPlayer == nil {
err := player.WakeUp(cfg)
if err != nil {
c.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{"errmsg": err.Error()})
return
}
} else {
c.HTML(http.StatusBadRequest, "error.tmpl", gin.H{"errmsg": "Player already running"})
return
}
case "nexttrack":
if player.CommonPlayer != nil {
player.CommonPlayer.NextTrack()
}
case "stop":
if player.CommonPlayer != nil {
err := player.CommonPlayer.Stop()
if err != nil {
c.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{"errmsg": err.Error()})
return
}
}
}
c.Redirect(http.StatusFound, "/nojs.html")
})
}

View File

@ -0,0 +1,4 @@
<h1>
Une erreur inattendue s'est produite&nbsp;:
{{ .errmsg }}
</h1>

View File

@ -0,0 +1,73 @@
<h1 style="margin-bottom: 0">
Prochain réveil le&nbsp;:
{{ if .sameDay }}
aujourd'hui
{{ else if lt .nCycles 16 }}
demain
{{ else }}
{{ .nextAlarmDate }}
{{ end }}
à {{ .nextAlarmTime }}
</h1>
<h2 style="color: gray; margin-top: 0; margin-left: 5em">
{{ if gt .nDays 2 }}(dans {{ .nDays }} jours){{ else }}(dans {{ .nCycles }} cycles + {{ .nMinutes }} min){{ end }}
</h2>
<div style="display: flex; gap: 10px;">
<form method="post" action="/nojs.html">
<input type="hidden" name="action" value="cancel">
<button type="submit">
Annuler la prochaine alarme
</button>
</form>
{{ if .isPlaying }}
<form method="post" action="/nojs.html">
<input type="hidden" name="action" value="stop">
<button type="submit">
Arrêter le réveil
</button>
</form>
<form method="post" action="/nojs.html">
<input type="hidden" name="action" value="nexttrack">
<button type="submit">
Prochaine musique
</button>
</form>
{{ else }}
<form method="post" action="/nojs.html">
<input type="hidden" name="action" value="start">
<button type="submit">
Lancer le réveil
</button>
</form>
{{ end }}
</div>
<h3>Programmer une nouvelle alarme</h3>
<div style="display: flex; gap: 10px;">
<form method="post" action="/nojs.html">
<input type="hidden" name="action" value="new">
<input type="time" required name="time">
<button type="submit">
Nouvelle alarme
</button>
</form>
<form method="post" action="/nojs.html">
<input type="hidden" name="action" value="new">
<input type="hidden" name="time" value="5c">
<button type="submit">
+ 5 cycles
</button>
</form>
<form method="post" action="/nojs.html">
<input type="hidden" name="action" value="new">
<input type="hidden" name="time" value="6c">
<button type="submit">
+ 6 cycles
</button>
</form>
</div>

View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="fr" class="d-flex flex-column mh-100 h-100">
<head>
<meta charset="utf-8" />
<meta name="description" content="" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="manifest" href="/manifest.json" />
<meta name="theme-color" content="#ffffff"/>
<link rel="apple-touch-icon" sizes="192x192" href="/img/apple-touch-icon.png">
<meta name="author" content="nemucorp">
<meta name="robots" content="none">
<base href="/">
</head>
<body class="flex-fill d-flex flex-column">
<div class="flex-fill d-flex flex-column justify-content-between" style="min-height: 100%">%sveltekit.body%</div>
</body>
</html>

View File

@ -14,6 +14,7 @@
%sveltekit.head%
</head>
<body class="flex-fill d-flex flex-column">
<noscript>Si la page ne charge pas, essayez <a href="/nojs.html">la version sans JavaScript</a>.</noscript>
<div class="flex-fill d-flex flex-column justify-content-between" style="min-height: 100%">%sveltekit.body%</div>
</body>
</html>

View File