updates
This commit is contained in:
parent
18c3bf3dd8
commit
5ad1da76b1
@ -169,7 +169,7 @@ func questionHandler(c *fiber.Ctx, newGame NullableUUID, prevError NullableStrin
|
||||
data["Errors"] = gameSession.QuestionsErrors
|
||||
data["PreviousError"] = prevError.String
|
||||
data["ShortcutKeys"] = []string{"d", "f", "h", "j", "k"}
|
||||
return c.Render("apps/flags/question", data, "layouts/base")
|
||||
return web.Render(c, "apps/flags/question", data, "layouts/base")
|
||||
}
|
||||
|
||||
func sharedGameHandler(c *fiber.Ctx) error {
|
||||
@ -242,7 +242,7 @@ func createSharedGameData(c *fiber.Ctx, tags []string, maxQuestions int, seconds
|
||||
data := *web.Common(c)
|
||||
data["Title"] = "tmp"
|
||||
data["ShareKey"] = shareKey
|
||||
return c.Render("apps/flags/shared", data, "layouts/base")
|
||||
return web.Render(c, "apps/flags/shared", data, "layouts/base")
|
||||
}
|
||||
|
||||
func setupGame(c *fiber.Ctx, tags []string, maxQuestions int, seconds int, gameSeed NullableUUID) error {
|
||||
@ -291,7 +291,7 @@ func gameEndHandler(c *fiber.Ctx) error {
|
||||
data := *web.Common(c)
|
||||
data["Title"] = "tmp"
|
||||
data["Errors"] = gameSession.QuestionsErrors
|
||||
return c.Render("apps/flags/end", data, "layouts/base")
|
||||
return web.Render(c, "apps/flags/end", data, "layouts/base")
|
||||
|
||||
}
|
||||
|
||||
@ -304,5 +304,5 @@ func gameStartHandler(c *fiber.Ctx) error {
|
||||
data := *web.Common(c)
|
||||
data["Title"] = "Flags Game | tijl.dev"
|
||||
data["SupportedTags"] = supportedTags
|
||||
return c.Render("apps/flags/start", data, "layouts/base")
|
||||
return web.Render(c, "apps/flags/start", data, "layouts/base")
|
||||
}
|
||||
|
@ -5,6 +5,8 @@
|
||||
"max_questions": "Max Questions",
|
||||
"time_limit": "Time limit (seconds)",
|
||||
"share_game": "Save",
|
||||
"deselect_all": "Deselect all",
|
||||
"select_all": "Select all",
|
||||
"Asia": "Asia",
|
||||
"MiddleEast": "Middle East",
|
||||
"SoutheastAsia": "Southeast Asia",
|
||||
|
@ -5,6 +5,8 @@
|
||||
"time_limit": "Tijdlimit (seconden)",
|
||||
"max_questions": "Maximaal aantal vragen",
|
||||
"share_game": "Opslaan",
|
||||
"deselect_all": "Alles deselecteren",
|
||||
"select_all": "Alles selecteren",
|
||||
"Asia": "Azië",
|
||||
"MiddleEast": "Midden-Oosten",
|
||||
"SoutheastAsia": "Zuidoost-Azië",
|
||||
|
@ -1,200 +1,200 @@
|
||||
package uploader
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"embed"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
"context"
|
||||
"database/sql"
|
||||
"embed"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"git.tijl.dev/tijl/tijl.dev-core/internal/config"
|
||||
"git.tijl.dev/tijl/tijl.dev-core/internal/queries"
|
||||
"git.tijl.dev/tijl/tijl.dev-core/internal/user"
|
||||
"git.tijl.dev/tijl/tijl.dev-core/modules/db"
|
||||
"git.tijl.dev/tijl/tijl.dev-core/modules/i18n"
|
||||
"git.tijl.dev/tijl/tijl.dev-core/modules/web"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"git.tijl.dev/tijl/tijl.dev-core/internal/config"
|
||||
"git.tijl.dev/tijl/tijl.dev-core/internal/queries"
|
||||
"git.tijl.dev/tijl/tijl.dev-core/internal/user"
|
||||
"git.tijl.dev/tijl/tijl.dev-core/modules/db"
|
||||
"git.tijl.dev/tijl/tijl.dev-core/modules/i18n"
|
||||
"git.tijl.dev/tijl/tijl.dev-core/modules/web"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
//go:embed locales/*
|
||||
var Embed embed.FS
|
||||
|
||||
func Setup() {
|
||||
i18n.RegisterTranslations(Embed, "locales")
|
||||
i18n.RegisterTranslations(Embed, "locales")
|
||||
|
||||
var uploadDir string = filepath.Join(config.Config.DataLocation, "./uploader")
|
||||
var uploadDir string = filepath.Join(config.Config.DataLocation, "./uploader")
|
||||
|
||||
web.RegisterAppSetupFunc(func(a *fiber.App) {
|
||||
a.Get("/app/uploader", func(c *fiber.Ctx) error {
|
||||
_, err := user.GetSession(c)
|
||||
if err != nil {
|
||||
return c.Redirect("/auth?redirect=/app/uploader", http.StatusUnauthorized)
|
||||
}
|
||||
data := *web.Common(c)
|
||||
data["Title"] = "tmp"
|
||||
return c.Render("apps/uploader/index", data, "layouts/base")
|
||||
})
|
||||
a.Get("/app/uploader/:key", func(c *fiber.Ctx) error {
|
||||
encryptionKey, err := hex.DecodeString(c.Params("key"))
|
||||
if err != nil {
|
||||
return c.Next()
|
||||
}
|
||||
storageKey := hashKey(encryptionKey)
|
||||
|
||||
web.RegisterAppSetupFunc(func(a *fiber.App) {
|
||||
a.Get("/app/uploader", func(c *fiber.Ctx) error {
|
||||
_, err := user.GetSession(c)
|
||||
if err != nil {
|
||||
return c.Redirect("/auth?redirect=/app/uploader", http.StatusUnauthorized)
|
||||
}
|
||||
data := *web.Common(c)
|
||||
data["Title"] = "tmp"
|
||||
return c.Render("apps/uploader/index", data, "layouts/base")
|
||||
})
|
||||
a.Get("/app/uploader/:key", func(c *fiber.Ctx) error {
|
||||
encryptionKey, err := hex.DecodeString(c.Params("key"))
|
||||
if err != nil {
|
||||
return c.Next()
|
||||
}
|
||||
storageKey := hashKey(encryptionKey)
|
||||
row, err := db.Queries.AppUploaderGet(context.TODO(), storageKey)
|
||||
if err != nil {
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
row, err := db.Queries.AppUploaderGet(context.TODO(), storageKey)
|
||||
if err != nil {
|
||||
return c.Next()
|
||||
}
|
||||
if row.Expire.UnixMilli() < time.Now().UnixMilli() {
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
if row.Expire.UnixMilli() < time.Now().UnixMilli() {
|
||||
return c.Next()
|
||||
}
|
||||
accessCount, err := db.Queries.AppUploaderAccessCount(context.TODO(), row.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
accessCount, err := db.Queries.AppUploaderAccessCount(context.TODO(), row.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if accessCount == int64(row.MaxVisits) || accessCount > int64(row.MaxVisits) {
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
if accessCount == int64(row.MaxVisits) || accessCount > int64(row.MaxVisits) {
|
||||
return c.Next()
|
||||
}
|
||||
createLog := queries.AppUploaderAccessCreateParams{
|
||||
FileID: row.ID,
|
||||
IpAddress: c.IP(),
|
||||
Agent: string(c.Context().UserAgent()),
|
||||
}
|
||||
|
||||
createLog := queries.AppUploaderAccessCreateParams{
|
||||
FileID: row.ID,
|
||||
IpAddress: c.IP(),
|
||||
Agent: string(c.Context().UserAgent()),
|
||||
}
|
||||
user, err := user.GetSession(c)
|
||||
if err == nil {
|
||||
createLog.Uid = sql.NullString{
|
||||
Valid: true,
|
||||
String: user,
|
||||
}
|
||||
}
|
||||
|
||||
user, err := user.GetSession(c)
|
||||
if err == nil {
|
||||
createLog.Uid = sql.NullString{
|
||||
Valid: true,
|
||||
String: user,
|
||||
}
|
||||
}
|
||||
db.Queries.AppUploaderAccessCreate(context.TODO(), createLog)
|
||||
|
||||
db.Queries.AppUploaderAccessCreate(context.TODO(), createLog)
|
||||
encryptedContent, err := os.ReadFile(filepath.Join(uploadDir, row.ID.String()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
encryptedContent, err := os.ReadFile(filepath.Join(uploadDir, row.ID.String()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
encryptedMetadata, err := os.ReadFile(filepath.Join(uploadDir, row.ID.String()+".meta"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
encryptedMetadata, err := os.ReadFile(filepath.Join(uploadDir, row.ID.String()+".meta"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
decryptedContent, err := decryptData(encryptedContent, encryptionKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
decryptedContent, err := decryptData(encryptedContent, encryptionKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
decryptedMetadata, err := decryptData(encryptedMetadata, encryptionKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
decryptedMetadata, err := decryptData(encryptedMetadata, encryptionKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var metadata map[string]interface{}
|
||||
if err := json.Unmarshal(decryptedMetadata, &metadata); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).SendString("Failed to parse metadata")
|
||||
}
|
||||
|
||||
var metadata map[string]interface{}
|
||||
if err := json.Unmarshal(decryptedMetadata, &metadata); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).SendString("Failed to parse metadata")
|
||||
}
|
||||
c.Set("Content-Disposition", "attachment; filename="+metadata["filename"].(string))
|
||||
c.Set("Content-Type", metadata["content_type"].(string))
|
||||
|
||||
c.Set("Content-Disposition", "attachment; filename="+metadata["filename"].(string))
|
||||
c.Set("Content-Type", metadata["content_type"].(string))
|
||||
return c.Send(decryptedContent)
|
||||
})
|
||||
|
||||
return c.Send(decryptedContent)
|
||||
})
|
||||
a.Post("/app/uploader", func(c *fiber.Ctx) error {
|
||||
uid, err := user.GetSession(c)
|
||||
if err != nil {
|
||||
return c.SendStatus(http.StatusUnauthorized)
|
||||
}
|
||||
expireDays, err := strconv.Atoi(c.FormValue("expire_days"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
maxDownloads, err := strconv.Atoi(c.FormValue("max_downloads"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).SendString("Failed to read file")
|
||||
}
|
||||
|
||||
a.Post("/app/uploader", func(c *fiber.Ctx) error {
|
||||
uid, err := user.GetSession(c)
|
||||
if err != nil {
|
||||
return c.SendStatus(http.StatusUnauthorized)
|
||||
}
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).SendString("Failed to open file")
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
expireDays, err := strconv.Atoi(c.FormValue("expire_days"))
|
||||
if err != nil {return err}
|
||||
maxDownloads, err := strconv.Atoi(c.FormValue("max_downloads"))
|
||||
if err != nil {return err}
|
||||
// Read file content into a byte slice
|
||||
fileContent := make([]byte, file.Size)
|
||||
if _, err := src.Read(fileContent); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).SendString("Failed to read file content")
|
||||
}
|
||||
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).SendString("Failed to read file")
|
||||
}
|
||||
// Generate encryption key
|
||||
encryptionKey, err := generateEncryptionKey()
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).SendString("Failed to generate encryption key")
|
||||
}
|
||||
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).SendString("Failed to open file")
|
||||
}
|
||||
defer src.Close()
|
||||
// Encrypt the file content
|
||||
encryptedContent, err := encryptData(fileContent, encryptionKey)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).SendString("Failed to encrypt file content")
|
||||
}
|
||||
|
||||
// Read file content into a byte slice
|
||||
fileContent := make([]byte, file.Size)
|
||||
if _, err := src.Read(fileContent); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).SendString("Failed to read file content")
|
||||
}
|
||||
storageKey := hashKey(encryptionKey)
|
||||
|
||||
// Generate encryption key
|
||||
encryptionKey, err := generateEncryptionKey()
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).SendString("Failed to generate encryption key")
|
||||
}
|
||||
id, err := db.Queries.AppUploaderCreate(context.TODO(), queries.AppUploaderCreateParams{
|
||||
Uid: uid,
|
||||
FileCrypto: storageKey,
|
||||
Expire: time.Now().AddDate(0, 0, expireDays),
|
||||
MaxVisits: int32(maxDownloads),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Encrypt the file content
|
||||
encryptedContent, err := encryptData(fileContent, encryptionKey)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).SendString("Failed to encrypt file content")
|
||||
}
|
||||
// Save encrypted file content
|
||||
if err := os.WriteFile(filepath.Join(uploadDir, id.String()), encryptedContent, 0644); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).SendString("Failed to save encrypted file")
|
||||
}
|
||||
|
||||
storageKey := hashKey(encryptionKey)
|
||||
metadata := map[string]interface{}{
|
||||
"filename": file.Filename,
|
||||
"content_type": file.Header.Get("Content-Type"),
|
||||
}
|
||||
|
||||
id, err := db.Queries.AppUploaderCreate(context.TODO(), queries.AppUploaderCreateParams{
|
||||
Uid: uid,
|
||||
FileCrypto: storageKey,
|
||||
Expire: time.Now().AddDate(0, 0, expireDays),
|
||||
MaxVisits: int32(maxDownloads),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
metadataBytes, err := json.Marshal(metadata)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).SendString("Failed to marshal metadata")
|
||||
}
|
||||
|
||||
// Save encrypted file content
|
||||
if err := os.WriteFile(filepath.Join(uploadDir, id.String()), encryptedContent, 0644); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).SendString("Failed to save encrypted file")
|
||||
}
|
||||
encryptedMetadata, err := encryptData(metadataBytes, encryptionKey)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).SendString("Failed to encrypt metadata")
|
||||
}
|
||||
|
||||
metadata := map[string]interface{}{
|
||||
"filename": file.Filename,
|
||||
"content_type": file.Header.Get("Content-Type"),
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(uploadDir, id.String()+".meta"), encryptedMetadata, 0644); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).SendString("Failed to save encrypted metadata")
|
||||
}
|
||||
|
||||
metadataBytes, err := json.Marshal(metadata)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).SendString("Failed to marshal metadata")
|
||||
}
|
||||
data := *web.Common(c)
|
||||
data["Title"] = "tmp"
|
||||
data["Key"] = hex.EncodeToString(encryptionKey)
|
||||
return c.Render("apps/uploader/uploaded", data, "layouts/base")
|
||||
})
|
||||
|
||||
encryptedMetadata, err := encryptData(metadataBytes, encryptionKey)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).SendString("Failed to encrypt metadata")
|
||||
}
|
||||
|
||||
if err := os.WriteFile(filepath.Join(uploadDir, id.String()+".meta"), encryptedMetadata, 0644); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).SendString("Failed to save encrypted metadata")
|
||||
}
|
||||
|
||||
data := *web.Common(c)
|
||||
data["Title"] = "tmp"
|
||||
data["Key"] = hex.EncodeToString(encryptionKey)
|
||||
return c.Render("apps/uploader/uploaded", data, "layouts/base")
|
||||
})
|
||||
|
||||
|
||||
}, 1000)
|
||||
}, 1000)
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
|
||||
type Service struct {
|
||||
Name string
|
||||
Slug string
|
||||
Description string
|
||||
Url string
|
||||
InfoUrl string
|
||||
@ -16,38 +17,46 @@ type Service struct {
|
||||
Scale float64
|
||||
}
|
||||
|
||||
var services = map[string]Service{
|
||||
"immich": {
|
||||
var services = []Service{
|
||||
{
|
||||
Name: "Immich",
|
||||
Slug: "immich",
|
||||
Description: "...",
|
||||
Url: "https://fotos.tijl.dev/photos",
|
||||
InfoUrl: "https://immich.app",
|
||||
Color: "white",
|
||||
Scale: 0,
|
||||
},
|
||||
"element": {
|
||||
{
|
||||
Name: "Element",
|
||||
Slug: "element",
|
||||
Description: "...",
|
||||
Url: "https://element.tijl.dev",
|
||||
InfoUrl: "https://matrix.org",
|
||||
Color: "#0DBD8B",
|
||||
Scale: 0.1,
|
||||
},
|
||||
"nextcloud": {
|
||||
{
|
||||
Name: "Nextcloud",
|
||||
Slug: "nextcloud",
|
||||
Description: "...",
|
||||
Url: "https://cloud.tijl.dev",
|
||||
InfoUrl: "https://nextcloud.com",
|
||||
Color: "#00679e",
|
||||
Scale: -0.2,
|
||||
},
|
||||
{
|
||||
Name: "Gitea",
|
||||
Slug: "gitea",
|
||||
Description: "...",
|
||||
Url: "https://git.tijl.dev",
|
||||
InfoUrl: "https://about.gitea.com",
|
||||
Color: "white",
|
||||
Scale: -0.2,
|
||||
},
|
||||
}
|
||||
|
||||
func servicesHandler(c *fiber.Ctx) error {
|
||||
_, err := user.GetSession(c)
|
||||
if err != nil {
|
||||
return c.Next()
|
||||
}
|
||||
data := *web.Common(c)
|
||||
data["Title"] = i18n.Translate(c, "services")
|
||||
data["Services"] = services
|
||||
@ -59,9 +68,11 @@ func serviceHandler(c *fiber.Ctx) error {
|
||||
if err != nil {
|
||||
return c.Next()
|
||||
}
|
||||
if services[c.Params("service")].Url != "" {
|
||||
return c.Redirect(services[c.Params("service")].Url)
|
||||
}
|
||||
/*
|
||||
if services[c.Params("service")].Url != "" {
|
||||
return c.Redirect(services[c.Params("service")].Url)
|
||||
}
|
||||
*/
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
@ -70,8 +81,10 @@ func serviceInfoHandler(c *fiber.Ctx) error {
|
||||
if err != nil {
|
||||
return c.Next()
|
||||
}
|
||||
if services[c.Params("service")].Url != "" {
|
||||
return c.Redirect(services[c.Params("service")].InfoUrl)
|
||||
}
|
||||
/*
|
||||
if services[c.Params("service")].Url != "" {
|
||||
return c.Redirect(services[c.Params("service")].InfoUrl)
|
||||
}
|
||||
*/
|
||||
return c.Next()
|
||||
}
|
||||
|
@ -70,6 +70,7 @@ func Listen() {
|
||||
app := fiber.New(fiber.Config{
|
||||
Views: engine,
|
||||
DisableStartupMessage: true,
|
||||
BodyLimit: 4 * 1024 * 1024 * 1024,
|
||||
})
|
||||
app.Use(fiberzerolog.New(fiberzerolog.Config{
|
||||
Logger: &log.Logger,
|
||||
|
10
modules/web/render.go
Normal file
10
modules/web/render.go
Normal file
@ -0,0 +1,10 @@
|
||||
package web
|
||||
|
||||
import "github.com/gofiber/fiber/v2"
|
||||
|
||||
func Render(c *fiber.Ctx, template string, data fiber.Map, layout string) error {
|
||||
if c.Get("HX-Request") != "" && c.Get("HX-Target") == "main-content" {
|
||||
return c.Render(template, data)
|
||||
}
|
||||
return c.Render(template, data, layout)
|
||||
}
|
@ -6,7 +6,7 @@
|
||||
|
||||
<div>game is over</div>
|
||||
|
||||
<form method="post" hx-boost="true">
|
||||
<form method="post" hx-boost="true" hx-target="#main-content">
|
||||
<input class="hidden" name="type" value="exit" />
|
||||
<button type="submit" class="btn">exit</button>
|
||||
</form>
|
||||
|
@ -18,7 +18,7 @@
|
||||
<span style="font-size: 105px;">{{.Flag}}</span>
|
||||
</div>
|
||||
|
||||
<div hx-boost="true" id="answers" class="flex justify-center flex-wrap">
|
||||
<div hx-target="#main-content" hx-boost="true" id="answers" class="flex justify-center flex-wrap">
|
||||
{{range $index, $answer := .Answers}}
|
||||
<form method="post" class="m-1">
|
||||
<input class="hidden" name="type" value="answer" />
|
||||
@ -34,7 +34,7 @@
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<form hx-boost="true" method="post">
|
||||
<form hx-boost="true" hx-target="#main-content" method="post">
|
||||
<input class="hidden" name="type" value="exit" />
|
||||
<button type="submit" class="btn hover:btn-error">Stop</button>
|
||||
</form>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<div>
|
||||
<div class="text-center text-2xl my-4">{{.ShareKey}}</div>
|
||||
|
||||
<form hx-boost="true" method="get" class="justify-center flex mt-4">
|
||||
<form hx-target="#main-content" hx-boost="true" method="get" class="justify-center flex mt-4">
|
||||
<button type="submit" class="btn text-center">OK</button>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -1,4 +1,4 @@
|
||||
<div hx-boost="true">
|
||||
<div hx-target="#main-content" hx-boost="true">
|
||||
|
||||
<div id="error-message" class="text-red-500 mb-4"></div>
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
<form enctype="multipart/form-data" method="post" hx-boost="true">
|
||||
<form enctype="multipart/form-data" method="post">
|
||||
<div>
|
||||
<label class="input input-bordered flex items-center gap-2 mt-2">
|
||||
Max downloaders
|
||||
|
@ -16,7 +16,7 @@
|
||||
<header>
|
||||
{{template "partials/menu" .}}
|
||||
</header>
|
||||
<main class="mx-6 pt-24 pb-4">
|
||||
<main id="main-content" class="mx-6 pt-24 pb-4">
|
||||
{{embed}}
|
||||
</main>
|
||||
</body>
|
||||
|
@ -5,18 +5,18 @@
|
||||
|
||||
<div class="mt-4 flex justify-center">
|
||||
<div class="flex gap-2">
|
||||
{{range $key, $value := .Services}}
|
||||
<a href="#{{$key}}" id="service-{{$key}}" class="group"
|
||||
{{range .Services}}
|
||||
<a href="#{{.Slug}}" id="service-{{.Slug}}" class="group"
|
||||
onmouseover="this.querySelector('img').style.transform='scale({{incfloat .Scale 1.1}})';"
|
||||
onmouseout="this.querySelector('img').style.transform='scale({{incfloat .Scale 1}})';">
|
||||
<div class="relative w-24 h-24 rounded-2xl rounded-3xl shadow-2xl" style="background-color: {{.Color}};">
|
||||
<div class="absolute inset-0 flex items-center justify-center">
|
||||
<img alt="{{.Name}} logo"
|
||||
class="text-white transform transition-transform duration-300 drop-shadow-2xl"
|
||||
style="transform: scale({{incfloat .Scale 1}});" src="/static/assets/{{$key}}.svg" />
|
||||
style="transform: scale({{incfloat .Scale 1}});" src="/static/assets/{{.Slug}}.svg" />
|
||||
</div>
|
||||
</div>
|
||||
<div id="service-{{$key}}-label" class="w-24 text-center mt-2 rounded-2xl rounded-b-none py-1">
|
||||
<div id="service-{{.Slug}}-label" class="w-24 text-center mt-2 rounded-2xl rounded-b-none py-1">
|
||||
{{.Name}}
|
||||
</div>
|
||||
</a>
|
||||
@ -25,13 +25,13 @@
|
||||
</div>
|
||||
|
||||
<div class="card bg-base-300" id="services-info">
|
||||
{{range $key, $value := .Services}}
|
||||
<div class="card-body hidden" id="service-{{$key}}-info">
|
||||
{{range .Services}}
|
||||
<div class="card-body hidden" id="service-{{.Slug}}-info">
|
||||
<h2 class="card-title">{{.Name}}</h2>
|
||||
<p>{{.Description}}</p>
|
||||
<div class="card-actions justify-end">
|
||||
<a href="/service/{{$key}}/info" class="btn bg-base-300">Info</a>
|
||||
<a href="/service/{{$key}}" class="btn btn-primary">Open</a>
|
||||
<a href="/service/{{.Slug}}/info" class="btn bg-base-300">Info</a>
|
||||
<a href="/service/{{.Slug}}" class="btn btn-primary">Open</a>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
Loading…
Reference in New Issue
Block a user