purge MPA version
This commit is contained in:
@@ -1,70 +0,0 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"teletype_softwares/domain"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type DocsController struct {
|
||||
softwareService domain.SoftwareServiceInterface
|
||||
fileService domain.FileServiceInterface
|
||||
}
|
||||
|
||||
func NewDocsController(software_service domain.SoftwareServiceInterface, file_service domain.FileServiceInterface) *DocsController {
|
||||
return &DocsController{
|
||||
softwareService: software_service,
|
||||
fileService: file_service,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *DocsController) ServeDocs(w http.ResponseWriter, r *http.Request) {
|
||||
name := chi.URLParam(r, "name")
|
||||
version := chi.URLParam(r, "version")
|
||||
|
||||
software, err := c.softwareService.GetByName(name)
|
||||
if err != nil {
|
||||
if err == domain.ErrSoftwareNotFound {
|
||||
http.Error(w, "Software not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var targetRelease *domain.Release
|
||||
for _, release := range software.Releases {
|
||||
if release.Version == version && release.DocsFolderPath != "" {
|
||||
targetRelease = &release
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if targetRelease == nil {
|
||||
http.Error(w, "No documentation found for this release", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
docs_base_dir := targetRelease.DocsFolderPath
|
||||
|
||||
if _, err := os.Stat(docs_base_dir); os.IsNotExist(err) {
|
||||
http.Error(w, fmt.Sprintf("Documentation for '%s' version '%s' not found.", name, version), http.StatusNotFound)
|
||||
return
|
||||
} else if err != nil {
|
||||
http.Error(w, fmt.Sprintf("Error accessing documentation for '%s' version '%s': %v", name, version, err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
prefix := fmt.Sprintf("/docs/%s/%s", name, version)
|
||||
|
||||
if r.URL.Path == prefix {
|
||||
http.Redirect(w, r, prefix+"/", http.StatusMovedPermanently)
|
||||
return
|
||||
}
|
||||
|
||||
fs := http.StripPrefix(prefix, http.FileServer(http.Dir(docs_base_dir)))
|
||||
fs.ServeHTTP(w, r)
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"teletype_softwares/domain"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type DownloadController struct {
|
||||
downloadService domain.DownloadServiceInterface
|
||||
softwareService domain.SoftwareServiceInterface
|
||||
}
|
||||
|
||||
func NewDownloadController(
|
||||
downloadService domain.DownloadServiceInterface,
|
||||
softwareService domain.SoftwareServiceInterface,
|
||||
) *DownloadController {
|
||||
return &DownloadController{
|
||||
downloadService: downloadService,
|
||||
softwareService: softwareService,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *DownloadController) serve(w http.ResponseWriter, r *http.Request, release *domain.Release, is_source bool) {
|
||||
var file_path string
|
||||
if is_source {
|
||||
file_path = release.SourcePath
|
||||
} else {
|
||||
file_path = release.CartridgePath
|
||||
}
|
||||
|
||||
abs_file_path, err := filepath.Abs(file_path)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid file path", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
abs_contents_dir, err := filepath.Abs(os.Getenv("FILE_CONTAINER_PATH"))
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid contents directory path", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(abs_file_path, abs_contents_dir) {
|
||||
http.Error(w, "Access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Disposition", "attachment; filename="+filepath.Base(file_path))
|
||||
http.ServeFile(w, r, file_path)
|
||||
}
|
||||
|
||||
func (c *DownloadController) handleError(w http.ResponseWriter, err error) {
|
||||
if err == domain.ErrSoftwareNotFound || err == domain.ErrNoReleasesFound {
|
||||
http.Error(w, "Software not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if err == domain.ErrReleaseNotFound {
|
||||
http.Error(w, "Software or release not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
func (c *DownloadController) GetLatestSource(w http.ResponseWriter, r *http.Request) {
|
||||
name := chi.URLParam(r, "name")
|
||||
release, err := c.downloadService.GetLatestRelease(name)
|
||||
if err != nil {
|
||||
c.handleError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.serve(w, r, release, true)
|
||||
}
|
||||
|
||||
func (c *DownloadController) GetLatestCartridge(w http.ResponseWriter, r *http.Request) {
|
||||
name := chi.URLParam(r, "name")
|
||||
release, err := c.downloadService.GetLatestRelease(name)
|
||||
if err != nil {
|
||||
c.handleError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.serve(w, r, release, false)
|
||||
}
|
||||
|
||||
func (c *DownloadController) GetSource(w http.ResponseWriter, r *http.Request) {
|
||||
name := chi.URLParam(r, "name")
|
||||
version := chi.URLParam(r, "version")
|
||||
release, err := c.downloadService.GetSpecificRelease(name, version)
|
||||
if err != nil {
|
||||
c.handleError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.serve(w, r, release, true)
|
||||
}
|
||||
|
||||
func (c *DownloadController) GetCartridge(w http.ResponseWriter, r *http.Request) {
|
||||
name := chi.URLParam(r, "name")
|
||||
version := chi.URLParam(r, "version")
|
||||
release, err := c.downloadService.GetSpecificRelease(name, version)
|
||||
if err != nil {
|
||||
c.handleError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.serve(w, r, release, false)
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"teletype_softwares/domain"
|
||||
"teletype_softwares/lib/template_utils"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type PlayController struct {
|
||||
softwareService domain.SoftwareServiceInterface
|
||||
fileService domain.FileServiceInterface
|
||||
}
|
||||
|
||||
func NewPlayController(software_service domain.SoftwareServiceInterface, file_service domain.FileServiceInterface) *PlayController {
|
||||
return &PlayController{
|
||||
softwareService: software_service,
|
||||
fileService: file_service,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *PlayController) Play(w http.ResponseWriter, r *http.Request) {
|
||||
name := chi.URLParam(r, "name")
|
||||
version := chi.URLParam(r, "version")
|
||||
|
||||
software, err := c.softwareService.GetByName(name)
|
||||
if err != nil {
|
||||
if err == domain.ErrSoftwareNotFound {
|
||||
http.Error(w, "Software not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var web_playable_release *domain.Release
|
||||
for _, release := range software.Releases {
|
||||
if release.Version == version && release.HTMLFolderPath != "" {
|
||||
web_playable_release = &release
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if web_playable_release == nil {
|
||||
http.Error(w, "No web-playable version found for this software", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
softwares, err := c.softwareService.List()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
tmpl, err := template_utils.GetTemplate("play_controller_play", "http/views/shared/layout.html", "http/views/play/play.html")
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
tmpl.Execute(w, map[string]interface{}{
|
||||
"Software": software,
|
||||
"WebPlayableRelease": web_playable_release,
|
||||
"Softwares": softwares,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *PlayController) ServeContent(w http.ResponseWriter, r *http.Request) {
|
||||
name := chi.URLParam(r, "name")
|
||||
version := chi.URLParam(r, "version")
|
||||
|
||||
w.Header().Set("Cross-Origin-Opener-Policy", "same-origin")
|
||||
w.Header().Set("Cross-Origin-Embedder-Policy", "require-corp")
|
||||
w.Header().Set("Cross-Origin-Resource-Policy", "same-origin")
|
||||
|
||||
html_base_dir := c.fileService.GetPath(name + "-" + version)
|
||||
|
||||
if _, err := os.Stat(html_base_dir); os.IsNotExist(err) {
|
||||
http.Error(w, fmt.Sprintf("Content for '%s' not found.", name), http.StatusNotFound)
|
||||
return
|
||||
} else if err != nil {
|
||||
http.Error(w, fmt.Sprintf("Error accessing content for '%s': %v", name, err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
fs := http.StripPrefix(
|
||||
fmt.Sprintf("/play/%s/%s/content", name, version),
|
||||
http.FileServer(http.Dir(html_base_dir)),
|
||||
)
|
||||
|
||||
fs.ServeHTTP(w, r)
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"teletype_softwares/domain"
|
||||
"teletype_softwares/lib/template_utils"
|
||||
)
|
||||
|
||||
type RootController struct {
|
||||
softwareService domain.SoftwareServiceInterface
|
||||
}
|
||||
|
||||
func NewRootController(softwareService domain.SoftwareServiceInterface) *RootController {
|
||||
return &RootController{
|
||||
softwareService: softwareService,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *RootController) Index(w http.ResponseWriter, r *http.Request) {
|
||||
softwares, err := c.softwareService.List()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"Softwares": softwares,
|
||||
}
|
||||
|
||||
tmpl, err := template_utils.GetTemplate("root_index", "http/views/shared/layout.html", "http/views/root/index.html")
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
tmpl.Execute(w, data)
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"teletype_softwares/domain"
|
||||
"teletype_softwares/lib/template_utils"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type SoftwareController struct {
|
||||
softwareService domain.SoftwareServiceInterface
|
||||
}
|
||||
|
||||
func NewSoftwareController(software_service domain.SoftwareServiceInterface) *SoftwareController {
|
||||
return &SoftwareController{softwareService: software_service}
|
||||
}
|
||||
|
||||
func (c *SoftwareController) Index(w http.ResponseWriter, r *http.Request) {
|
||||
softwares, err := c.softwareService.List()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"Softwares": softwares,
|
||||
}
|
||||
|
||||
tmpl, err := template_utils.GetTemplate("software_index", "http/views/shared/layout.html", "http/views/software/index.html")
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
tmpl.Execute(w, data)
|
||||
}
|
||||
|
||||
func (c *SoftwareController) Show(w http.ResponseWriter, r *http.Request) {
|
||||
name := chi.URLParam(r, "name")
|
||||
|
||||
showData, err := c.softwareService.GetForShowByName(name)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
softwares, err := c.softwareService.List()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"Software": showData.Software,
|
||||
"Releases": showData.Releases,
|
||||
"LatestRelease": showData.LatestRelease,
|
||||
"WebPlayableRelease": showData.WebPlayableRelease,
|
||||
"Softwares": softwares,
|
||||
}
|
||||
|
||||
tmpl, err := template_utils.GetTemplate("software_show", "http/views/shared/layout.html", "http/views/software/show.html")
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
tmpl.Execute(w, data)
|
||||
|
||||
}
|
||||
@@ -8,12 +8,7 @@ import (
|
||||
func StartHttpServer(domain_instance domain.Domain) {
|
||||
router := NewRouter(
|
||||
NewAPISoftwareController(domain_instance.SoftwareService),
|
||||
NewSoftwareController(domain_instance.SoftwareService),
|
||||
NewSoftwareUpdaterController(domain_instance.SoftwareUpdaterService, domain_instance.SoftwareService),
|
||||
NewDownloadController(domain_instance.DownloadService, domain_instance.SoftwareService),
|
||||
NewPlayController(domain_instance.SoftwareService, domain_instance.FileService),
|
||||
NewRootController(domain_instance.SoftwareService),
|
||||
NewDocsController(domain_instance.SoftwareService, domain_instance.FileService),
|
||||
).Init()
|
||||
|
||||
http_utils.StartGenericHTTPServer(http_utils.StartGenericHTTPServerContext{
|
||||
|
||||
@@ -8,31 +8,16 @@ import (
|
||||
|
||||
type Router struct {
|
||||
apiSoftwareController *APISoftwareController
|
||||
softwareController *SoftwareController
|
||||
softwareUpdaterController *SoftwareUpdaterController
|
||||
downloadController *DownloadController
|
||||
playController *PlayController
|
||||
rootController *RootController
|
||||
docsController *DocsController
|
||||
}
|
||||
|
||||
func NewRouter(
|
||||
api_software_controller *APISoftwareController,
|
||||
software_controller *SoftwareController,
|
||||
software_updater_controller *SoftwareUpdaterController,
|
||||
download_controller *DownloadController,
|
||||
play_controller *PlayController,
|
||||
root_controller *RootController,
|
||||
docs_controller *DocsController,
|
||||
) *Router {
|
||||
return &Router{
|
||||
apiSoftwareController: api_software_controller,
|
||||
softwareController: software_controller,
|
||||
softwareUpdaterController: software_updater_controller,
|
||||
downloadController: download_controller,
|
||||
playController: play_controller,
|
||||
rootController: root_controller,
|
||||
docsController: docs_controller,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,25 +25,8 @@ func (r *Router) Init() *chi.Mux {
|
||||
router := chi.NewRouter()
|
||||
router.Use(CORSMiddleware)
|
||||
|
||||
router.Get("/", r.rootController.Index)
|
||||
|
||||
router.Get("/api/software", r.apiSoftwareController.Index)
|
||||
|
||||
router.Get("/software", r.softwareController.Index)
|
||||
router.Get("/software/{name}", r.softwareController.Show)
|
||||
|
||||
router.Get("/update", r.softwareUpdaterController.Update)
|
||||
router.Get("/download/{name}/source", r.downloadController.GetLatestSource)
|
||||
router.Get("/download/{name}/cartridge", r.downloadController.GetLatestCartridge)
|
||||
router.Get("/download/{name}/{version}/source", r.downloadController.GetSource)
|
||||
router.Get("/download/{name}/{version}/cartridge", r.downloadController.GetCartridge)
|
||||
router.Get("/play/{name}/{version}", r.playController.Play)
|
||||
router.Get("/play/{name}/{version}/content*", r.playController.ServeContent)
|
||||
router.Get("/docs/{name}/{version}", r.docsController.ServeDocs)
|
||||
router.Get("/docs/{name}/{version}/*", r.docsController.ServeDocs)
|
||||
|
||||
fs_assets := http.FileServer(http.Dir("assets"))
|
||||
router.Handle("/assets/*", http.StripPrefix("/assets/", fs_assets))
|
||||
|
||||
fs_file := http.FileServer(http.Dir("/softwares"))
|
||||
router.Handle("/file/*", http.StripPrefix("/file/", fs_file))
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
{{define "content"}}
|
||||
<h1 class="my-4">Play {{.Software.Title}}</h1>
|
||||
|
||||
<div class="ratio ratio-4x3" style="max-width: 960px; margin: 0 auto;">
|
||||
<iframe id="gameFrame" src="/play/{{.Software.Name}}/{{.WebPlayableRelease.Version}}/content/index.html" frameborder="0" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
<div style="max-width: 960px; margin: 10px auto; text-align: center;">
|
||||
<div class="btn-group" role="group" aria-label="Game controls">
|
||||
<a href="/releases/{{.Software.Name}}" class="btn btn-primary">Back to Releases</a>
|
||||
{{if .WebPlayableRelease.DocsFolderPath}}
|
||||
<a href="/docs/{{.Software.Name}}/{{.WebPlayableRelease.Version}}" class="btn btn-info">Docs</a>
|
||||
{{end}}
|
||||
<button id="fullscreenButton" class="btn btn-secondary">Fullscreen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="max-width: 960px; margin: 10px auto; text-align: start;">
|
||||
<h2 class="my-4">Version</h2>
|
||||
<p>{{.WebPlayableRelease.Version}}</p>
|
||||
<h2 class="my-4">Other Versions</h2>
|
||||
<div class="list-group">
|
||||
{{range .Software.Releases}}
|
||||
{{if and .WebPlayable (ne .Version $.WebPlayableRelease.Version)}}
|
||||
<a href="/play/{{$.Software.Name}}/{{.Version}}" class="list-group-item list-group-item-action">
|
||||
{{.Version}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('fullscreenButton').addEventListener('click', function() {
|
||||
var iframe = document.getElementById('gameFrame');
|
||||
if (iframe.requestFullscreen) {
|
||||
iframe.requestFullscreen();
|
||||
} else if (iframe.mozRequestFullScreen) { /* Firefox */
|
||||
iframe.mozRequestFullScreen();
|
||||
} else if (iframe.webkitRequestFullscreen) { /* Chrome, Safari and Opera */
|
||||
iframe.webkitRequestFullscreen();
|
||||
} else if (iframe.msRequestFullscreen) { /* IE/Edge */
|
||||
iframe.msRequestFullscreen();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('fullscreenchange', function() {
|
||||
if (document.fullscreenElement) {
|
||||
document.body.style.overflow = 'hidden';
|
||||
} else {
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
});
|
||||
document.addEventListener('mozfullscreenchange', function() {
|
||||
if (document.mozFullScreenElement) {
|
||||
document.body.style.overflow = 'hidden';
|
||||
} else {
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
});
|
||||
document.addEventListener('webkitfullscreenchange', function() {
|
||||
if (document.webkitFullscreenElement) {
|
||||
document.body.style.overflow = 'hidden';
|
||||
} else {
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
});
|
||||
document.addEventListener('msfullscreenchange', function() {
|
||||
if (document.msFullscreenElement) {
|
||||
document.body.style.overflow = 'hidden';
|
||||
} else {
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{{end}}
|
||||
@@ -1,8 +0,0 @@
|
||||
{{define "content"}}
|
||||
<h1 class="my-4">Welcome</h1>
|
||||
|
||||
<p>
|
||||
Welcome to Teletype Games! We are an independent, community-driven game development collective. Our mission is to create small, experimental, and full-fledged games in short development cycles while keeping everything open, transparent, and collaborative.
|
||||
</p>
|
||||
|
||||
{{end}}
|
||||
@@ -1,70 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Teletype Games</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
|
||||
<link href="/assets/css/style.css" rel="stylesheet" type="text/css" />
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<header class="navbar navbar-expand-lg header-border">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/" >Teletype Games</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav me-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="https://www.youtube.com/@teletypegames" target="_blank">Youtube</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="https://git.teletype.hu/explore/repos" target="_blank">Repositories</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="https://wiki.teletype.hu/" target="_blank">Wiki</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="container mt-4 mb-4">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<div class="content-border">
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item disabled" aria-disabled="true">Softwares</li>
|
||||
{{range .Softwares}}
|
||||
<li class="list-group-item">
|
||||
<a href="/software/{{.Software.Name}}">{{.Software.Name}}</a>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<div class="content-border p-3">
|
||||
{{block "content" .}}{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="footer-border text-center py-3">
|
||||
<p>
|
||||
<a class="nav-link" href="http://teletype.hu" target="_blank">
|
||||
Teletype Games 2025-2026
|
||||
</a>
|
||||
</p>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,15 +0,0 @@
|
||||
{{define "content"}}
|
||||
<h1 class="my-4">Softwares</h1>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12 mb-4">
|
||||
<ul>
|
||||
{{range .Softwares}}
|
||||
<li>
|
||||
<a href="/software/{{.Software.Name}}">{{.Software.Title}}</a>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
@@ -1,100 +0,0 @@
|
||||
{{define "content"}}
|
||||
<h1 class="my-4">{{.Software.Title}}</h1>
|
||||
|
||||
<div class="row">
|
||||
{{if .WebPlayableRelease}}
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-header">
|
||||
<h3>Play</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="ratio ratio-4x3" style="max-width: 960px; margin: 0 auto;">
|
||||
<iframe id="gameFrame" src="/play/{{.Software.Name}}/{{.WebPlayableRelease.Version}}/content/index.html"
|
||||
frameborder="0" allowfullscreen></iframe>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<div class="btn-group d-flex justify-content-center" role="group" aria-label="Game controls">
|
||||
<button id="fullscreenButton" class="btn btn-secondary btn-sm">Fullscreen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-header">
|
||||
<h3>Overview</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-bordered">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row" style="width: 30%;">Title</th>
|
||||
<td>{{.Software.Title}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Author</th>
|
||||
<td>{{.Software.Author}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Description</th>
|
||||
<td>{{.Software.Desc}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Version</th>
|
||||
<td>{{.LatestRelease.Version}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">License</th>
|
||||
<td>{{.Software.License}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Website</th>
|
||||
<td><a href="{{.Software.Site}}" target="_blank">{{.Software.Site}}</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<div class="btn-group d-flex justify-content-end" role="group" aria-label="Operations">
|
||||
<a href="/download/{{.Software.Name}}/source" class="btn btn-primary btn-sm">Source</a>
|
||||
<a href="/download/{{.Software.Name}}/cartridge" class="btn btn-primary btn-sm">Cartridge</a>
|
||||
{{if .WebPlayableRelease.DocsFolderPath}}
|
||||
<a href="/docs/{{.Software.Name}}/{{.WebPlayableRelease.Version}}" class="btn btn-primary btn-sm">Docs</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Releases</h3>
|
||||
<div class="row">
|
||||
{{range .Releases}}
|
||||
<div class="col-12 mb-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Version: {{.Version}}</h5>
|
||||
<p class="card-subtitle mb-2 text-muted">Released At: {{.CreatedAt.Format "2006-01-02 15:04:05"}}</p>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<div class="btn-group d-flex justify-content-center" role="group" aria-label="Operations">
|
||||
{{if .HTMLFolderPath}}
|
||||
<a href="/play/{{$.Software.Name}}/{{.Version}}" class="btn btn-primary btn-sm">Play</a>
|
||||
{{end}}
|
||||
<a href="/download/{{$.Software.Name}}/{{.Version}}/source"
|
||||
class="btn btn-primary btn-sm">Source</a>
|
||||
<a href="/download/{{$.Software.Name}}/{{.Version}}/cartridge"
|
||||
class="btn btn-primary btn-sm">Cartridge</a>
|
||||
{{if .DocsFolderPath}}
|
||||
<a href="/docs/{{$.Software.Name}}/{{.Version}}" class="btn btn-primary btn-sm">Docs</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
Reference in New Issue
Block a user