moved html and css out to seperate files.
This commit is contained in:
parent
a4c02cdbc1
commit
f9cfd3fc3b
5 changed files with 152 additions and 126 deletions
|
|
@ -17,8 +17,9 @@ import (
|
|||
)
|
||||
|
||||
type Handler struct {
|
||||
dir string
|
||||
tmpl *template.Template
|
||||
dir string
|
||||
tmpl *template.Template
|
||||
staticFS http.FileSystem
|
||||
}
|
||||
|
||||
// breadcrumb represents a link in the navigation chain
|
||||
|
|
@ -29,6 +30,7 @@ type breadcrumb struct {
|
|||
|
||||
func New(dir string) *Handler {
|
||||
h := &Handler{dir: dir}
|
||||
h.staticFS = http.FS(os.DirFS("static"))
|
||||
h.tmpl = template.Must(
|
||||
template.New("").Funcs(template.FuncMap{
|
||||
"humanSize": humanSize,
|
||||
|
|
@ -37,7 +39,7 @@ func New(dir string) *Handler {
|
|||
"base": filepath.Base,
|
||||
"add1": func(i int) int { return i + 1 },
|
||||
"trimExt": func(s string) string { return strings.TrimSuffix(s, filepath.Ext(s)) },
|
||||
}).Parse(allTemplates),
|
||||
}).ParseGlob("templates/*.html"),
|
||||
)
|
||||
return h
|
||||
}
|
||||
|
|
@ -74,9 +76,9 @@ func (h *Handler) ListISOs(w http.ResponseWriter, r *http.Request) {
|
|||
name := e.Name()
|
||||
relPath := filepath.ToSlash(filepath.Join(relDir, name))
|
||||
info, _ := e.Info()
|
||||
|
||||
|
||||
isISO := !e.IsDir() && strings.EqualFold(filepath.Ext(name), ".iso")
|
||||
|
||||
|
||||
item := libraryEntry{
|
||||
Name: name,
|
||||
RelativePath: relPath,
|
||||
|
|
@ -106,11 +108,13 @@ func (h *Handler) ListISOs(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
sort.Slice(items, func(i, j int) bool {
|
||||
if items[i].IsDir != items[j].IsDir { return items[i].IsDir }
|
||||
if items[i].IsDir != items[j].IsDir {
|
||||
return items[i].IsDir
|
||||
}
|
||||
return strings.ToLower(items[i].Name) < strings.ToLower(items[j].Name)
|
||||
})
|
||||
|
||||
h.tmpl.ExecuteTemplate(w, "index", map[string]any{
|
||||
h.tmpl.ExecuteTemplate(w, "index.html", map[string]any{
|
||||
"Title": "ISO Library",
|
||||
"Items": items,
|
||||
"CurrentPath": relDir,
|
||||
|
|
@ -139,11 +143,13 @@ func (h *Handler) BrowseISO(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
sort.Slice(entries, func(i, j int) bool {
|
||||
if entries[i].IsDir != entries[j].IsDir { return entries[i].IsDir }
|
||||
if entries[i].IsDir != entries[j].IsDir {
|
||||
return entries[i].IsDir
|
||||
}
|
||||
return strings.ToLower(entries[i].Name) < strings.ToLower(entries[j].Name)
|
||||
})
|
||||
|
||||
h.tmpl.ExecuteTemplate(w, "browse", map[string]any{
|
||||
h.tmpl.ExecuteTemplate(w, "browse.html", map[string]any{
|
||||
"Title": filepath.Base(isoPath),
|
||||
"ISOName": isoPath,
|
||||
"InternalPath": internalPath,
|
||||
|
|
@ -176,7 +182,9 @@ func (h *Handler) DownloadFile(w http.ResponseWriter, r *http.Request) {
|
|||
filename := filepath.Base(internalPath)
|
||||
ext := strings.ToLower(filepath.Ext(filename))
|
||||
ct := mime.TypeByExtension(ext)
|
||||
if ct == "" { ct = "application/octet-stream" }
|
||||
if ct == "" {
|
||||
ct = "application/octet-stream"
|
||||
}
|
||||
|
||||
viewable := false
|
||||
switch ext {
|
||||
|
|
@ -200,6 +208,10 @@ func (h *Handler) RawFile(w http.ResponseWriter, r *http.Request) {
|
|||
http.ServeFile(w, r, filepath.Join(h.dir, fileName))
|
||||
}
|
||||
|
||||
func (h *Handler) ServeStatic(w http.ResponseWriter, r *http.Request) {
|
||||
http.StripPrefix("/static/", http.FileServer(h.staticFS)).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (h *Handler) openISO(relPath string) (*iso.Reader, error) {
|
||||
return iso.Open(filepath.Join(h.dir, relPath))
|
||||
}
|
||||
|
|
@ -209,17 +221,27 @@ func parsePath(urlPath, prefix string) (isoPath, internalPath string, ok bool) {
|
|||
decoded, _ := url.PathUnescape(rest)
|
||||
lower := strings.ToLower(decoded)
|
||||
idx := strings.Index(lower, ".iso")
|
||||
if idx == -1 { return decoded, "", true }
|
||||
if idx == -1 {
|
||||
return decoded, "", true
|
||||
}
|
||||
return decoded[:idx+4], strings.TrimPrefix(decoded[idx+4:], "/"), true
|
||||
}
|
||||
|
||||
func buildLibraryBreadcrumbs(relDir string) []breadcrumb {
|
||||
crumbs := []breadcrumb{{Name: "Library", URL: "/"}}
|
||||
if relDir == "" { return crumbs }
|
||||
if relDir == "" {
|
||||
return crumbs
|
||||
}
|
||||
acc := ""
|
||||
for _, p := range strings.Split(relDir, "/") {
|
||||
if p == "" { continue }
|
||||
if acc == "" { acc = p } else { acc = filepath.Join(acc, p) }
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
if acc == "" {
|
||||
acc = p
|
||||
} else {
|
||||
acc = filepath.Join(acc, p)
|
||||
}
|
||||
crumbs = append(crumbs, breadcrumb{Name: p, URL: "/" + filepath.ToSlash(acc)})
|
||||
}
|
||||
return crumbs
|
||||
|
|
@ -233,13 +255,19 @@ func buildISOBreadcrumbs(isoPath, internalPath string) []breadcrumb {
|
|||
} else {
|
||||
crumbs = buildLibraryBreadcrumbs(parentDir)
|
||||
}
|
||||
|
||||
|
||||
crumbs = append(crumbs, breadcrumb{Name: filepath.Base(isoPath), URL: "/browse/" + url.PathEscape(isoPath)})
|
||||
if internalPath != "" {
|
||||
acc := ""
|
||||
for _, p := range strings.Split(internalPath, "/") {
|
||||
if p == "" { continue }
|
||||
if acc == "" { acc = p } else { acc += "/" + p }
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
if acc == "" {
|
||||
acc = p
|
||||
} else {
|
||||
acc += "/" + p
|
||||
}
|
||||
crumbs = append(crumbs, breadcrumb{Name: p, URL: "/browse/" + url.PathEscape(isoPath) + "/" + acc})
|
||||
}
|
||||
}
|
||||
|
|
@ -247,121 +275,29 @@ func buildISOBreadcrumbs(isoPath, internalPath string) []breadcrumb {
|
|||
}
|
||||
|
||||
func humanSize(n int64) string {
|
||||
if n < 1024 { return fmt.Sprintf("%d B", n) }
|
||||
if n < 1024 {
|
||||
return fmt.Sprintf("%d B", n)
|
||||
}
|
||||
div, exp := int64(1024), 0
|
||||
for v := n / 1024; v >= 1024; v /= 1024 { div *= 1024; exp++ }
|
||||
for v := n / 1024; v >= 1024; v /= 1024 {
|
||||
div *= 1024
|
||||
exp++
|
||||
}
|
||||
return fmt.Sprintf("%.1f %cB", float64(n)/float64(div), "KMGTPE"[exp])
|
||||
}
|
||||
|
||||
func fileIcon(name string) string {
|
||||
ext := strings.ToLower(filepath.Ext(name))
|
||||
switch ext {
|
||||
case ".iso": return "💿"
|
||||
case ".pdf": return "📄"
|
||||
case ".jpg", ".png", ".jpeg": return "🖼️"
|
||||
case ".txt", ".md": return "📝"
|
||||
default: return "📄"
|
||||
case ".iso":
|
||||
return "💿"
|
||||
case ".pdf":
|
||||
return "📄"
|
||||
case ".jpg", ".png", ".jpeg":
|
||||
return "🖼️"
|
||||
case ".txt", ".md":
|
||||
return "📝"
|
||||
default:
|
||||
return "📄"
|
||||
}
|
||||
}
|
||||
|
||||
const allTemplates = `
|
||||
{{define "css"}}
|
||||
<style>
|
||||
:root {
|
||||
--bg: #0f1117; --surface: #1a1d27; --border: #252836;
|
||||
--accent: #4f8ef7; --text: #e2e4ef; --muted: #6b7090; --radius: 8px;
|
||||
}
|
||||
body { background: var(--bg); color: var(--text); font-family: system-ui; margin: 0; }
|
||||
header { background: #12151f; border-bottom: 1px solid var(--border); padding: 0 2rem; height: 56px; display: flex; align-items: center; }
|
||||
.logo { color: var(--accent); font-weight: bold; text-decoration: none; font-family: monospace; }
|
||||
main { max-width: 1100px; margin: 0 auto; padding: 2rem; }
|
||||
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 1.5rem; }
|
||||
.card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; display: flex; flex-direction: column; text-decoration: none; color: inherit; transition: transform 0.1s; position: relative;}
|
||||
.card:hover { transform: translateY(-3px); border-color: var(--accent); }
|
||||
.card-img { width: 100%; height: 150px; object-fit: cover; background: #000; }
|
||||
.folder-icon { height: 150px; display: flex; align-items: center; justify-content: center; font-size: 4rem; background: #1c202d; color: #f7c948; }
|
||||
.iso-icon { height: 150px; display: flex; align-items: center; justify-content: center; font-size: 4rem; background: #12151f; }
|
||||
.card-body { padding: 1rem; }
|
||||
.card-name { font-weight: bold; color: var(--accent); margin-bottom: 0.5rem; display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.card-desc { font-size: 0.8rem; color: var(--muted); height: 3em; overflow: hidden; }
|
||||
.bc { background: var(--surface); padding: 0.6rem 1rem; border-radius: 4px; margin-bottom: 1.5rem; font-size: 0.85rem; }
|
||||
.bc a { color: var(--accent); text-decoration: none; }
|
||||
.tbl { width: 100%; border-collapse: collapse; background: var(--surface); border-radius: var(--radius); overflow: hidden; }
|
||||
.tbl th { text-align: left; padding: 1rem; background: #1e2132; color: var(--muted); font-size: 0.75rem; }
|
||||
.tbl td { padding: 1rem; border-bottom: 1px solid var(--border); }
|
||||
.dl-btn { border: 1px solid var(--accent); color: var(--accent); padding: 0.2rem 0.5rem; border-radius: 4px; font-size: 0.7rem; text-decoration: none; }
|
||||
.dl-btn:hover { background: var(--accent); color: #fff; }
|
||||
</style>
|
||||
{{end}}
|
||||
|
||||
{{define "index"}}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><title>{{.Title}}</title>{{template "css" .}}</head>
|
||||
<body>
|
||||
<header><a class="logo" href="/">💿 ISOSilo</a></header>
|
||||
<main>
|
||||
<nav class="bc">
|
||||
{{range $i, $c := .Breadcrumbs}}<a href="{{$c.URL}}">{{$c.Name}}</a> {{if lt (add1 $i) (len $.Breadcrumbs)}}/{{end}} {{end}}
|
||||
</nav>
|
||||
|
||||
<div class="grid">
|
||||
{{range .Items}}
|
||||
{{if .IsDir}}
|
||||
<a href="/{{urlenc .RelativePath}}" class="card">
|
||||
<div class="folder-icon">📁</div>
|
||||
<div class="card-body"><span class="card-name">{{.Name}}</span></div>
|
||||
</a>
|
||||
{{else}}
|
||||
<div class="card">
|
||||
<a href="/browse/{{urlenc .RelativePath}}">
|
||||
{{if .HasImage}}<img src="/raw/{{urlenc (trimExt .RelativePath)}}{{.ImageExt}}" class="card-img">
|
||||
{{else}}<div class="iso-icon">💿</div>{{end}}
|
||||
</a>
|
||||
<div class="card-body">
|
||||
<a href="/browse/{{urlenc .RelativePath}}" class="card-name">{{.Name}}</a>
|
||||
<p class="card-desc">{{if .Description}}{{.Description}}{{else}}ISO Disk Image{{end}}</p>
|
||||
<div style="margin-top:1rem; display:flex; gap:0.5rem;">
|
||||
<a href="/browse/{{urlenc .RelativePath}}" class="dl-btn">Browse</a>
|
||||
<a href="/raw/{{urlenc .RelativePath}}" class="dl-btn" download>ISO</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
|
||||
{{define "browse"}}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><title>{{.Title}}</title>{{template "css" .}}</head>
|
||||
<body>
|
||||
<header><a class="logo" href="/">💿 ISOSilo</a></header>
|
||||
<main>
|
||||
<nav class="bc">
|
||||
{{range $i, $c := .Breadcrumbs}}<a href="{{$c.URL}}">{{$c.Name}}</a> {{if lt (add1 $i) (len $.Breadcrumbs)}}/{{end}} {{end}}
|
||||
</nav>
|
||||
<table class="tbl">
|
||||
<thead><tr><th>Name</th><th>Size</th><th>Action</th></tr></thead>
|
||||
<tbody>
|
||||
{{range .Entries}}
|
||||
<tr>
|
||||
<td>
|
||||
{{if .IsDir}}📁 <a href="/browse/{{urlenc $.ISOName}}/{{urlenc .Path}}" style="color:var(--text); text-decoration:none;">{{.Name}}</a>
|
||||
{{else}}{{fileIcon .Name}} <a href="/file/{{urlenc $.ISOName}}/{{urlenc .Path}}" target="_blank" style="color:var(--text); text-decoration:none;">{{.Name}}</a>{{end}}
|
||||
</td>
|
||||
<td>{{if .IsDir}}—{{else}}{{humanSize .Size}}{{end}}</td>
|
||||
<td>{{if not .IsDir}}<a href="/file/{{urlenc $.ISOName}}/{{urlenc .Path}}" class="dl-btn" download>Download</a>{{end}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
`
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue