Skip to content

Commit 38c99a8

Browse files
committed
Progress towards multiple input methods
1 parent 3c06b9d commit 38c99a8

15 files changed

Lines changed: 330 additions & 14 deletions

File tree

File renamed without changes.

cmd/server/handleJson.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ package main
33
import (
44
"encoding/json"
55
"net/http"
6+
7+
"github.com/FileFormatInfo/svgan/internal/common"
68
)
79

810
func handleJson(w http.ResponseWriter, r *http.Request, data any) {
911

1012
b, err := json.Marshal(data)
1113
if err != nil {
12-
logger.Error("json.Marshal failed", "error", err, "data", data)
14+
common.Logger.Error("json.Marshal failed", "error", err, "data", data)
1315
b = []byte("{\"success\":false,\"err\":\"json.Marshal failed\"}")
1416
}
1517

cmd/server/logger.go

Lines changed: 0 additions & 8 deletions
This file was deleted.

cmd/server/server.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"os"
66
"strconv"
77

8+
"github.com/FileFormatInfo/svgan/internal/common"
89
"github.com/FileFormatInfo/svgan/ui"
910
)
1011

@@ -21,11 +22,17 @@ func main() {
2122
http.HandleFunc("/favicon.ico", ui.StaticHandler.ServeHTTP)
2223
http.HandleFunc("/favicon.svg", ui.StaticHandler.ServeHTTP)
2324
http.HandleFunc("/images/", ui.StaticHandler.ServeHTTP)
24-
http.HandleFunc("GET /{$}", ui.StaticHandler.ServeHTTP)
25-
http.HandleFunc("POST /{$}", uploadHandler)
25+
http.HandleFunc("/{$}", func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/index.html", http.StatusSeeOther) })
26+
http.HandleFunc("/index.html", func(w http.ResponseWriter, r *http.Request) { ui.RunTemplate(w, r, "index.tmpl", nil) })
27+
http.HandleFunc("GET /upload.html", func(w http.ResponseWriter, r *http.Request) { ui.RunTemplate(w, r, "upload.tmpl", nil) })
28+
http.HandleFunc("POST /upload.html", uploadHandler)
29+
http.HandleFunc("GET /url.html", func(w http.ResponseWriter, r *http.Request) { ui.RunTemplate(w, r, "url.tmpl", nil) })
30+
http.HandleFunc("POST /url.html", uploadHandler)
31+
http.HandleFunc("GET /clipboard.html", func(w http.ResponseWriter, r *http.Request) { ui.RunTemplate(w, r, "clipboard.tmpl", nil) })
32+
http.HandleFunc("POST /clipboard.html", uploadHandler)
2633

2734
err := http.ListenAndServe(listenAddress+":"+strconv.Itoa(listenPort), nil)
2835
if err != nil {
29-
logger.Error("unable to listen", "address", listenAddress, "port", listenPort, "error", err)
36+
common.Logger.Error("unable to listen", "address", listenAddress, "port", listenPort, "error", err)
3037
}
3138
}

cmd/server/uploadHandler.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"net/http"
66

7+
"github.com/FileFormatInfo/svgan/internal/common"
78
svgan "github.com/FileFormatInfo/svgan/lib"
89
)
910

@@ -37,7 +38,7 @@ func uploadHandler(w http.ResponseWriter, r *http.Request) {
3738
return
3839
}
3940

40-
svgInfo, svgErr := svgan.SvgCheck(logger, raw)
41+
svgInfo, svgErr := svgan.SvgCheck(common.Logger, raw)
4142
if svgErr != nil {
4243
fmt.Fprintf(w, "ERROR: Parsing the SVG (%v)\n", svgErr)
4344
return

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/FileFormatInfo/svgan
22

3-
go 1.22.1
3+
go 1.23
44

55
require (
66
github.com/JoshVarga/svgparser v0.0.0-20200804023048-5eaba627a7d1 // indirect

internal/common/logger.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package common
2+
3+
import (
4+
"log/slog"
5+
"os"
6+
)
7+
8+
var Logger = slog.New(slog.NewJSONHandler(os.Stdout, nil))

ui/partials/above.tmpl

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{{define "above"}}
2+
<!DOCTYPE html>
3+
<html lang="en">
4+
<head>
5+
<meta charset="utf-8">
6+
<title>{{if .H1}}{{ .Title }}{{else}}{{ .Title }} - SVG Analyzer{{end}}</title>
7+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
8+
<meta name="referrer" content="unsafe-url" />
9+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
10+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
11+
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
12+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" />
13+
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"
14+
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
15+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
16+
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
17+
crossorigin="anonymous"></script>
18+
<script src="https://cdn.jsdelivr.net/npm/clipboard@2.0.11/dist/clipboard.min.js"></script>
19+
</head>
20+
21+
<body>
22+
<nav class="navbar navbar-expand d-flex justify-content-between d-print-none border-bottom bg-body-tertiary pb-1 mb-2">
23+
<div class="container">
24+
<div class="navbar-nav">
25+
<a class="navbar-brand h1 mb-0" href="/" title="SVG Analyzer"><span class="d-sm-none">BIMI</span><span class="d-none d-sm-inline"><img
26+
alt="SVG Analyzer Logo" class="navbar-logo me-2" src="/favicon.svg" style="height:1em;vertical-align:middle;"/>SVG Analyzer</span></a>
27+
</div>
28+
<div class="navbar-nav">
29+
<a class="nav-link" href="/">Home</a>
30+
{{range .crumbtrail}}
31+
<span class="navbar-text font-weight-bold">&nbsp;&raquo;&nbsp;</span><a class="nav-link text-nobreak"
32+
href="{{.URL}}">{{.Text}}</a>
33+
{{end}}
34+
</div>
35+
</div>
36+
</nav>
37+
38+
<div class="container">
39+
<h1>
40+
{{if .H1}}{{.H1}}{{else}}{{ .Title }}{{end}}
41+
</h1>
42+
<hr />
43+
{{- if (ne .Err nil) }}
44+
<div class="alert alert-danger">
45+
{{.Err}}
46+
</div>
47+
{{- end}}
48+
{{end}}

ui/partials/below.tmpl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{{define "below"}}
2+
<hr />
3+
<footer class="d-flex justify-content-center">
4+
<small>
5+
<a href="https://github.com/FileFormatInfo/svgan">Source</a>
6+
| <a href="https://www.fileformat.info/">FileFormat.Info</a>
7+
| <a href="https://www.svg.zone/">SVG Zone</a>
8+
</small>
9+
</footer>
10+
</div>
11+
<!--/container-->
12+
</body>
13+
</html>
14+
{{end}}

ui/templates.go

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package ui
2+
3+
import (
4+
"bytes"
5+
"embed"
6+
"html/template"
7+
"io/fs"
8+
"net/http"
9+
"strings"
10+
11+
"github.com/FileFormatInfo/svgan/internal/common"
12+
)
13+
14+
//go:embed partials
15+
var partialsFS embed.FS
16+
17+
//go:embed all:views
18+
var viewsFS embed.FS
19+
20+
type TemplateFunc func(data any) (string, error)
21+
22+
type TemplateData map[string]any
23+
24+
var templateCache = initTemplates()
25+
26+
func initTemplates() map[string]TemplateFunc {
27+
28+
funcMap := template.FuncMap{
29+
"dec": func(i int) int {
30+
return i - 1
31+
},
32+
"inc": func(i int) int {
33+
return i + 1
34+
},
35+
"loop": func(from, to int) []int {
36+
result := []int{}
37+
for i := from; i < to; i++ {
38+
result = append(result, i)
39+
}
40+
return result
41+
},
42+
}
43+
44+
theCache := make(map[string]TemplateFunc)
45+
46+
var partials bytes.Buffer
47+
48+
partialErr := fs.WalkDir(partialsFS, ".", func(path string, d fs.DirEntry, err error) error {
49+
if err != nil {
50+
return err
51+
}
52+
if !d.IsDir() {
53+
// read the partials file and append to partials
54+
content, readErr := fs.ReadFile(partialsFS, path)
55+
if readErr != nil {
56+
common.Logger.Error("unable to read partials file", "err", readErr, "filename", path)
57+
return err
58+
}
59+
partials.Write(content)
60+
}
61+
return nil
62+
})
63+
if partialErr != nil {
64+
common.Logger.Error("unable to register partials", "err", partialErr)
65+
}
66+
67+
viewErr := fs.WalkDir(viewsFS, ".", func(path string, d fs.DirEntry, err error) error {
68+
if err != nil {
69+
common.Logger.Error("walkdir error", "err", err)
70+
return err
71+
}
72+
if !d.IsDir() {
73+
common.Logger.Debug("registering view", "filename", path)
74+
content, readErr := fs.ReadFile(viewsFS, path)
75+
if readErr != nil {
76+
common.Logger.Error("unable to read view file", "err", readErr, "filename", path)
77+
return err
78+
}
79+
name := path[len("views/"):]
80+
81+
var templateBuffer bytes.Buffer
82+
templateBuffer.Write(content)
83+
templateBuffer.Write(partials.Bytes())
84+
t := template.New(name).Funcs(funcMap)
85+
template, parseErr := t.Parse(templateBuffer.String())
86+
if parseErr != nil {
87+
common.Logger.Error("unable to parse template", "err", parseErr, "filename", path, "content", string(content))
88+
return parseErr
89+
}
90+
theCache[name] = func(data any) (string, error) {
91+
var buf bytes.Buffer
92+
err := template.Execute(&buf, data)
93+
if err != nil {
94+
common.Logger.Error("unable to execute template", "err", err, "filename", path, "content", string(content))
95+
return "", err
96+
}
97+
return buf.String(), nil
98+
}
99+
}
100+
return nil
101+
})
102+
if viewErr != nil {
103+
common.Logger.Error("unable to register views", "err", viewErr)
104+
}
105+
106+
return theCache
107+
}
108+
109+
type CrumbtrailEntry struct {
110+
Text string
111+
URL string
112+
}
113+
114+
func makeCrumbtrail(r *http.Request) []CrumbtrailEntry {
115+
crumbtrail := []CrumbtrailEntry{}
116+
117+
// Add additional entries based on the request path
118+
path := r.URL.Path
119+
segments := strings.Split(path, "/")
120+
for i := 1; i < len(segments); i++ {
121+
entry := CrumbtrailEntry{
122+
Text: segments[i],
123+
URL: strings.Join(segments[0:i+1], "/"),
124+
}
125+
crumbtrail = append(crumbtrail, entry)
126+
}
127+
128+
return crumbtrail
129+
}
130+
131+
func RunTemplate(w http.ResponseWriter, r *http.Request, templateName string, data TemplateData) {
132+
133+
fn := templateCache[templateName]
134+
if fn == nil {
135+
http.NotFound(w, r)
136+
return
137+
}
138+
if data == nil {
139+
data = make(map[string]any)
140+
}
141+
data["crumbtrail"] = makeCrumbtrail(r)
142+
143+
result, execErr := fn(data)
144+
if execErr != nil {
145+
common.Logger.Error("template failed", "err", execErr, "template", templateName)
146+
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
147+
return
148+
}
149+
w.Write([]byte(result))
150+
}

0 commit comments

Comments
 (0)