diff --git a/backend/internal/api/modlists.go b/backend/internal/api/modlists.go index 9444ae1..99981a7 100644 --- a/backend/internal/api/modlists.go +++ b/backend/internal/api/modlists.go @@ -1,7 +1,9 @@ package api import ( + "fmt" "net/http" + "strings" "arma3-web-server/internal/models" "arma3-web-server/internal/services" @@ -121,6 +123,25 @@ func (h *Handler) ImportModlist(c *gin.Context) { c.JSON(http.StatusCreated, ml) } +func (h *Handler) ExportModlist(c *gin.Context) { + ml, err := h.modlists.Get(c.Param("id")) + if err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) + return + } + + html, err := services.RenderModlistHTML(ml.Name, ml.Mods) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + filename := fmt.Sprintf("%s.html", strings.ReplaceAll(ml.Name, `"`, `_`)) + c.Header("Content-Type", "text/html; charset=utf-8") + c.Header("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename)) + c.String(http.StatusOK, html) +} + type modStatus struct { ID string `json:"id"` Name string `json:"name"` diff --git a/backend/internal/api/router.go b/backend/internal/api/router.go index 7d2c686..aeac260 100644 --- a/backend/internal/api/router.go +++ b/backend/internal/api/router.go @@ -80,6 +80,7 @@ func (h *Handler) SetupRoutes(r *gin.Engine) { api.GET("/modlists/:id/check", h.CheckModlistMods) api.POST("/modlists/:id/download-missing", h.DownloadMissingMods) api.POST("/modlists/:id/update-all", h.UpdateAllMods) + api.GET("/modlists/:id/export", h.ExportModlist) api.GET("/mods", h.ListMods) api.DELETE("/mods", h.DeleteMod) diff --git a/backend/internal/services/modlist_parser.go b/backend/internal/services/modlist_parser.go index 139f0ec..8e327ac 100644 --- a/backend/internal/services/modlist_parser.go +++ b/backend/internal/services/modlist_parser.go @@ -1,7 +1,9 @@ package services import ( + "bytes" "fmt" + "html/template" "io" "net/url" "path" @@ -11,6 +13,42 @@ import ( "golang.org/x/net/html" ) +const modlistHTMLTmpl = ` + + + + + Arma 3 + + + +

Arma 3 Mods

+

+ To import this preset, drag this file onto the Launcher window. Or click the MODS tab, then PRESET in the top right, then IMPORT at the bottom, and finally select this file. +

+
+ {{range .Mods}} + + + + + {{end}} +
{{.Name}}Steamhttps://steamcommunity.com/sharedfiles/filedetails/?id={{.ID}}
+
+ + +` + func ParseModlistHTML(r io.Reader) (string, []models.ModEntry, error) { doc, err := html.Parse(r) if err != nil { @@ -114,3 +152,32 @@ func FileNameToModlistName(filename string) string { name = strings.ReplaceAll(name, "-", " ") return name } + +type renderMod struct { + Name string + ID string +} + +func RenderModlistHTML(name string, mods []models.ModEntry) (string, error) { + tmpl, err := template.New("modlist").Parse(modlistHTMLTmpl) + if err != nil { + return "", fmt.Errorf("parse template: %w", err) + } + + var renderMods []renderMod + for _, m := range mods { + if m.ID == "" { + continue + } + renderMods = append(renderMods, renderMod{ + Name: m.Name, + ID: m.ID, + }) + } + + var buf bytes.Buffer + if err := tmpl.Execute(&buf, struct{ Mods []renderMod }{renderMods}); err != nil { + return "", fmt.Errorf("execute template: %w", err) + } + return buf.String(), nil +} diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 011c45d..1b528e0 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -64,6 +64,7 @@ export const modlistsApi = { return r.json() as Promise }) }, + exportUrl: (id: string) => `${BASE}/modlists/${id}/export`, checkMods: (id: string) => request(`/modlists/${id}/check`), downloadMissing: (id: string) => request<{ status: string; count?: number }>(`/modlists/${id}/download-missing`, { method: 'POST' }), updateAll: (id: string) => request<{ status: string; count?: number }>(`/modlists/${id}/update-all`, { method: 'POST' }), diff --git a/frontend/src/pages/ModlistEditor.tsx b/frontend/src/pages/ModlistEditor.tsx index 8858b4e..caa4b41 100644 --- a/frontend/src/pages/ModlistEditor.tsx +++ b/frontend/src/pages/ModlistEditor.tsx @@ -112,6 +112,13 @@ export default function ModlistEditor() { )} + + Export +