tracks can be edited! + major template overhaul
This commit is contained in:
parent
99b6a21179
commit
63122eb428
|
@ -14,7 +14,7 @@ tmp_dir = "tmp"
|
||||||
follow_symlink = false
|
follow_symlink = false
|
||||||
full_bin = ""
|
full_bin = ""
|
||||||
include_dir = []
|
include_dir = []
|
||||||
include_ext = ["go", "tpl", "tmpl"]
|
include_ext = ["go", "tpl", "tmpl", "html"]
|
||||||
include_file = []
|
include_file = []
|
||||||
kill_delay = "0s"
|
kill_delay = "0s"
|
||||||
log = "build-errors.log"
|
log = "build-errors.log"
|
||||||
|
|
23
admin/components/release/release-list-item.html
Normal file
23
admin/components/release/release-list-item.html
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
{{define "release"}}
|
||||||
|
<div class="release">
|
||||||
|
<div class="release-artwork">
|
||||||
|
<img src="{{.Artwork}}" alt="" width="128" loading="lazy">
|
||||||
|
</div>
|
||||||
|
<div class="release-info">
|
||||||
|
<h3 class="release-title">
|
||||||
|
<a href="/admin/release/{{.ID}}">{{.Title}}</a>
|
||||||
|
<small>
|
||||||
|
{{.GetReleaseYear}}
|
||||||
|
{{if not .Visible}}(hidden){{end}}
|
||||||
|
</small>
|
||||||
|
</h3>
|
||||||
|
<p class="release-artists">{{.PrintArtists true true}}</p>
|
||||||
|
<p class="release-type-single">{{.ReleaseType}}
|
||||||
|
(<a href="/admin/release/{{.ID}}#tracks">{{len .Tracks}} track{{if not (eq (len .Tracks) 1)}}s{{end}}</a>)</p>
|
||||||
|
<div class="release-actions">
|
||||||
|
<a href="/admin/release/{{.ID}}">Edit</a>
|
||||||
|
<a href="/music/{{.ID}}" target="_blank">Gateway</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
|
@ -12,6 +12,7 @@ import (
|
||||||
|
|
||||||
"arimelody.me/arimelody.me/discord"
|
"arimelody.me/arimelody.me/discord"
|
||||||
"arimelody.me/arimelody.me/global"
|
"arimelody.me/arimelody.me/global"
|
||||||
|
musicController "arimelody.me/arimelody.me/music/controller"
|
||||||
musicModel "arimelody.me/arimelody.me/music/model"
|
musicModel "arimelody.me/arimelody.me/music/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -27,6 +28,19 @@ func Handler() http.Handler {
|
||||||
mux.Handle("/logout", MustAuthorise(LogoutHandler()))
|
mux.Handle("/logout", MustAuthorise(LogoutHandler()))
|
||||||
mux.Handle("/static/", http.StripPrefix("/static", staticHandler()))
|
mux.Handle("/static/", http.StripPrefix("/static", staticHandler()))
|
||||||
mux.Handle("/release/", MustAuthorise(http.StripPrefix("/release", serveRelease())))
|
mux.Handle("/release/", MustAuthorise(http.StripPrefix("/release", serveRelease())))
|
||||||
|
mux.Handle("/track/", MustAuthorise(http.StripPrefix("/track", serveTrack())))
|
||||||
|
mux.Handle("/createtrack", MustAuthorise(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
track := musicModel.Track{ Title: "Untitled Track" }
|
||||||
|
trackID, err := musicController.CreateTrackDB(global.DB, &track)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to create track: %s\n", err)
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
track.ID = trackID
|
||||||
|
global.Tracks = append(global.Tracks, &track)
|
||||||
|
http.Redirect(w, r, fmt.Sprintf("/admin/track/%s", trackID), http.StatusTemporaryRedirect)
|
||||||
|
})))
|
||||||
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path != "/" {
|
if r.URL.Path != "/" {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
@ -61,11 +75,16 @@ func Handler() http.Handler {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
serveTemplate("index.html", IndexData{
|
err := pages["index"].Execute(w, IndexData{
|
||||||
Releases: global.Releases,
|
Releases: global.Releases,
|
||||||
Artists: global.Artists,
|
Artists: global.Artists,
|
||||||
Tracks: tracks,
|
Tracks: tracks,
|
||||||
}).ServeHTTP(w, r)
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error executing template: %s\n", err)
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return mux
|
return mux
|
||||||
|
@ -144,7 +163,7 @@ func LoginHandler() http.Handler {
|
||||||
code := r.URL.Query().Get("code")
|
code := r.URL.Query().Get("code")
|
||||||
|
|
||||||
if code == "" {
|
if code == "" {
|
||||||
serveTemplate("login.html", loginData{DiscordURI: discord.REDIRECT_URI}).ServeHTTP(w, r)
|
pages["login"].Execute(w, loginData{DiscordURI: discord.REDIRECT_URI})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,7 +202,12 @@ func LoginHandler() http.Handler {
|
||||||
cookie.Path = "/"
|
cookie.Path = "/"
|
||||||
http.SetCookie(w, &cookie)
|
http.SetCookie(w, &cookie)
|
||||||
|
|
||||||
serveTemplate("login.html", loginData{Token: session.Token}).ServeHTTP(w, r)
|
err = pages["login"].Execute(w, loginData{Token: session.Token})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error rendering admin login page: %s\n", err)
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,72 +231,9 @@ func LogoutHandler() http.Handler {
|
||||||
return new_sessions
|
return new_sessions
|
||||||
}(session.Token)
|
}(session.Token)
|
||||||
|
|
||||||
serveTemplate("logout.html", nil).ServeHTTP(w, r)
|
err := pages["logout"].Execute(w, nil)
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func serveTemplate(page string, data any) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
lp_layout := filepath.Join("admin", "views", "layout.html")
|
|
||||||
lp_prideflag := filepath.Join("views", "prideflag.html")
|
|
||||||
fp := filepath.Join("admin", "views", filepath.Clean(page))
|
|
||||||
|
|
||||||
info, err := os.Stat(fp)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
fmt.Printf("Error rendering admin logout page: %s\n", err)
|
||||||
http.NotFound(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if info.IsDir() {
|
|
||||||
http.NotFound(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
template, err := template.ParseFiles(lp_layout, lp_prideflag, fp)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error parsing template files: %s\n", err)
|
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = template.ExecuteTemplate(w, "layout.html", data)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error executing template: %s\n", err)
|
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func serveComponent(page string, data any) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
fp := filepath.Join("admin", "components", filepath.Clean(page))
|
|
||||||
|
|
||||||
info, err := os.Stat(fp)
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
http.NotFound(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if info.IsDir() {
|
|
||||||
http.NotFound(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
template, err := template.ParseFiles(fp)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error parsing template files: %s\n", err)
|
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = template.Execute(w, data);
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error executing template: %s\n", err)
|
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"arimelody.me/arimelody.me/global"
|
"arimelody.me/arimelody.me/global"
|
||||||
|
@ -71,13 +70,10 @@ func serveRelease() http.Handler {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
lrw := global.LoggingResponseWriter{ResponseWriter: w, Code: http.StatusOK}
|
err := pages["release"].Execute(w, gatewayRelease{release, tracks})
|
||||||
|
if err != nil {
|
||||||
serveTemplate("edit-release.html", gatewayRelease{release, tracks}).ServeHTTP(&lrw, r)
|
fmt.Printf("Error rendering admin release page for %s: %s\n", id, err)
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
if lrw.Code != http.StatusOK {
|
|
||||||
fmt.Printf("Error rendering admin release page for %s\n", id)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -85,8 +81,11 @@ func serveRelease() http.Handler {
|
||||||
func serveEditCredits(release *model.Release) http.Handler {
|
func serveEditCredits(release *model.Release) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
serveComponent(path.Join("credits", "editcredits.html"), release).ServeHTTP(w, r)
|
err := components["editcredits"].Execute(w, release)
|
||||||
return
|
if err != nil {
|
||||||
|
fmt.Printf("Error rendering edit credits component for %s: %s\n", release.ID, err)
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,11 +111,14 @@ func serveAddCredit(release *model.Release) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
serveComponent(path.Join("credits", "addcredit.html"), response{
|
err := components["addcredit"].Execute(w, response{
|
||||||
ReleaseID: release.ID,
|
ReleaseID: release.ID,
|
||||||
Artists: artists,
|
Artists: artists,
|
||||||
}).ServeHTTP(w, r)
|
})
|
||||||
return
|
if err != nil {
|
||||||
|
fmt.Printf("Error rendering add credits component for %s: %s\n", release.ID, err)
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,7 +131,11 @@ func serveNewCredit() http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
serveComponent(path.Join("credits", "newcredit.html"), artist).ServeHTTP(w, r)
|
err := components["newcredit"].Execute(w, artist)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error rendering new credit component for %s: %s\n", artist.ID, err)
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -137,7 +143,11 @@ func serveNewCredit() http.Handler {
|
||||||
func serveEditLinks(release *model.Release) http.Handler {
|
func serveEditLinks(release *model.Release) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
serveComponent(path.Join("links", "editlinks.html"), release).ServeHTTP(w, r)
|
err := components["editlinks"].Execute(w, release)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error rendering edit links component for %s: %s\n", release.ID, err)
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -158,7 +168,11 @@ func serveEditTracks(release *model.Release) http.Handler {
|
||||||
data.Tracks = append(data.Tracks, Track{track, i + 1})
|
data.Tracks = append(data.Tracks, Track{track, i + 1})
|
||||||
}
|
}
|
||||||
|
|
||||||
serveComponent(path.Join("tracks", "edittracks.html"), data).ServeHTTP(w, r)
|
err := components["edittracks"].Execute(w, data)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error rendering edit tracks component for %s: %s\n", release.ID, err)
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -185,10 +199,14 @@ func serveAddTrack(release *model.Release) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
serveComponent(path.Join("tracks", "addtrack.html"), response{
|
err := components["addtrack"].Execute(w, response{
|
||||||
ReleaseID: release.ID,
|
ReleaseID: release.ID,
|
||||||
Tracks: tracks,
|
Tracks: tracks,
|
||||||
}).ServeHTTP(w, r)
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error rendering add tracks component for %s: %s\n", release.ID, err)
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -207,10 +225,14 @@ func serveNewTrack(release *model.Release) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
serveComponent(path.Join("tracks", "newtrack.html"), Track{
|
err := components["newtrack"].Execute(w, Track{
|
||||||
track,
|
track,
|
||||||
len(release.Tracks) + 1,
|
len(release.Tracks) + 1,
|
||||||
}).ServeHTTP(w, r)
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error rendering new track component for %s: %s\n", track.ID, err)
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,10 +93,12 @@ a img {
|
||||||
margin: 0 0 .5em 0;
|
margin: 0 0 .5em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
.card h3,
|
.card h3,
|
||||||
.card p {
|
.card p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
.card-title {
|
.card-title {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
|
|
|
@ -87,9 +87,8 @@ input[type="text"] {
|
||||||
.release-info table td select,
|
.release-info table td select,
|
||||||
.release-info table td input,
|
.release-info table td input,
|
||||||
.release-info table td textarea {
|
.release-info table td textarea {
|
||||||
padding: .2em;
|
|
||||||
resize: none;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
padding: .2em;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
@ -212,6 +211,10 @@ dialog div.dialog-actions {
|
||||||
border: 1px solid #808080;
|
border: 1px solid #808080;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card.credits .credit p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.card.credits .credit .artist-avatar {
|
.card.credits .credit .artist-avatar {
|
||||||
border-radius: .5em;
|
border-radius: .5em;
|
||||||
}
|
}
|
||||||
|
@ -441,6 +444,11 @@ dialog div.dialog-actions {
|
||||||
border: 1px solid #808080;
|
border: 1px solid #808080;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card.tracks .track h3,
|
||||||
|
.card.tracks .track p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.card.tracks h2.track-title {
|
.card.tracks h2.track-title {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -1,112 +1,127 @@
|
||||||
import Stateful from "/script/silver.min.js"
|
import Stateful from "/script/silver.min.js"
|
||||||
|
|
||||||
const releaseID = document.getElementById("release").dataset.id;
|
const releaseID = document.getElementById("release").dataset.id;
|
||||||
const title_input = document.getElementById("title");
|
const titleInput = document.getElementById("title");
|
||||||
const artwork_img = document.getElementById("artwork");
|
const artworkImg = document.getElementById("artwork");
|
||||||
const artwork_input = document.getElementById("artwork-file");
|
const artworkInput = document.getElementById("artwork-file");
|
||||||
const type_input = document.getElementById("type");
|
const typeInput = document.getElementById("type");
|
||||||
const desc_input = document.getElementById("description");
|
const descInput = document.getElementById("description");
|
||||||
const date_input = document.getElementById("release-date");
|
const dateInput = document.getElementById("release-date");
|
||||||
const buyname_input = document.getElementById("buyname");
|
const buynameInput = document.getElementById("buyname");
|
||||||
const buylink_input = document.getElementById("buylink");
|
const buylinkInput = document.getElementById("buylink");
|
||||||
const vis_input = document.getElementById("visibility");
|
const visInput = document.getElementById("visibility");
|
||||||
const save_btn = document.getElementById("save");
|
const saveBtn = document.getElementById("save");
|
||||||
|
const deleteBtn = document.getElementById("delete");
|
||||||
var artwork_data = artwork_img.attributes.src.value;
|
|
||||||
|
|
||||||
var token = atob(localStorage.getItem("arime-token"));
|
|
||||||
|
|
||||||
|
var artworkData = artworkImg.attributes.src.value;
|
||||||
var edited = new Stateful(false);
|
var edited = new Stateful(false);
|
||||||
|
var releaseData = updateData(undefined);
|
||||||
|
|
||||||
var release_data = update_data(undefined);
|
function updateData(old) {
|
||||||
|
var releaseData = {
|
||||||
function update_data(old) {
|
visible: visInput.value === "true",
|
||||||
var release_data = {
|
title: titleInput.value,
|
||||||
visible: vis_input.value === "true",
|
description: descInput.value,
|
||||||
title: title_input.value,
|
type: typeInput.value,
|
||||||
description: desc_input.value,
|
releaseDate: dateInput.value,
|
||||||
type: type_input.value,
|
artwork: artworkData,
|
||||||
releaseDate: date_input.value,
|
buyname: buynameInput.value,
|
||||||
artwork: artwork_data,
|
buylink: buylinkInput.value,
|
||||||
buyname: buyname_input.value,
|
|
||||||
buylink: buylink_input.value,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (release_data && release_data != old) {
|
if (releaseData && releaseData != old) {
|
||||||
edited.set(true);
|
edited.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return release_data;
|
return releaseData;
|
||||||
}
|
}
|
||||||
|
|
||||||
function save_release() {
|
function saveRelease() {
|
||||||
console.table(release_data);
|
console.table(releaseData);
|
||||||
|
|
||||||
(async () => {
|
fetch("/api/v1/music/" + releaseID, {
|
||||||
const res = await fetch(
|
|
||||||
"/api/v1/music/" + releaseID, {
|
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
body: JSON.stringify(release_data),
|
body: JSON.stringify(releaseData),
|
||||||
headers: {
|
headers: { "Content-Type": "application/json" }
|
||||||
"Content-Type": "application/json",
|
}).then(res => {
|
||||||
"Authorisation": "Bearer " + token,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const text = await res.text();
|
res.text().then(error => {
|
||||||
console.error(text);
|
console.error(error);
|
||||||
alert(text);
|
alert("Failed to update release: " + error);
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
location = location;
|
location = location;
|
||||||
})();
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteRelease() {
|
||||||
|
fetch("/api/v1/music/" + releaseID, {
|
||||||
|
method: "DELETE",
|
||||||
|
}).then(res => {
|
||||||
|
if (!res.ok) {
|
||||||
|
res.text().then(error => {
|
||||||
|
console.error(error);
|
||||||
|
alert("Failed to delete release: " + error);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = "/admin";
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
edited.onUpdate(edited => {
|
edited.onUpdate(edited => {
|
||||||
save_btn.disabled = !edited;
|
saveBtn.disabled = !edited;
|
||||||
})
|
})
|
||||||
|
|
||||||
title_input.addEventListener("change", () => {
|
titleInput.addEventListener("change", () => {
|
||||||
release_data = update_data(release_data);
|
releaseData = updateData(releaseData);
|
||||||
});
|
});
|
||||||
artwork_img.addEventListener("click", () => {
|
artworkImg.addEventListener("click", () => {
|
||||||
artwork_input.addEventListener("change", () => {
|
artworkInput.addEventListener("change", () => {
|
||||||
if (artwork_input.files.length > 0) {
|
if (artworkInput.files.length > 0) {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = e => {
|
reader.onload = e => {
|
||||||
const data = e.target.result;
|
const data = e.target.result;
|
||||||
artwork_img.src = data;
|
artworkImg.src = data;
|
||||||
artwork_data = data;
|
artworkData = data;
|
||||||
release_data = update_data(release_data);
|
releaseData = updateData(releaseData);
|
||||||
};
|
};
|
||||||
reader.readAsDataURL(artwork_input.files[0]);
|
reader.readAsDataURL(artworkInput.files[0]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
artwork_input.click();
|
artworkInput.click();
|
||||||
});
|
});
|
||||||
type_input.addEventListener("change", () => {
|
typeInput.addEventListener("change", () => {
|
||||||
release_data = update_data(release_data);
|
releaseData = updateData(releaseData);
|
||||||
});
|
});
|
||||||
desc_input.addEventListener("change", () => {
|
descInput.addEventListener("change", () => {
|
||||||
release_data = update_data(release_data);
|
releaseData = updateData(releaseData);
|
||||||
});
|
});
|
||||||
date_input.addEventListener("change", () => {
|
dateInput.addEventListener("change", () => {
|
||||||
release_data = update_data(release_data);
|
releaseData = updateData(releaseData);
|
||||||
});
|
});
|
||||||
buyname_input.addEventListener("change", () => {
|
buynameInput.addEventListener("change", () => {
|
||||||
release_data = update_data(release_data);
|
releaseData = updateData(releaseData);
|
||||||
});
|
});
|
||||||
buylink_input.addEventListener("change", () => {
|
buylinkInput.addEventListener("change", () => {
|
||||||
release_data = update_data(release_data);
|
releaseData = updateData(releaseData);
|
||||||
});
|
});
|
||||||
vis_input.addEventListener("change", () => {
|
visInput.addEventListener("change", () => {
|
||||||
release_data = update_data(release_data);
|
releaseData = updateData(releaseData);
|
||||||
});
|
});
|
||||||
|
|
||||||
save_btn.addEventListener("click", () => {
|
saveBtn.addEventListener("click", () => {
|
||||||
if (!edited.get()) return;
|
if (!edited.get()) return;
|
||||||
|
saveRelease();
|
||||||
|
});
|
||||||
|
|
||||||
save_release();
|
deleteBtn.addEventListener("click", () => {
|
||||||
})
|
if (releaseID != prompt(
|
||||||
|
"You are about to permanently delete " + releaseID + ". " +
|
||||||
|
"This action is irreversible. " +
|
||||||
|
"Please enter \"" + releaseID + "\" to continue.")) return;
|
||||||
|
deleteRelease();
|
||||||
|
});
|
||||||
|
|
219
admin/static/edit-track.css
Normal file
219
admin/static/edit-track.css
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
h1 {
|
||||||
|
margin: 0 0 .5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#track {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
padding: 1.5em;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 1.2em;
|
||||||
|
|
||||||
|
border-radius: .5em;
|
||||||
|
background: #f8f8f8f8;
|
||||||
|
border: 1px solid #808080;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-info {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-title-header {
|
||||||
|
margin: 0;
|
||||||
|
opacity: .5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-title {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#title {
|
||||||
|
width: 100%;
|
||||||
|
margin: -.1em -.2em;
|
||||||
|
padding: .1em .2em;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: inherit;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
background: transparent;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#title:hover {
|
||||||
|
background: #ffffff;
|
||||||
|
border-color: #80808080;
|
||||||
|
}
|
||||||
|
|
||||||
|
#title:active,
|
||||||
|
#title:focus {
|
||||||
|
background: #ffffff;
|
||||||
|
border-color: #808080;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-title small {
|
||||||
|
opacity: .75;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-info h2 {
|
||||||
|
margin-bottom: .4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-info textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: .5em;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
color: inherit;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
resize: vertical;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button, .button {
|
||||||
|
padding: .5em .8em;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
border-radius: .5em;
|
||||||
|
border: 1px solid #a0a0a0;
|
||||||
|
background: #f0f0f0;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
button:hover, .button:hover {
|
||||||
|
background: #fff;
|
||||||
|
border-color: #d0d0d0;
|
||||||
|
}
|
||||||
|
button:active, .button:active {
|
||||||
|
background: #d0d0d0;
|
||||||
|
border-color: #808080;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
button.save {
|
||||||
|
background: #6fd7ff;
|
||||||
|
border-color: #6f9eb0;
|
||||||
|
}
|
||||||
|
button.delete {
|
||||||
|
background: #ff7171;
|
||||||
|
border-color: #7d3535;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
background: #fff;
|
||||||
|
border-color: #d0d0d0;
|
||||||
|
}
|
||||||
|
button:active {
|
||||||
|
background: #d0d0d0;
|
||||||
|
border-color: #808080;
|
||||||
|
}
|
||||||
|
button[disabled] {
|
||||||
|
background: #d0d0d0 !important;
|
||||||
|
border-color: #808080 !important;
|
||||||
|
opacity: .5;
|
||||||
|
cursor: not-allowed !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.delete {
|
||||||
|
color: #d22828;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-actions {
|
||||||
|
margin-top: 1em;
|
||||||
|
display: flex;
|
||||||
|
gap: .5em;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.release {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
padding: 1em;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 1em;
|
||||||
|
|
||||||
|
border-radius: .5em;
|
||||||
|
background: #f8f8f8f8;
|
||||||
|
border: 1px solid #808080;
|
||||||
|
}
|
||||||
|
|
||||||
|
.release h3,
|
||||||
|
.release p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.release-artwork {
|
||||||
|
width: 96px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.release-artwork img {
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.release-title small {
|
||||||
|
opacity: .75;
|
||||||
|
}
|
||||||
|
|
||||||
|
.release-links {
|
||||||
|
margin: .5em 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
list-style: none;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.release-links li {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.release-links a {
|
||||||
|
padding: .5em;
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
border-radius: .5em;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #f0f0f0;
|
||||||
|
background: #303030;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
transition: color .1s, background .1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.release-links a:hover {
|
||||||
|
color: #303030;
|
||||||
|
background: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.release-actions {
|
||||||
|
margin-top: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.release-actions a {
|
||||||
|
margin-right: .3em;
|
||||||
|
padding: .3em .5em;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
border-radius: .3em;
|
||||||
|
background: #e0e0e0;
|
||||||
|
|
||||||
|
transition: color .1s, background .1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.release-actions a:hover {
|
||||||
|
color: #303030;
|
||||||
|
background: #f0f0f0;
|
||||||
|
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
58
admin/static/edit-track.js
Normal file
58
admin/static/edit-track.js
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
const trackID = document.getElementById("track").dataset.id;
|
||||||
|
const titleInput = document.getElementById("title");
|
||||||
|
const descInput = document.getElementById("description");
|
||||||
|
const lyricsInput = document.getElementById("lyrics");
|
||||||
|
const saveBtn = document.getElementById("save");
|
||||||
|
const deleteBtn = document.getElementById("delete");
|
||||||
|
|
||||||
|
saveBtn.addEventListener("click", () => {
|
||||||
|
fetch("/api/v1/track/" + trackID, {
|
||||||
|
method: "PUT",
|
||||||
|
body: JSON.stringify({
|
||||||
|
title: titleInput.value,
|
||||||
|
description: descInput.value,
|
||||||
|
lyrics: lyricsInput.value,
|
||||||
|
}),
|
||||||
|
headers: { "Content-Type": "application/json" }
|
||||||
|
}).then(res => {
|
||||||
|
if (!res.ok) {
|
||||||
|
res.text().then(error => {
|
||||||
|
console.error(error);
|
||||||
|
alert("Failed to update track: " + error);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = location;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
deleteBtn.addEventListener("click", () => {
|
||||||
|
if (!confirm(
|
||||||
|
"You are about to permanently delete \"" + titleInput.value + "\".\n" +
|
||||||
|
"This action is irreversible. Do you wish to continue?")) return;
|
||||||
|
|
||||||
|
fetch("/api/v1/track/" + trackID, {
|
||||||
|
method: "DELETE",
|
||||||
|
}).then(res => {
|
||||||
|
if (!res.ok) {
|
||||||
|
res.text().then(error => {
|
||||||
|
console.error(error);
|
||||||
|
alert("Failed to delete track: " + error);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = "/admin";
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
[titleInput, descInput, lyricsInput].forEach(input => {
|
||||||
|
input.addEventListener("change", () => {
|
||||||
|
saveBtn.disabled = false;
|
||||||
|
});
|
||||||
|
input.addEventListener("keypress", () => {
|
||||||
|
saveBtn.disabled = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -28,6 +28,11 @@
|
||||||
border: 1px solid #808080;
|
border: 1px solid #808080;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.release h3,
|
||||||
|
.release p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.release-artwork {
|
.release-artwork {
|
||||||
width: 96px;
|
width: 96px;
|
||||||
|
|
||||||
|
@ -140,6 +145,10 @@
|
||||||
border: 1px solid #808080;
|
border: 1px solid #808080;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.track p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.card h2.track-title {
|
.card h2.track-title {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
50
admin/templates.go
Normal file
50
admin/templates.go
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
var pages = map[string]*template.Template{
|
||||||
|
"index": template.Must(template.ParseFiles(
|
||||||
|
filepath.Join("admin", "views", "layout.html"),
|
||||||
|
filepath.Join("views", "prideflag.html"),
|
||||||
|
filepath.Join("admin", "components", "release", "release-list-item.html"),
|
||||||
|
filepath.Join("admin", "views", "index.html"),
|
||||||
|
)),
|
||||||
|
|
||||||
|
"login": template.Must(template.ParseFiles(
|
||||||
|
filepath.Join("admin", "views", "layout.html"),
|
||||||
|
filepath.Join("views", "prideflag.html"),
|
||||||
|
filepath.Join("admin", "views", "login.html"),
|
||||||
|
)),
|
||||||
|
"logout": template.Must(template.ParseFiles(
|
||||||
|
filepath.Join("admin", "views", "layout.html"),
|
||||||
|
filepath.Join("views", "prideflag.html"),
|
||||||
|
filepath.Join("admin", "views", "logout.html"),
|
||||||
|
)),
|
||||||
|
|
||||||
|
"release": template.Must(template.ParseFiles(
|
||||||
|
filepath.Join("admin", "views", "layout.html"),
|
||||||
|
filepath.Join("views", "prideflag.html"),
|
||||||
|
filepath.Join("admin", "views", "edit-release.html"),
|
||||||
|
)),
|
||||||
|
"track": template.Must(template.ParseFiles(
|
||||||
|
filepath.Join("admin", "views", "layout.html"),
|
||||||
|
filepath.Join("views", "prideflag.html"),
|
||||||
|
filepath.Join("admin", "components", "release", "release-list-item.html"),
|
||||||
|
filepath.Join("admin", "views", "edit-track.html"),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
|
||||||
|
var components = map[string]*template.Template{
|
||||||
|
"editcredits": template.Must(template.ParseFiles(filepath.Join("admin", "components", "credits", "editcredits.html"))),
|
||||||
|
"addcredit": template.Must(template.ParseFiles(filepath.Join("admin", "components", "credits", "addcredit.html"))),
|
||||||
|
"newcredit": template.Must(template.ParseFiles(filepath.Join("admin", "components", "credits", "newcredit.html"))),
|
||||||
|
|
||||||
|
"editlinks": template.Must(template.ParseFiles(filepath.Join("admin", "components", "links", "editlinks.html"))),
|
||||||
|
|
||||||
|
"edittracks": template.Must(template.ParseFiles(filepath.Join("admin", "components", "tracks", "addtrack.html"))),
|
||||||
|
"addtrack": template.Must(template.ParseFiles(filepath.Join("admin", "components", "tracks", "addtrack.html"))),
|
||||||
|
"newtrack": template.Must(template.ParseFiles(filepath.Join("admin", "components", "tracks", "addtrack.html"))),
|
||||||
|
}
|
28
admin/trackhttp.go
Normal file
28
admin/trackhttp.go
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"arimelody.me/arimelody.me/global"
|
||||||
|
)
|
||||||
|
|
||||||
|
func serveTrack() http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
slices := strings.Split(r.URL.Path[1:], "/")
|
||||||
|
id := slices[0]
|
||||||
|
track := global.GetTrack(id)
|
||||||
|
if track == nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := pages["track"].Execute(w, track)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error rendering admin track page for %s: %s\n", id, err)
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{{define "head"}}
|
{{define "head"}}
|
||||||
<title>editing {{.Title}} - ari melody 💫</title>
|
<title>Editing {{.Title}} - ari melody 💫</title>
|
||||||
<link rel="shortcut icon" href="{{.GetArtwork}}" type="image/x-icon">
|
<link rel="shortcut icon" href="{{.GetArtwork}}" type="image/x-icon">
|
||||||
|
|
||||||
<link rel="stylesheet" href="/admin/static/edit-release.css">
|
<link rel="stylesheet" href="/admin/static/edit-release.css">
|
||||||
|
@ -129,7 +129,7 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-title">
|
<div class="card-title" id="tracks">
|
||||||
<h2>Tracklist ({{len .Tracks}})</h2>
|
<h2>Tracklist ({{len .Tracks}})</h2>
|
||||||
<a class="button edit"
|
<a class="button edit"
|
||||||
href="/admin/release/{{.ID}}/edittracks"
|
href="/admin/release/{{.ID}}/edittracks"
|
||||||
|
@ -163,6 +163,18 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card-title">
|
||||||
|
<h2>Danger Zone</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card danger">
|
||||||
|
<p>
|
||||||
|
Clicking the button below will delete this release.
|
||||||
|
This action is <strong>irreversible</strong>.
|
||||||
|
You will be prompted to confirm this decision.
|
||||||
|
</p>
|
||||||
|
<button class="delete" id="delete">Delete Release</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script type="module" src="/admin/static/edit-release.js" defer></script>
|
<script type="module" src="/admin/static/edit-release.js" defer></script>
|
||||||
|
|
68
admin/views/edit-track.html
Normal file
68
admin/views/edit-track.html
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
{{define "head"}}
|
||||||
|
<title>Editing Track - ari melody 💫</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/admin/static/edit-track.css">
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "content"}}
|
||||||
|
<main>
|
||||||
|
<h1>Editing Track "{{.Title}}"</h1>
|
||||||
|
|
||||||
|
<div id="track" data-id="{{.ID}}">
|
||||||
|
<div class="track-info">
|
||||||
|
<p class="track-title-header">Title</p>
|
||||||
|
<h2 class="track-title">
|
||||||
|
<input type="text" id="title" name="Title" value="{{.Title}}">
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<h2>Description</h2>
|
||||||
|
<textarea
|
||||||
|
name="Description"
|
||||||
|
value="{{.Description}}"
|
||||||
|
placeholder="No description provided."
|
||||||
|
rows="5"
|
||||||
|
id="description"
|
||||||
|
>{{.Description}}</textarea>
|
||||||
|
|
||||||
|
<h2>Lyrics</h2>
|
||||||
|
<textarea
|
||||||
|
name="Lyrics"
|
||||||
|
value="{{.Lyrics}}"
|
||||||
|
placeholder="There are no lyrics."
|
||||||
|
rows="5"
|
||||||
|
id="lyrics"
|
||||||
|
>{{.Lyrics}}</textarea>
|
||||||
|
|
||||||
|
<div class="track-actions">
|
||||||
|
<button type="submit" class="save" id="save" disabled>Save</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-title">
|
||||||
|
<h2>Featured in</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card releases">
|
||||||
|
{{if .Release}}
|
||||||
|
{{block "release" .Release}}{{end}}
|
||||||
|
{{else}}
|
||||||
|
<p>This track isn't bound to a release.</p>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-title">
|
||||||
|
<h2>Danger Zone</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card danger">
|
||||||
|
<p>
|
||||||
|
Clicking the button below will delete this track.
|
||||||
|
This action is <strong>irreversible</strong>.
|
||||||
|
You will be prompted to confirm this decision.
|
||||||
|
</p>
|
||||||
|
<button class="delete" id="delete">Delete Track</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script type="module" src="/admin/static/edit-track.js" defer></script>
|
||||||
|
{{end}}
|
|
@ -1,5 +1,5 @@
|
||||||
{{define "head"}}
|
{{define "head"}}
|
||||||
<title>admin - ari melody 💫</title>
|
<title>Admin - ari melody 💫</title>
|
||||||
<link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon">
|
<link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon">
|
||||||
<link rel="stylesheet" href="/admin/static/index.css">
|
<link rel="stylesheet" href="/admin/static/index.css">
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -12,28 +12,8 @@
|
||||||
<a href="/admin/createrelease" class="create-btn" id="create-release">Create New</a>
|
<a href="/admin/createrelease" class="create-btn" id="create-release">Create New</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="card releases">
|
<div class="card releases">
|
||||||
{{range $Release := .Releases}}
|
{{range .Releases}}
|
||||||
<div class="release">
|
{{block "release" .}}{{end}}
|
||||||
<div class="release-artwork">
|
|
||||||
<img src="{{$Release.Artwork}}" alt="" width="128" loading="lazy">
|
|
||||||
</div>
|
|
||||||
<div class="release-info">
|
|
||||||
<h3 class="release-title">
|
|
||||||
{{$Release.Title}}
|
|
||||||
<small>
|
|
||||||
{{$Release.GetReleaseYear}}
|
|
||||||
{{if not $Release.Visible}}(hidden){{end}}
|
|
||||||
</small>
|
|
||||||
</h3>
|
|
||||||
<p class="release-artists">{{$Release.PrintArtists true true}}</p>
|
|
||||||
<p class="release-type-single">{{$Release.ReleaseType}}
|
|
||||||
({{len $Release.Tracks}} track{{if not (eq (len $Release.Tracks) 1)}}s{{end}})</p>
|
|
||||||
<div class="release-actions">
|
|
||||||
<a href="/admin/release/{{$Release.ID}}">Edit</a>
|
|
||||||
<a href="/music/{{$Release.ID}}" target="_blank">Gateway</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if not .Releases}}
|
{{if not .Releases}}
|
||||||
<p>There are no releases.</p>
|
<p>There are no releases.</p>
|
||||||
|
@ -66,14 +46,13 @@
|
||||||
{{range $Track := .Tracks}}
|
{{range $Track := .Tracks}}
|
||||||
<div class="track">
|
<div class="track">
|
||||||
<h2 class="track-title">
|
<h2 class="track-title">
|
||||||
{{$Track.Title}}
|
<a href="/admin/track/{{$Track.ID}}">{{$Track.Title}}</a>
|
||||||
{{if $Track.Release}}
|
{{if $Track.Release}}
|
||||||
<small class="track-album">{{$Track.Release.Title}}</small>
|
<small class="track-album">{{$Track.Release.Title}}</small>
|
||||||
{{else}}
|
{{else}}
|
||||||
<small class="track-album empty">(no release)</small>
|
<small class="track-album empty">(no release)</small>
|
||||||
{{end}}
|
{{end}}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="track-id">{{$Track.ID}}</p>
|
|
||||||
{{if $Track.Description}}
|
{{if $Track.Description}}
|
||||||
<p class="track-description">{{$Track.Description}}</p>
|
<p class="track-description">{{$Track.Description}}</p>
|
||||||
{{else}}
|
{{else}}
|
||||||
|
|
|
@ -23,8 +23,7 @@
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{{block "content" .}}
|
{{block "content" .}}{{end}}
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{template "prideflag"}}
|
{{template "prideflag"}}
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{{define "head"}}
|
{{define "head"}}
|
||||||
<title>login - ari melody 💫</title>
|
<title>Login - ari melody 💫</title>
|
||||||
<link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon">
|
<link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon">
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -15,21 +15,18 @@ a.discord {
|
||||||
|
|
||||||
{{define "content"}}
|
{{define "content"}}
|
||||||
<main>
|
<main>
|
||||||
|
|
||||||
{{if .Token}}
|
{{if .Token}}
|
||||||
|
|
||||||
<meta http-equiv="refresh" content="5;url=/admin/" />
|
<meta http-equiv="refresh" content="5;url=/admin/" />
|
||||||
<meta name="token" content="{{.Token}}" />
|
|
||||||
<p>
|
<p>
|
||||||
Logged in successfully.
|
Logged in successfully.
|
||||||
You should be redirected to <a href="/admin">/admin</a> in 5 seconds.
|
You should be redirected to <a href="/admin">/admin</a> in 5 seconds.
|
||||||
<script>
|
|
||||||
const token = document.querySelector("meta[name=token]").content;
|
|
||||||
localStorage.setItem("arime-token", btoa(token));
|
|
||||||
</script>
|
|
||||||
</p>
|
</p>
|
||||||
{{else}}
|
|
||||||
<p>Log in with <a href="{{.DiscordURI}}" class="discord">Discord</a>.</p>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
|
{{else}}
|
||||||
|
|
||||||
|
<p>Log in with <a href="{{.DiscordURI}}" class="discord">Discord</a>.</p>
|
||||||
|
|
||||||
|
{{end}}
|
||||||
</main>
|
</main>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{{define "head"}}
|
{{define "head"}}
|
||||||
<title>admin - ari melody 💫</title>
|
<title>Admin - ari melody 💫</title>
|
||||||
<link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon">
|
<link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon">
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -262,13 +262,8 @@ func UpdateRelease() http.Handler {
|
||||||
update.Artwork = *data.Artwork
|
update.Artwork = *data.Artwork
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if data.Buyname != nil {
|
|
||||||
if *data.Buyname == "" {
|
if data.Buyname != nil { update.Buyname = *data.Buyname }
|
||||||
http.Error(w, "Release buy name cannot be empty", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
update.Buyname = *data.Buyname
|
|
||||||
}
|
|
||||||
if data.Buylink != nil { update.Buylink = *data.Buylink }
|
if data.Buylink != nil { update.Buylink = *data.Buylink }
|
||||||
|
|
||||||
err = controller.UpdateReleaseDB(global.DB, &update)
|
err = controller.UpdateReleaseDB(global.DB, &update)
|
||||||
|
|
|
@ -35,11 +35,10 @@ var Args = func() map[string]string {
|
||||||
return args
|
return args
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
||||||
var HTTP_DOMAIN = func() string {
|
var HTTP_DOMAIN = func() string {
|
||||||
envvar := os.Getenv("HTTP_DOMAIN")
|
domain := Args["httpDomain"]
|
||||||
if envvar != "" {
|
if domain != "" {
|
||||||
return envvar
|
return domain
|
||||||
}
|
}
|
||||||
return "https://arimelody.me"
|
return "https://arimelody.me"
|
||||||
}()
|
}()
|
||||||
|
|
|
@ -80,12 +80,21 @@
|
||||||
</ul>
|
</ul>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if .Description}}
|
{{if .IsSingle}}
|
||||||
<p id="description">
|
|
||||||
{{.Description}}
|
{{$Track := index .Tracks 0}}
|
||||||
|
{{if $Track.Description}}
|
||||||
|
<p id="description">{{$Track.Description}}</p>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{else}}
|
||||||
|
|
||||||
|
{{if .Description}}<p id="description">{{.Description}}
|
||||||
</p>
|
</p>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
{{end}}
|
||||||
|
|
||||||
<button id="share">share</button>
|
<button id="share">share</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue