HUGE refactor. working towards web UI
This commit is contained in:
parent
dd54e8cc49
commit
1f94eecca9
29 changed files with 1669 additions and 162 deletions
59
templates/html.go
Normal file
59
templates/html.go
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
package templates
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"html/template"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
//go:embed "html/layout.html"
|
||||
var layoutHTML string
|
||||
//go:embed "html/index.html"
|
||||
var indexHTML string
|
||||
//go:embed "html/vod-list.html"
|
||||
var vodListHTML string
|
||||
//go:embed "html/vod.html"
|
||||
var vodHTML string
|
||||
|
||||
type (
|
||||
Crumb struct {
|
||||
Name string
|
||||
Path string
|
||||
}
|
||||
)
|
||||
|
||||
var templateFuncs = template.FuncMap{
|
||||
"FormatTime": func (time time.Time, format string) string {
|
||||
return time.Format(format)
|
||||
},
|
||||
"ToLower": func (str string) string {
|
||||
return strings.ToLower(str)
|
||||
},
|
||||
"ToUpper": func (str string) string {
|
||||
return strings.ToUpper(str)
|
||||
},
|
||||
"Crumbs": func (path string) []Crumb {
|
||||
split := strings.Split(strings.TrimSuffix(path, "/"), "/")
|
||||
crumbs := make([]Crumb, len(split))
|
||||
for i := range crumbs {
|
||||
segment := split[i]
|
||||
crumbs[i] = Crumb{
|
||||
segment,
|
||||
strings.Repeat("../", len(crumbs)-i-1),
|
||||
}
|
||||
}
|
||||
return crumbs
|
||||
},
|
||||
}
|
||||
|
||||
var BaseTemplate = template.Must(template.New("base").Funcs(templateFuncs).Parse(
|
||||
strings.Join([]string{
|
||||
layoutHTML,
|
||||
// headerHTML,
|
||||
// footerHTML,
|
||||
}, "\n"),
|
||||
))
|
||||
var IndexTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(indexHTML))
|
||||
var VODListTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(vodListHTML))
|
||||
var VODTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(vodHTML))
|
||||
18
templates/html/index.html
Normal file
18
templates/html/index.html
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{{define "head"}}
|
||||
<title>Melody VOD Manager</title>
|
||||
|
||||
<link rel="stylesheet" href="/style/index.css">
|
||||
{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<main>
|
||||
<h1>Melody VOD Manager</h1>
|
||||
<a href="/vods">Vods</a>
|
||||
<p>Templates:</p>
|
||||
<ul>
|
||||
<li><a href="/templates/title">Title</a></li>
|
||||
<li><a href="/templates/description">Description</a></li>
|
||||
<li><a href="/templates/tags">Tags</a></li>
|
||||
</ul>
|
||||
</main>
|
||||
{{end}}
|
||||
32
templates/html/layout.html
Normal file
32
templates/html/layout.html
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
{{block "head" .}}{{end}}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<nav>
|
||||
<a href="/">Home</a>
|
||||
<a href="/vods">Vods</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
{{block "content" .}}
|
||||
<main>
|
||||
<h1>hello, world!</h1>
|
||||
<p>
|
||||
this is a template page.
|
||||
you probably shouldn't be seeing this!
|
||||
</p>
|
||||
</main>
|
||||
{{end}}
|
||||
</body>
|
||||
|
||||
</html>
|
||||
55
templates/html/vod-list.html
Normal file
55
templates/html/vod-list.html
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
{{define "head"}}
|
||||
<title>Listing "{{.Directory}}" - Melody VOD Manager</title>
|
||||
|
||||
<link rel="stylesheet" href="/style/vod-list.css">
|
||||
{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<main>
|
||||
<h1>VODs</h1>
|
||||
|
||||
<nav>→ {{range Crumbs .Directory}}<a href="./{{.Path}}">{{.Name}}</a>/{{end}}</nav>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">Name</th>
|
||||
<th>Size</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range $VOD := .VODs}}
|
||||
<tr>
|
||||
<td>📼</td>
|
||||
<td><a href="/vods{{$.Directory}}/{{$VOD.Path}}">{{$VOD.Title}}</a></td>
|
||||
<td>—</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
|
||||
{{range $Name := .Directories}}
|
||||
<tr>
|
||||
<td>📁</td>
|
||||
<td><a href="/vods{{$.Directory}}/{{$Name}}">{{$Name}}</a></td>
|
||||
<td>—</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
|
||||
{{range $File := .Files}}
|
||||
<tr>
|
||||
<td>📄</td>
|
||||
<td><a href="/vods{{$.Directory}}/{{$File.Name}}">{{$File.Name}}</a></td>
|
||||
<td>{{$File.Size}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>Actions</h2>
|
||||
<div class="actions">
|
||||
<p>
|
||||
<a href="/vods{{.Directory}}?init=true">Initialise as VOD Directory</a>
|
||||
— Create a <code>metadata.toml</code> file here, initialising this directory as a VOD.
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
{{end}}
|
||||
72
templates/html/vod.html
Normal file
72
templates/html/vod.html
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
{{define "head"}}
|
||||
<title>{{.Title}}</title>
|
||||
|
||||
<link rel="stylesheet" href="/style/vod.css">
|
||||
{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<main>
|
||||
<h1>VOD Details</h1>
|
||||
|
||||
<nav>→ {{range Crumbs .Directory}}<a href="./{{.Path}}">{{.Name}}</a>/{{end}}</nav>
|
||||
|
||||
<form method="POST">
|
||||
<div class="metadata-top">
|
||||
<div>
|
||||
<label for="title">Title</label>
|
||||
<input type="text" name="title" value="{{.Title}}" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="part">Part</label>
|
||||
<input type="number" min="0" name="part" value="{{.Part}}" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="date">Date</label>
|
||||
<input type="date" name="date" value="{{FormatTime .Date "2006-01-02"}}" />
|
||||
</div>
|
||||
</div>
|
||||
<p>Formatted Title: <strong>{{.TitleFormatted}}</strong></p>
|
||||
|
||||
<p>Uploaded: <span id="upload-state">{{if .Uploaded}}Yes{{else}}No{{end}}</span></p>
|
||||
|
||||
<p>Tags:</p>
|
||||
<ul>
|
||||
{{range .Tags}}
|
||||
<li><code>{{.}}</code></li>
|
||||
{{end}}
|
||||
</ul>
|
||||
|
||||
<p>Category:</p>
|
||||
<blockquote>
|
||||
<label for="category-enabled">Enabled</label>
|
||||
<input type="checkbox" name="category-enabled" {{if .Category}}checked{{end}} />
|
||||
<label for="category-name">Name</label>
|
||||
<input type="text" name="category-name" value="{{if ne .Category nil}}{{.Category.Name}}{{end}}" />
|
||||
<label for="category-type">Type</label>
|
||||
<select name="category-type">
|
||||
<option value="entertainment" {{if ne .Category nil}}{{if eq .Category.Type "entertainment"}}selected{{end}}{{end}}>Entertainment</option>
|
||||
<option value="gaming" {{if ne .Category nil}}{{if eq .Category.Type "gaming"}}selected{{end}}{{end}}>Gaming</option>
|
||||
</select>
|
||||
<label for="category-url">URL</label>
|
||||
<input type="text" name="category-url" value="{{if ne .Category nil}}{{.Category.Url}}{{end}}" />
|
||||
</blockquote>
|
||||
|
||||
<p>Formatted Description:</p>
|
||||
<p><pre class="description">{{.DescriptionFormatted}}</pre></p>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
|
||||
<hr>
|
||||
|
||||
<p>Footage Directory: <code>"{{.FootageDir}}"</code></p>
|
||||
<p>Footage Blocks:</p>
|
||||
<ul>
|
||||
{{range .Footage}}
|
||||
<li><code>{{.Name}} ({{.Size}})</code></li>
|
||||
{{end}}
|
||||
</ul>
|
||||
</main>
|
||||
|
||||
<script type="module" src="/script/vod.js" defer></script>
|
||||
{{end}}
|
||||
108
templates/text.go
Normal file
108
templates/text.go
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
package templates
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"arimelody.space/melody-vod-manager/scanner"
|
||||
)
|
||||
|
||||
|
||||
|
||||
type (
|
||||
TextTemplates struct {
|
||||
Title *template.Template
|
||||
Description *template.Template
|
||||
DefaultTags []string
|
||||
}
|
||||
|
||||
TextTemplateData struct {
|
||||
Title string
|
||||
Part uint
|
||||
Date time.Time
|
||||
Category *scanner.Category
|
||||
}
|
||||
)
|
||||
|
||||
const DefaultTitleTemplate =
|
||||
"{{.Title}} - {{FormatTime .Date \"02 Jan 2006\"}}"
|
||||
const DefaultDescriptionTemplate =
|
||||
"Streamed on {{FormatTime .Date \"02 January 2006\"}}"
|
||||
|
||||
func FetchTemplates(ctx context.Context, templateDir string) (*TextTemplates, error) {
|
||||
tmpl := TextTemplates{}
|
||||
logger := slog.Default().WithGroup("TEMPLATE")
|
||||
|
||||
var tagsPath = path.Join(templateDir, "tags.txt")
|
||||
var titlePath = path.Join(templateDir, "title.txt")
|
||||
var descriptionPath = path.Join(templateDir, "description.txt")
|
||||
|
||||
// tags
|
||||
if tagsFile, err := os.ReadFile(tagsPath); err == nil {
|
||||
tmpl.DefaultTags = strings.Split(string(tagsFile), "\n")
|
||||
} else {
|
||||
if !os.IsNotExist(err) { return nil, err }
|
||||
|
||||
logger.Info(fmt.Sprintf(
|
||||
"%s not found. No default tags will be used.",
|
||||
tagsPath,
|
||||
))
|
||||
tmpl.DefaultTags = []string{}
|
||||
}
|
||||
|
||||
// title
|
||||
titleTemplate := template.New("title").Funcs(templateFuncs)
|
||||
if titleFile, err := os.ReadFile(titlePath); err == nil {
|
||||
tmpl.Title, err = titleTemplate.Parse(string(titleFile))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse title template: %v", err)
|
||||
}
|
||||
} else {
|
||||
if !os.IsNotExist(err) { return nil, err }
|
||||
|
||||
logger.Info(
|
||||
fmt.Sprintf(
|
||||
"%s not found. Falling back to default template:",
|
||||
titlePath,
|
||||
),
|
||||
"template",
|
||||
DefaultTitleTemplate,
|
||||
)
|
||||
tmpl.Title, err = titleTemplate.Parse(DefaultTitleTemplate)
|
||||
if err != nil { panic(err) }
|
||||
|
||||
os.WriteFile(titlePath, []byte(DefaultTitleTemplate), 0644)
|
||||
}
|
||||
|
||||
// description
|
||||
descriptionTemplate := template.New("description").Funcs(templateFuncs)
|
||||
if descriptionFile, err := os.ReadFile(descriptionPath); err == nil {
|
||||
tmpl.Description, err = descriptionTemplate.Parse(string(descriptionFile))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse description template: %v", err)
|
||||
}
|
||||
} else {
|
||||
if !os.IsNotExist(err) { return nil, err }
|
||||
|
||||
logger.Info(
|
||||
fmt.Sprintf(
|
||||
"%s not found. Falling back to default template:",
|
||||
descriptionPath,
|
||||
),
|
||||
"template",
|
||||
DefaultDescriptionTemplate,
|
||||
)
|
||||
tmpl.Description, err = descriptionTemplate.Parse(DefaultDescriptionTemplate)
|
||||
if err != nil { panic(err) }
|
||||
|
||||
os.WriteFile(descriptionPath, []byte(DefaultDescriptionTemplate), 0644)
|
||||
}
|
||||
|
||||
return &tmpl, nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue