mirror of
https://github.com/libretro/webdb.git
synced 2024-11-26 18:30:37 +00:00
Initial commit
This commit is contained in:
commit
d30f449bd1
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
build/
|
||||
webdb
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "database"]
|
||||
path = database
|
||||
url = https://github.com/libretro/libretro-database.git
|
1
database
Submodule
1
database
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 297a6b2719c8156d4de0abbfb323aee3538243f1
|
10
go.mod
Normal file
10
go.mod
Normal file
@ -0,0 +1,10 @@
|
||||
module github.com/kivutar/webdb
|
||||
|
||||
require (
|
||||
github.com/disintegration/imaging v1.6.0 // indirect
|
||||
github.com/go-gl/glfw v0.0.0-20190217072633-93b30450e032 // indirect
|
||||
github.com/libretro/ludo v0.5.4-0.20190309060234-c7f145152c90
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 // indirect
|
||||
golang.org/x/mobile v0.0.0-20190307202846-d2e1c1c4a691 // indirect
|
||||
golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95 // indirect
|
||||
)
|
27
go.sum
Normal file
27
go.sum
Normal file
@ -0,0 +1,27 @@
|
||||
github.com/disintegration/imaging v1.5.0/go.mod h1:9B/deIUIrliYkyMTuXJd6OUFLcrZ2tf+3Qlwnaf/CjU=
|
||||
github.com/disintegration/imaging v1.6.0/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/go-gl/gl v0.0.0-20181026044259-55b76b7df9d2/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
|
||||
github.com/go-gl/glfw v0.0.0-20181213070059-819e8ce5125f/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw v0.0.0-20190217072633-93b30450e032/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/kivutar/glfont v0.0.0-20190119064645-3f4c07235fb3/go.mod h1:yvRxgIrpErStul4gzj88N+5yL15ze5JUVqk6Ze4mU+I=
|
||||
github.com/libretro/ludo v0.5.2 h1:cPFIl4mrlskiWMYLcGT/e0btFsZX5DWUlipx0sbj678=
|
||||
github.com/libretro/ludo v0.5.2/go.mod h1:VzVK8b71TYyUHQc5UQJj9ULC06O6HPCQD+QOe4YhqAU=
|
||||
github.com/libretro/ludo v0.5.4-0.20190304150752-d547d4a5b25f h1:gcvXAXmCm90feQLn73EOhLmzHFjnaksbVJiPo06oywM=
|
||||
github.com/libretro/ludo v0.5.4-0.20190304150752-d547d4a5b25f/go.mod h1:VzVK8b71TYyUHQc5UQJj9ULC06O6HPCQD+QOe4YhqAU=
|
||||
github.com/libretro/ludo v0.5.4-0.20190309054306-2241406a64b0 h1:WCPRk5Gb63wnqDs7seb7JfI/FmRcYf1EK5qjXheF7dE=
|
||||
github.com/libretro/ludo v0.5.4-0.20190309054306-2241406a64b0/go.mod h1:VzVK8b71TYyUHQc5UQJj9ULC06O6HPCQD+QOe4YhqAU=
|
||||
github.com/libretro/ludo v0.5.4-0.20190309060234-c7f145152c90 h1:Q8Q0sOAqNINUKQclux4ADohpZRWBfNBSihCyrmDYoDg=
|
||||
github.com/libretro/ludo v0.5.4-0.20190309060234-c7f145152c90/go.mod h1:VzVK8b71TYyUHQc5UQJj9ULC06O6HPCQD+QOe4YhqAU=
|
||||
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/tanema/gween v0.0.0-20171018143308-f05d97ee644d/go.mod h1:q58RIyFkCWKEi86xWwyOYLA9YZCY6myIgdN1Os5HECU=
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||
golang.org/x/image v0.0.0-20190118043309-183bebdce1b2/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/mobile v0.0.0-20190127143845-a42111704963/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190307202846-d2e1c1c4a691/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/net v0.0.0-20190225153610-fe579d43d832/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
BIN
img-broken.png
Normal file
BIN
img-broken.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
315
main.go
Normal file
315
main.go
Normal file
@ -0,0 +1,315 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/template"
|
||||
|
||||
"github.com/libretro/ludo/rdb"
|
||||
)
|
||||
|
||||
var target = "build"
|
||||
var perPage = 24
|
||||
var tmpl *template.Template
|
||||
|
||||
// Scrub characters that are not cross-platform and/or violate the
|
||||
// No-Intro filename standard.
|
||||
func scrubIllegalChars(str string) string {
|
||||
str = strings.Replace(str, "&", "_", -1)
|
||||
str = strings.Replace(str, "*", "_", -1)
|
||||
str = strings.Replace(str, "/", "_", -1)
|
||||
str = strings.Replace(str, ":", "_", -1)
|
||||
str = strings.Replace(str, "`", "_", -1)
|
||||
str = strings.Replace(str, "<", "_", -1)
|
||||
str = strings.Replace(str, ">", "_", -1)
|
||||
str = strings.Replace(str, "?", "_", -1)
|
||||
str = strings.Replace(str, "|", "_", -1)
|
||||
str = strings.Replace(str, "\"", "_", -1)
|
||||
return str
|
||||
}
|
||||
|
||||
func reverse(s rdb.RDB) rdb.RDB {
|
||||
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func loadDB(dir string) (rdb.DB, error) {
|
||||
files, err := ioutil.ReadDir(dir)
|
||||
if err != nil {
|
||||
return rdb.DB{}, err
|
||||
}
|
||||
db := make(rdb.DB)
|
||||
for _, f := range files {
|
||||
name := f.Name()
|
||||
if !strings.Contains(name, ".rdb") {
|
||||
continue
|
||||
}
|
||||
system := name[0 : len(name)-4]
|
||||
bytes, _ := ioutil.ReadFile(filepath.Join(dir, name))
|
||||
db[system] = reverse(rdb.Parse(bytes))
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func buildHome(db rdb.DB) {
|
||||
os.MkdirAll(target, os.ModePerm)
|
||||
os.Link("img-broken.png", filepath.Join(target, "img-broken.png"))
|
||||
|
||||
f, err := os.OpenFile(filepath.Join(target, "index.html"), os.O_CREATE|os.O_WRONLY, os.ModePerm)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
tmpl.ExecuteTemplate(f, "home.html", struct {
|
||||
DB rdb.DB
|
||||
}{
|
||||
db,
|
||||
})
|
||||
}
|
||||
|
||||
func buildSystemPages(system string, games rdb.RDB) {
|
||||
os.MkdirAll(filepath.Join(target, system), os.ModePerm)
|
||||
numPages := int(math.Ceil(float64(len(games)) / float64(perPage)))
|
||||
for p := 0; p < numPages; p++ {
|
||||
page := fmt.Sprintf("%d", p)
|
||||
f, err := os.OpenFile(filepath.Join(target, system, "index-"+page+".html"), os.O_CREATE|os.O_WRONLY, os.ModePerm)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
tmpl.ExecuteTemplate(f, "systempage.html", struct {
|
||||
System string
|
||||
Games rdb.RDB
|
||||
Page int
|
||||
LastPage int
|
||||
}{
|
||||
system,
|
||||
games[p*perPage : min(p*perPage+perPage, len(games))],
|
||||
p,
|
||||
numPages - 1,
|
||||
})
|
||||
|
||||
f.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func buildGame(system string, game rdb.Game) {
|
||||
path := filepath.Join(target, system, scrubIllegalChars(game.Name)+".html")
|
||||
|
||||
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, os.ModePerm)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
tmpl.ExecuteTemplate(f, "game.html", struct {
|
||||
System string
|
||||
Game rdb.Game
|
||||
}{
|
||||
system,
|
||||
game,
|
||||
})
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
var funcMap = template.FuncMap{
|
||||
"mkslice": func(a []rdb.Game, start, end int) []rdb.Game {
|
||||
e := min(end, len(a))
|
||||
return a[start:e]
|
||||
},
|
||||
"Clean": scrubIllegalChars,
|
||||
"Tags": func(name string) []string {
|
||||
_, tags := extractTags(name)
|
||||
return tags
|
||||
},
|
||||
"WithoutTags": func(name string) string {
|
||||
sname, _ := extractTags(name)
|
||||
return sname
|
||||
},
|
||||
"add": func(a, b int) int {
|
||||
return a + b
|
||||
},
|
||||
"title": func(name string) string {
|
||||
return strings.Title(name)
|
||||
},
|
||||
}
|
||||
|
||||
func extractTags(name string) (string, []string) {
|
||||
re := regexp.MustCompile(`\(.*?\)`)
|
||||
pars := re.FindAllString(name, -1)
|
||||
var tags []string
|
||||
for _, par := range pars {
|
||||
name = strings.Replace(name, par, "", -1)
|
||||
par = strings.Replace(par, "(", "", -1)
|
||||
par = strings.Replace(par, ")", "", -1)
|
||||
results := strings.Split(par, ",")
|
||||
for _, result := range results {
|
||||
tags = append(tags, strings.TrimSpace(result))
|
||||
}
|
||||
}
|
||||
name = strings.TrimSpace(name)
|
||||
return name, tags
|
||||
}
|
||||
|
||||
func build() {
|
||||
tmpl = template.Must(
|
||||
template.New("main").Funcs(funcMap).ParseGlob("templates/*.html"),
|
||||
)
|
||||
|
||||
db, err := loadDB("./database/rdb")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
buildHome(db)
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
for system, games := range db {
|
||||
buildSystemPages(system, games)
|
||||
system := system
|
||||
games := games
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
for _, game := range games {
|
||||
buildGame(system, game)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
buildTags(db, "franchise")
|
||||
buildTags(db, "developer")
|
||||
buildTags(db, "publisher")
|
||||
buildTags(db, "genre")
|
||||
buildTags(db, "origin")
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// Given a metatag like franchise or genre or developer, will create a hierarchy
|
||||
// of tag files and indexes allowing to browse the database based on this
|
||||
// property.
|
||||
func buildTags(db rdb.DB, tagType string) {
|
||||
perTag := map[string][]rdb.Game{}
|
||||
for system, games := range db {
|
||||
for _, game := range games {
|
||||
game.System = system
|
||||
switch tagType {
|
||||
case "franchise":
|
||||
if game.Franchise == "" {
|
||||
continue
|
||||
}
|
||||
perTag[game.Franchise] = append(perTag[game.Franchise], game)
|
||||
case "developer":
|
||||
if game.Developer == "" {
|
||||
continue
|
||||
}
|
||||
perTag[game.Developer] = append(perTag[game.Developer], game)
|
||||
case "publisher":
|
||||
if game.Publisher == "" {
|
||||
continue
|
||||
}
|
||||
perTag[game.Publisher] = append(perTag[game.Publisher], game)
|
||||
case "genre":
|
||||
if game.Genre == "" {
|
||||
continue
|
||||
}
|
||||
perTag[game.Genre] = append(perTag[game.Genre], game)
|
||||
case "origin":
|
||||
if game.Origin == "" {
|
||||
continue
|
||||
}
|
||||
perTag[game.Origin] = append(perTag[game.Origin], game)
|
||||
}
|
||||
}
|
||||
}
|
||||
buildTagIndex(perTag, tagType)
|
||||
for tag, games := range perTag {
|
||||
buildTagPage(tagType, tag, games)
|
||||
}
|
||||
}
|
||||
|
||||
// Will build a page like /franchise.html that will list all the franchises in
|
||||
// the database.
|
||||
func buildTagIndex(perTag map[string][]rdb.Game, tagType string) {
|
||||
os.MkdirAll(filepath.Join(target, tagType), os.ModePerm)
|
||||
path := filepath.Join(target, tagType, "index.html")
|
||||
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, os.ModePerm)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
err = tmpl.ExecuteTemplate(f, "tags.html", struct {
|
||||
TagType string
|
||||
PerTag map[string][]rdb.Game
|
||||
}{
|
||||
tagType,
|
||||
perTag,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Will build a page like /franchise/Bomberman.html that will list all the
|
||||
// games of the bomberman franchise.
|
||||
func buildTagPage(tagType, tag string, games []rdb.Game) {
|
||||
path := filepath.Join(target, tagType, scrubIllegalChars(tag)+".html")
|
||||
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, os.ModePerm)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
err = tmpl.ExecuteTemplate(f, "tag.html", struct {
|
||||
TagType string
|
||||
Tag string
|
||||
Games []rdb.Game
|
||||
}{
|
||||
tagType,
|
||||
tag,
|
||||
games,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func serve() {
|
||||
fs := http.FileServer(http.Dir(target))
|
||||
http.Handle("/", fs)
|
||||
|
||||
log.Println("Listening on http://0.0.0.0:3003")
|
||||
http.ListenAndServe(":3003", nil)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
args := flag.Args()
|
||||
switch args[0] {
|
||||
case "serve":
|
||||
serve()
|
||||
case "build":
|
||||
fallthrough
|
||||
default:
|
||||
build()
|
||||
}
|
||||
}
|
64
templates/game.html
Normal file
64
templates/game.html
Normal file
@ -0,0 +1,64 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
{{template "head"}}
|
||||
</head>
|
||||
<body>
|
||||
{{template "navbar"}}
|
||||
<div class="container">
|
||||
<h1 class="py-4">
|
||||
{{.Game.Name | WithoutTags}}
|
||||
{{range $_, $tag := (Tags .Game.Name) }}
|
||||
<span class="badge badge-secondary">{{$tag}}</span>
|
||||
{{end}}
|
||||
</h1>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/">Home</a></li>
|
||||
<li class="breadcrumb-item"><a href="/{{.System}}/index-0.html">{{.System}}</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">{{.Game.Name}}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<p>{{.Game.Description}}</p>
|
||||
<table class="table table-sm table-striped">
|
||||
<tbody>
|
||||
{{if .Game.Genre}}
|
||||
<tr><th scope="row" style="width: 25%">Genre:</th><td><a href="/genre/{{.Game.Genre | Clean}}.html">{{.Game.Genre}}</a></td></tr>
|
||||
{{end}}
|
||||
{{if .Game.ReleaseMonth}}
|
||||
<tr><th scope="row">Release Month:</th><td>{{.Game.ReleaseMonth}}</td></tr>
|
||||
{{end}}
|
||||
{{if .Game.ReleaseYear}}
|
||||
<tr><th scope="row">Release Year:</th><td>{{.Game.ReleaseYear}}</td></tr>
|
||||
{{end}}
|
||||
{{if .Game.Developer}}
|
||||
<tr><th scope="row">Developer:</th><td><a href="/developer/{{.Game.Developer | Clean}}.html">{{.Game.Developer}}</a></td></tr>
|
||||
{{end}}
|
||||
{{if .Game.Developer}}
|
||||
<tr><th scope="row">Publisher:</th><td><a href="/publisher/{{.Game.Publisher | Clean}}.html">{{.Game.Publisher}}</a></td></tr>
|
||||
{{end}}
|
||||
{{if .Game.Franchise}}
|
||||
<tr><th scope="row">Franchise:</th><td><a href="/franchise/{{.Game.Franchise | Clean}}.html">{{.Game.Franchise}}</a></td></tr>
|
||||
{{end}}
|
||||
{{if .Game.Origin}}
|
||||
<tr><th scope="row">Origin:</th><td><a href="/origin/{{.Game.Origin | Clean}}.html">{{.Game.Origin}}</a></td></tr>
|
||||
{{end}}
|
||||
<tr><th scope="row">Serial:</th><td>{{.Game.Serial}}</td></tr>
|
||||
<tr><th scope="row">Size:</th><td>{{.Game.Size}}</td></tr>
|
||||
<tr><th scope="row">CRC32:</th><td>{{.Game.CRC32}}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<div class="row">
|
||||
<div class="col-12 mb-4"><img class="w-100" src="http://thumbnails.libretro.com/{{.System}}/Named_Boxarts/{{.Game.Name | Clean}}.png" onerror="this.src='/img-broken.png'"></div>
|
||||
<div class="col-6 mb-4"><img class="w-100" src="http://thumbnails.libretro.com/{{.System}}/Named_Titles/{{.Game.Name | Clean}}.png" onerror="this.src='/img-broken.png'"></div>
|
||||
<div class="col-6 mb-4"><img class="w-100" src="http://thumbnails.libretro.com/{{.System}}/Named_Snaps/{{.Game.Name | Clean}}.png" onerror="this.src='/img-broken.png'"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
9
templates/head.html
Normal file
9
templates/head.html
Normal file
@ -0,0 +1,9 @@
|
||||
{{define "head"}}
|
||||
<meta charset="utf-8">
|
||||
<title>Libretro Database</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
|
||||
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
|
||||
{{end}}
|
23
templates/home.html
Normal file
23
templates/home.html
Normal file
@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
{{template "head"}}
|
||||
</head>
|
||||
<body>
|
||||
{{template "navbar"}}
|
||||
<div class="container">
|
||||
<div class="row pt-5">
|
||||
{{range $system, $games := .DB}}
|
||||
{{if $games}}
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<div class="jumbotron position-relative" style="background-color: #555;">
|
||||
<h2 class="text-light pb-3">{{$system}}</h2>
|
||||
<div class="clearfix"><a href="/{{$system}}/index-0.html" class="float-right btn btn-light stretched-link">{{len $games}} games</a></div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
32
templates/navbar.html
Normal file
32
templates/navbar.html
Normal file
@ -0,0 +1,32 @@
|
||||
{{define "navbar"}}
|
||||
<div class="bg-light">
|
||||
<div class="container">
|
||||
<nav class="navbar navbar-expand-lg navbar-light">
|
||||
<a class="navbar-brand" href="/">Libretro Database</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="nav mr-auto">
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="/franchise">Franchises</a>
|
||||
</li>
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="/developer">Developers</a>
|
||||
</li>
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="/publisher">Publishers</a>
|
||||
</li>
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="/genre">Genres</a>
|
||||
</li>
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="/origin">Origins</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
56
templates/systempage.html
Normal file
56
templates/systempage.html
Normal file
@ -0,0 +1,56 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
{{template "head"}}
|
||||
</head>
|
||||
<body>
|
||||
{{template "navbar"}}
|
||||
<div class="container">
|
||||
<h1 class="py-4">{{.System}} <span class="text-secondary">(Page {{add .Page 1}})</span></h1>
|
||||
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/">Home</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">{{.System}}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<div class="row">
|
||||
{{range .Games}}
|
||||
<div class="col-lg-3 col-md-4 col-sm-6 col-xs-12 mb-3">
|
||||
<div class="card shadow-sm">
|
||||
<img src="http://thumbnails.libretro.com/{{$.System}}/Named_Snaps/{{.Name | Clean}}.png" class="card-img-top" alt="Game screenshot" onerror="this.src='/img-broken.png'">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title mb-0">
|
||||
{{.Name | WithoutTags}}
|
||||
{{range $_, $tag := (Tags .Name) }}
|
||||
<span class="badge badge-secondary">{{$tag}}</span>
|
||||
{{end}}
|
||||
</h6>
|
||||
<p class="card-text">{{.Genre}}</p>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<a href="/{{$.System}}/{{.Name | Clean}}.html" class="float-right stretched-link">See details</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<ul class="pagination justify-content-center">
|
||||
<li class="page-item {{if eq .Page 0}}disabled{{end}}">
|
||||
<a class="page-link" href="/{{$.System}}/index-{{add .Page -1}}.html" tabindex="-1">Previous</a>
|
||||
</li>
|
||||
<li class="page-item active" aria-current="page">
|
||||
<a class="page-link" href="#">{{.Page}} <span class="sr-only">(current)</span></a>
|
||||
</li>
|
||||
<li class="page-item {{if eq .Page .LastPage}}disabled{{end}}">
|
||||
<a class="page-link" href="/{{$.System}}/index-{{add .Page 1}}.html">Next</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
33
templates/tag.html
Normal file
33
templates/tag.html
Normal file
@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
{{template "head"}}
|
||||
</head>
|
||||
<body>
|
||||
{{template "navbar"}}
|
||||
<div class="container">
|
||||
<h1 class="py-4">{{.Tag}}</h1>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/">Home</a></li>
|
||||
<li class="breadcrumb-item"><a href="/{{.TagType}}/index.html">{{.TagType | title}}</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">{{.Tag}}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<table class="table">
|
||||
{{range .Games}}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/{{.System}}/{{.Name | Clean}}.html">{{.Name | WithoutTags}}</a>
|
||||
{{range $_, $tag := (Tags .Name) }}
|
||||
<span class="badge badge-secondary">{{$tag}}</span>
|
||||
{{end}}
|
||||
</td>
|
||||
<td><a href="/{{.System}}/index-0.html">{{.System}}</a></td>
|
||||
<td>{{.ReleaseYear}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
26
templates/tags.html
Normal file
26
templates/tags.html
Normal file
@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
{{template "head"}}
|
||||
</head>
|
||||
<body>
|
||||
{{template "navbar"}}
|
||||
<div class="container">
|
||||
<h1 class="py-4">{{.TagType | title}}</h1>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/">Home</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">{{.TagType | title}}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<table class="table">
|
||||
{{range $tag, $games := .PerTag}}
|
||||
<tr>
|
||||
<td><a href="/{{$.TagType}}/{{$tag | Clean}}.html">{{$tag}}</a></td>
|
||||
<td>{{len $games}} games</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user