Compare commits
No commits in common. "065a34a744a90d7f9ad5749108a2a452f8934943" and "f324c249f6bd391b6f840506a6117f250402fba3" have entirely different histories.
065a34a744
...
f324c249f6
25 changed files with 100 additions and 280 deletions
|
|
@ -18,12 +18,12 @@ import (
|
||||||
func accountHandler(app *model.AppState) http.Handler {
|
func accountHandler(app *model.AppState) http.Handler {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
mux.Handle("/account/totp-setup", totpSetupHandler(app))
|
mux.Handle("/totp-setup", totpSetupHandler(app))
|
||||||
mux.Handle("/account/totp-confirm", totpConfirmHandler(app))
|
mux.Handle("/totp-confirm", totpConfirmHandler(app))
|
||||||
mux.Handle("/account/totp-delete/", http.StripPrefix("/totp-delete", totpDeleteHandler(app)))
|
mux.Handle("/totp-delete/", http.StripPrefix("/totp-delete", totpDeleteHandler(app)))
|
||||||
|
|
||||||
mux.Handle("/account/password", changePasswordHandler(app))
|
mux.Handle("/password", changePasswordHandler(app))
|
||||||
mux.Handle("/account/delete", deleteAccountHandler(app))
|
mux.Handle("/delete", deleteAccountHandler(app))
|
||||||
|
|
||||||
return mux
|
return mux
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,9 @@ import (
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func serveArtists(app *model.AppState) http.Handler {
|
func serveArtist(app *model.AppState) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
slices := strings.Split(strings.TrimPrefix(r.URL.Path, "/artists")[1:], "/")
|
slices := strings.Split(r.URL.Path[1:], "/")
|
||||||
id := slices[0]
|
id := slices[0]
|
||||||
artist, err := controller.GetArtist(app.DB, id)
|
artist, err := controller.GetArtist(app.DB, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -47,16 +47,15 @@ func Handler(app *model.AppState) http.Handler {
|
||||||
mux.Handle("/register", registerAccountHandler(app))
|
mux.Handle("/register", registerAccountHandler(app))
|
||||||
|
|
||||||
mux.Handle("/account", requireAccount(accountIndexHandler(app)))
|
mux.Handle("/account", requireAccount(accountIndexHandler(app)))
|
||||||
mux.Handle("/account/", requireAccount(accountHandler(app)))
|
mux.Handle("/account/", requireAccount(http.StripPrefix("/account", accountHandler(app))))
|
||||||
|
|
||||||
mux.Handle("/logs", requireAccount(logsHandler(app)))
|
mux.Handle("/logs", requireAccount(logsHandler(app)))
|
||||||
|
|
||||||
mux.Handle("/releases", requireAccount(serveReleases(app)))
|
mux.Handle("/release/", requireAccount(http.StripPrefix("/release", serveRelease(app))))
|
||||||
mux.Handle("/releases/", requireAccount(serveReleases(app)))
|
mux.Handle("/artist/", requireAccount(http.StripPrefix("/artist", serveArtist(app))))
|
||||||
mux.Handle("/artists/", requireAccount(serveArtists(app)))
|
mux.Handle("/track/", requireAccount(http.StripPrefix("/track", serveTrack(app))))
|
||||||
mux.Handle("/tracks/", requireAccount(serveTracks(app)))
|
|
||||||
|
|
||||||
mux.Handle("/static/", staticHandler())
|
mux.Handle("/static/", http.StripPrefix("/static", staticHandler()))
|
||||||
|
|
||||||
mux.Handle("/", requireAccount(AdminIndexHandler(app)))
|
mux.Handle("/", requireAccount(AdminIndexHandler(app)))
|
||||||
|
|
||||||
|
|
@ -471,8 +470,7 @@ var staticFS embed.FS
|
||||||
|
|
||||||
func staticHandler() http.Handler {
|
func staticHandler() http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
uri := strings.TrimPrefix(r.URL.Path, "/static")
|
file, err := staticFS.ReadFile(filepath.Join("static", filepath.Clean(r.URL.Path)))
|
||||||
file, err := staticFS.ReadFile(filepath.Join("static", filepath.Clean(uri)))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package admin
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"arimelody-web/admin/templates"
|
"arimelody-web/admin/templates"
|
||||||
|
|
@ -11,52 +10,11 @@ import (
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func serveReleases(app *model.AppState) http.Handler {
|
func serveRelease(app *model.AppState) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
slices := strings.Split(strings.TrimPrefix(r.URL.Path, "/releases")[1:], "/")
|
slices := strings.Split(r.URL.Path[1:], "/")
|
||||||
releaseID := slices[0]
|
releaseID := slices[0]
|
||||||
|
|
||||||
var action string = ""
|
|
||||||
if len(slices) > 1 {
|
|
||||||
action = slices[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(releaseID) > 0 {
|
|
||||||
serveRelease(app, releaseID, action).ServeHTTP(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
session := r.Context().Value("session").(*model.Session)
|
|
||||||
|
|
||||||
type ReleasesData struct {
|
|
||||||
adminPageData
|
|
||||||
Releases []*model.Release
|
|
||||||
}
|
|
||||||
|
|
||||||
releases, err := controller.GetAllReleases(app.DB, false, 0, true)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to pull releases: %s\n", err)
|
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = templates.ReleasesTemplate.Execute(w, ReleasesData{
|
|
||||||
adminPageData: adminPageData{
|
|
||||||
Path: r.URL.Path,
|
|
||||||
Session: session,
|
|
||||||
},
|
|
||||||
Releases: releases,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to render releases page: %s\n", err)
|
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func serveRelease(app *model.AppState, releaseID string, action string) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
session := r.Context().Value("session").(*model.Session)
|
session := r.Context().Value("session").(*model.Session)
|
||||||
|
|
||||||
release, err := controller.GetRelease(app.DB, releaseID, true)
|
release, err := controller.GetRelease(app.DB, releaseID, true)
|
||||||
|
|
@ -70,8 +28,8 @@ func serveRelease(app *model.AppState, releaseID string, action string) http.Han
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(action) > 0 {
|
if len(slices) > 1 {
|
||||||
switch action {
|
switch slices[1] {
|
||||||
case "editcredits":
|
case "editcredits":
|
||||||
serveEditCredits(release).ServeHTTP(w, r)
|
serveEditCredits(release).ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -66,13 +66,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
img.icon {
|
|
||||||
-webkit-filter: invert(1);
|
|
||||||
filter: invert(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
width: calc(100% - 180px);
|
width: calc(100% - 180px);
|
||||||
height: calc(100vh - 1em);
|
height: calc(100vh - 1em);
|
||||||
|
|
@ -191,9 +184,8 @@ a:hover {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
img.icon {
|
a img.icon {
|
||||||
height: .8em;
|
height: .8em;
|
||||||
transition: filter .1s ease-out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ removeAvatarBtn.addEventListener("click", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('readystatechange', () => {
|
document.addEventListener('readystatechange', () => {
|
||||||
document.querySelectorAll('#releases .credit').forEach(el => {
|
document.querySelectorAll('.card#releases .credit').forEach(el => {
|
||||||
hijackClickEvent(el, el.querySelector('.credit-name a'));
|
hijackClickEvent(el, el.querySelector('.credit-name a'));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,6 @@ input[type="text"] {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: var(--bg-2);
|
background: var(--bg-2);
|
||||||
box-shadow: var(--shadow-md);
|
box-shadow: var(--shadow-md);
|
||||||
|
|
||||||
transition: background .1s ease-out, color .1s ease-out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.release-artwork {
|
.release-artwork {
|
||||||
|
|
@ -33,7 +31,6 @@ input[type="text"] {
|
||||||
.release-artwork #remove-artwork {
|
.release-artwork #remove-artwork {
|
||||||
margin-top: .5em;
|
margin-top: .5em;
|
||||||
padding: .3em .6em;
|
padding: .3em .6em;
|
||||||
background: var(--bg-3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.release-info {
|
.release-info {
|
||||||
|
|
@ -121,7 +118,6 @@ input[type="text"] {
|
||||||
gap: .5em;
|
gap: .5em;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: right;
|
justify-content: right;
|
||||||
color: var(--fg-3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.release-actions button,
|
.release-actions button,
|
||||||
|
|
@ -167,7 +163,7 @@ dialog div.dialog-actions {
|
||||||
* RELEASE CREDITS
|
* RELEASE CREDITS
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#credits .credit {
|
.card#credits .credit {
|
||||||
margin-bottom: .5em;
|
margin-bottom: .5em;
|
||||||
padding: .5em;
|
padding: .5em;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -182,24 +178,24 @@ dialog div.dialog-actions {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background .1s ease-out;
|
transition: background .1s ease-out;
|
||||||
}
|
}
|
||||||
#credits .credit:hover {
|
.card#credits .credit:hover {
|
||||||
background-color: var(--bg-1);
|
background-color: var(--bg-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#credits .credit p {
|
.card#credits .credit p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#credits .credit .artist-avatar {
|
.card#credits .credit .artist-avatar {
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#credits .credit .artist-name {
|
.card#credits .credit .artist-name {
|
||||||
color: var(--fg-3);
|
color: var(--fg-3);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
#credits .credit .artist-role small {
|
.card#credits .credit .artist-role small {
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
opacity: .66;
|
opacity: .66;
|
||||||
}
|
}
|
||||||
|
|
@ -318,38 +314,33 @@ dialog div.dialog-actions {
|
||||||
* RELEASE LINKS
|
* RELEASE LINKS
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#links ul {
|
.card#links ul {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: .2em;
|
gap: .2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#links a img.icon {
|
.card#links a.button:hover {
|
||||||
-webkit-filter: none;
|
|
||||||
filter: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#links a.button:hover {
|
|
||||||
color: var(--bg-3) !important;
|
color: var(--bg-3) !important;
|
||||||
background-color: var(--fg-3) !important;
|
background-color: var(--fg-3) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#links a.button[data-name="spotify"] {
|
.card#links a.button[data-name="spotify"] {
|
||||||
color: #101010;
|
color: #101010;
|
||||||
background-color: #8cff83
|
background-color: #8cff83
|
||||||
}
|
}
|
||||||
|
|
||||||
#links a.button[data-name="apple music"] {
|
.card#links a.button[data-name="apple music"] {
|
||||||
color: #101010;
|
color: #101010;
|
||||||
background-color: #8cd9ff
|
background-color: #8cd9ff
|
||||||
}
|
}
|
||||||
|
|
||||||
#links a.button[data-name="soundcloud"] {
|
.card#links a.button[data-name="soundcloud"] {
|
||||||
color: #101010;
|
color: #101010;
|
||||||
background-color: #fdaa6d
|
background-color: #fdaa6d
|
||||||
}
|
}
|
||||||
|
|
||||||
#links a.button[data-name="youtube"] {
|
.card#links a.button[data-name="youtube"] {
|
||||||
color: #101010;
|
color: #101010;
|
||||||
background-color: #ff6e6e
|
background-color: #ff6e6e
|
||||||
}
|
}
|
||||||
|
|
@ -437,7 +428,7 @@ dialog div.dialog-actions {
|
||||||
* RELEASE TRACKS
|
* RELEASE TRACKS
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#tracks .track {
|
.card#tracks .track {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -447,51 +438,49 @@ dialog div.dialog-actions {
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
background: var(--bg-2);
|
background: var(--bg-2);
|
||||||
box-shadow: var(--shadow-md);
|
box-shadow: var(--shadow-md);
|
||||||
|
|
||||||
transition: background .1s ease-out, color .1s ease-out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#tracks .track h3,
|
.card#tracks .track h3,
|
||||||
#tracks .track p {
|
.card#tracks .track p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tracks h2.track-title {
|
.card#tracks h2.track-title {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: .5em;
|
gap: .5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tracks h2.track-title .track-number {
|
.card#tracks h2.track-title .track-number {
|
||||||
opacity: .5;
|
opacity: .5;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tracks a:hover {
|
.card#tracks a:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tracks .track-album {
|
.card#tracks .track-album {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-size: .75em;
|
font-size: .75em;
|
||||||
opacity: .5;
|
opacity: .5;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tracks .track-album.empty {
|
.card#tracks .track-album.empty {
|
||||||
color: #ff2020;
|
color: #ff2020;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tracks .track-description {
|
.card#tracks .track-description {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tracks .track-lyrics {
|
.card#tracks .track-lyrics {
|
||||||
max-height: 10em;
|
max-height: 10em;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tracks .track .empty {
|
.card#tracks .track .empty {
|
||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ removeArtworkBtn.addEventListener("click", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener("readystatechange", () => {
|
document.addEventListener("readystatechange", () => {
|
||||||
document.querySelectorAll("#credits .credit").forEach(el => {
|
document.querySelectorAll(".card#credits .credit").forEach(el => {
|
||||||
hijackClickEvent(el, el.querySelector(".artist-name a"));
|
hijackClickEvent(el, el.querySelector(".artist-name a"));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ newReleaseBtn.addEventListener("click", event => {
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({id})
|
body: JSON.stringify({id})
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
if (res.ok) location = "/admin/releases/" + id;
|
if (res.ok) location = "/admin/release/" + id;
|
||||||
else {
|
else {
|
||||||
res.text().then(err => {
|
res.text().then(err => {
|
||||||
alert("Request failed: " + err);
|
alert("Request failed: " + err);
|
||||||
|
|
@ -39,7 +39,7 @@ newArtistBtn.addEventListener("click", event => {
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
res.text().then(text => {
|
res.text().then(text => {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
location = "/admin/artists/" + id;
|
location = "/admin/artist/" + id;
|
||||||
} else {
|
} else {
|
||||||
alert("Request failed: " + text);
|
alert("Request failed: " + text);
|
||||||
console.error(text);
|
console.error(text);
|
||||||
|
|
@ -63,7 +63,7 @@ newTrackBtn.addEventListener("click", event => {
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
res.text().then(text => {
|
res.text().then(text => {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
location = "/admin/tracks/" + text;
|
location = "/admin/track/" + text;
|
||||||
} else {
|
} else {
|
||||||
alert("Request failed: " + text);
|
alert("Request failed: " + text);
|
||||||
console.error(text);
|
console.error(text);
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,6 @@
|
||||||
.release-actions {
|
.release-actions {
|
||||||
margin-top: .5em;
|
margin-top: .5em;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
color: var(--fg-3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.release-actions a {
|
.release-actions a {
|
||||||
|
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
{{define "head"}}
|
|
||||||
<title>Artists - ari melody 💫</title>
|
|
||||||
<link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon">
|
|
||||||
<link rel="stylesheet" href="/admin/static/index.css">
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{define "content"}}
|
|
||||||
<main>
|
|
||||||
<h1>Artists</h1>
|
|
||||||
|
|
||||||
<div class="card-header">
|
|
||||||
<h2><a href="/admin/artists/">Artists</a></h2>
|
|
||||||
<a class="button new" id="create-artist">Create New</a>
|
|
||||||
</div>
|
|
||||||
{{if .Artists}}
|
|
||||||
<div class="artists-group">
|
|
||||||
{{range $Artist := .Artists}}
|
|
||||||
<div class="artist">
|
|
||||||
<img src="{{$Artist.GetAvatar}}" alt="" width="64" loading="lazy" class="artist-avatar">
|
|
||||||
<a href="/admin/artists/{{$Artist.ID}}" class="artist-name">{{$Artist.Name}}</a>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
{{else}}
|
|
||||||
<p>There are no artists.</p>
|
|
||||||
{{end}}
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<script type="module" src="/admin/static/admin.js"></script>
|
|
||||||
{{end}}
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
{{range $Artist := .Artists}}
|
{{range $Artist := .Artists}}
|
||||||
<li class="new-artist"
|
<li class="new-artist"
|
||||||
data-id="{{$Artist.ID}}"
|
data-id="{{$Artist.ID}}"
|
||||||
hx-get="/admin/releases/{{$.ReleaseID}}/newcredit/{{$Artist.ID}}"
|
hx-get="/admin/release/{{$.ReleaseID}}/newcredit/{{$Artist.ID}}"
|
||||||
hx-target="#editcredits ul"
|
hx-target="#editcredits ul"
|
||||||
hx-swap="beforeend"
|
hx-swap="beforeend"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
<h2>Editing: Credits</h2>
|
<h2>Editing: Credits</h2>
|
||||||
<a id="add-credit"
|
<a id="add-credit"
|
||||||
class="button new"
|
class="button new"
|
||||||
href="/admin/releases/{{.ID}}/addcredit"
|
href="/admin/release/{{.ID}}/addcredit"
|
||||||
hx-get="/admin/releases/{{.ID}}/addcredit"
|
hx-get="/admin/release/{{.ID}}/addcredit"
|
||||||
hx-target="body"
|
hx-target="body"
|
||||||
hx-swap="beforeend"
|
hx-swap="beforeend"
|
||||||
>Add</a>
|
>Add</a>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="release-info">
|
<div class="release-info">
|
||||||
<h3 class="release-title">
|
<h3 class="release-title">
|
||||||
<a href="/admin/releases/{{.ID}}">{{.Title}}</a>
|
<a href="/admin/release/{{.ID}}">{{.Title}}</a>
|
||||||
<small>
|
<small>
|
||||||
<span title="{{.PrintReleaseDate}}">{{.ReleaseDate.Year}}</span>
|
<span title="{{.PrintReleaseDate}}">{{.ReleaseDate.Year}}</span>
|
||||||
{{if not .Visible}}(hidden){{end}}
|
{{if not .Visible}}(hidden){{end}}
|
||||||
|
|
@ -13,9 +13,9 @@
|
||||||
</h3>
|
</h3>
|
||||||
<p class="release-artists">{{.PrintArtists true true}}</p>
|
<p class="release-artists">{{.PrintArtists true true}}</p>
|
||||||
<p class="release-type-single">{{.ReleaseType}}
|
<p class="release-type-single">{{.ReleaseType}}
|
||||||
(<a href="/admin/releases/{{.ID}}#tracks">{{len .Tracks}} track{{if not (eq (len .Tracks) 1)}}s{{end}}</a>)</p>
|
(<a href="/admin/release/{{.ID}}#tracks">{{len .Tracks}} track{{if not (eq (len .Tracks) 1)}}s{{end}}</a>)</p>
|
||||||
<div class="release-actions">
|
<div class="release-actions">
|
||||||
<a href="/admin/releases/{{.ID}}">Edit</a>
|
<a href="/admin/release/{{.ID}}">Edit</a>
|
||||||
<a href="/music/{{.ID}}" target="_blank">Gateway <img class="icon" src="/img/external-link.svg"/></a>
|
<a href="/music/{{.ID}}" target="_blank">Gateway <img class="icon" src="/img/external-link.svg"/></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
</li>
|
</li>
|
||||||
<li class="new-track"
|
<li class="new-track"
|
||||||
data-id="{{$Track.ID}}"
|
data-id="{{$Track.ID}}"
|
||||||
hx-get="/admin/releases/{{$.ReleaseID}}/newtrack/{{$Track.ID}}"
|
hx-get="/admin/release/{{$.ReleaseID}}/newtrack/{{$Track.ID}}"
|
||||||
hx-target="#edittracks ul"
|
hx-target="#edittracks ul"
|
||||||
hx-swap="beforeend"
|
hx-swap="beforeend"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
<h2>Editing: Tracks</h2>
|
<h2>Editing: Tracks</h2>
|
||||||
<a id="add-track"
|
<a id="add-track"
|
||||||
class="button new"
|
class="button new"
|
||||||
href="/admin/releases/{{.Release.ID}}/addtrack"
|
href="/admin/release/{{.Release.ID}}/addtrack"
|
||||||
hx-get="/admin/releases/{{.Release.ID}}/addtrack"
|
hx-get="/admin/release/{{.Release.ID}}/addtrack"
|
||||||
hx-target="body"
|
hx-target="body"
|
||||||
hx-swap="beforeend"
|
hx-swap="beforeend"
|
||||||
>Add</a>
|
>Add</a>
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@
|
||||||
<div class="credit">
|
<div class="credit">
|
||||||
<img src="{{.Release.Artwork}}" alt="" width="64" loading="lazy" class="release-artwork">
|
<img src="{{.Release.Artwork}}" alt="" width="64" loading="lazy" class="release-artwork">
|
||||||
<div class="credit-info">
|
<div class="credit-info">
|
||||||
<h3 class="credit-name"><a href="/admin/releases/{{.Release.ID}}">{{.Release.Title}}</a></h3>
|
<h3 class="credit-name"><a href="/admin/release/{{.Release.ID}}">{{.Release.Title}}</a></h3>
|
||||||
<p class="credit-artists">{{.Release.PrintArtists true true}}</p>
|
<p class="credit-artists">{{.Release.PrintArtists true true}}</p>
|
||||||
<p class="artist-role">
|
<p class="artist-role">
|
||||||
Role: {{.Role}}
|
Role: {{.Role}}
|
||||||
|
|
|
||||||
|
|
@ -99,10 +99,10 @@
|
||||||
|
|
||||||
<div class="card" id="credits">
|
<div class="card" id="credits">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h2>Credits <small>({{len .Release.Credits}} total)</small></h2>
|
<h2>Credits ({{len .Release.Credits}})</h2>
|
||||||
<a class="button edit"
|
<a class="button edit"
|
||||||
href="/admin/releases/{{.Release.ID}}/editcredits"
|
href="/admin/release/{{.Release.ID}}/editcredits"
|
||||||
hx-get="/admin/releases/{{.Release.ID}}/editcredits"
|
hx-get="/admin/release/{{.Release.ID}}/editcredits"
|
||||||
hx-target="body"
|
hx-target="body"
|
||||||
hx-swap="beforeend"
|
hx-swap="beforeend"
|
||||||
>Edit</a>
|
>Edit</a>
|
||||||
|
|
@ -111,7 +111,7 @@
|
||||||
<div class="credit">
|
<div class="credit">
|
||||||
<img src="{{.Artist.GetAvatar}}" alt="" width="64" loading="lazy" class="artist-avatar">
|
<img src="{{.Artist.GetAvatar}}" alt="" width="64" loading="lazy" class="artist-avatar">
|
||||||
<div class="credit-info">
|
<div class="credit-info">
|
||||||
<p class="artist-name"><a href="/admin/artists/{{.Artist.ID}}">{{.Artist.Name}}</a></p>
|
<p class="artist-name"><a href="/admin/artist/{{.Artist.ID}}">{{.Artist.Name}}</a></p>
|
||||||
<p class="artist-role">
|
<p class="artist-role">
|
||||||
{{.Role}}
|
{{.Role}}
|
||||||
{{if .Primary}}
|
{{if .Primary}}
|
||||||
|
|
@ -130,8 +130,8 @@
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h2>Links ({{len .Release.Links}})</h2>
|
<h2>Links ({{len .Release.Links}})</h2>
|
||||||
<a class="button edit"
|
<a class="button edit"
|
||||||
href="/admin/releases/{{.Release.ID}}/editlinks"
|
href="/admin/release/{{.Release.ID}}/editlinks"
|
||||||
hx-get="/admin/releases/{{.Release.ID}}/editlinks"
|
hx-get="/admin/release/{{.Release.ID}}/editlinks"
|
||||||
hx-target="body"
|
hx-target="body"
|
||||||
hx-swap="beforeend"
|
hx-swap="beforeend"
|
||||||
>Edit</a>
|
>Edit</a>
|
||||||
|
|
@ -147,8 +147,8 @@
|
||||||
<div class="card-header" id="tracks">
|
<div class="card-header" id="tracks">
|
||||||
<h2>Tracklist ({{len .Release.Tracks}})</h2>
|
<h2>Tracklist ({{len .Release.Tracks}})</h2>
|
||||||
<a class="button edit"
|
<a class="button edit"
|
||||||
href="/admin/releases/{{.Release.ID}}/edittracks"
|
href="/admin/release/{{.Release.ID}}/edittracks"
|
||||||
hx-get="/admin/releases/{{.Release.ID}}/edittracks"
|
hx-get="/admin/release/{{.Release.ID}}/edittracks"
|
||||||
hx-target="body"
|
hx-target="body"
|
||||||
hx-swap="beforeend"
|
hx-swap="beforeend"
|
||||||
>Edit</a>
|
>Edit</a>
|
||||||
|
|
@ -157,7 +157,7 @@
|
||||||
<div class="track" data-id="{{$track.ID}}">
|
<div class="track" data-id="{{$track.ID}}">
|
||||||
<h2 class="track-title">
|
<h2 class="track-title">
|
||||||
<span class="track-number">{{.Add $i 1}}</span>
|
<span class="track-number">{{.Add $i 1}}</span>
|
||||||
<a href="/admin/tracks/{{$track.ID}}">{{$track.Title}}</a>
|
<a href="/admin/track/{{$track.ID}}">{{$track.Title}}</a>
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<h3>Description</h3>
|
<h3>Description</h3>
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@
|
||||||
{{range $Artist := .Artists}}
|
{{range $Artist := .Artists}}
|
||||||
<div class="artist">
|
<div class="artist">
|
||||||
<img src="{{$Artist.GetAvatar}}" alt="" width="64" loading="lazy" class="artist-avatar">
|
<img src="{{$Artist.GetAvatar}}" alt="" width="64" loading="lazy" class="artist-avatar">
|
||||||
<a href="/admin/artists/{{$Artist.ID}}" class="artist-name">{{$Artist.Name}}</a>
|
<a href="/admin/artist/{{$Artist.ID}}" class="artist-name">{{$Artist.Name}}</a>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -51,7 +51,7 @@
|
||||||
{{range $Track := .Tracks}}
|
{{range $Track := .Tracks}}
|
||||||
<div class="track">
|
<div class="track">
|
||||||
<h2 class="track-title">
|
<h2 class="track-title">
|
||||||
<a href="/admin/tracks/{{$Track.ID}}">{{$Track.Title}}</a>
|
<a href="/admin/track/{{$Track.ID}}">{{$Track.Title}}</a>
|
||||||
</h2>
|
</h2>
|
||||||
{{if $Track.Description}}
|
{{if $Track.Description}}
|
||||||
<p class="track-description">{{$Track.GetDescriptionHTML}}</p>
|
<p class="track-description">{{$Track.GetDescriptionHTML}}</p>
|
||||||
|
|
|
||||||
|
|
@ -28,14 +28,14 @@
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<p class="section-label">music</p>
|
<p class="section-label">music</p>
|
||||||
<div class="nav-item{{if hasPrefix .Path "/releases"}} active{{end}}">
|
<div class="nav-item{{if eq .Path "/releases"}} active{{end}}">
|
||||||
<a href="/admin/releases/">releases</a>
|
<a href="/admin/releases">releases</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item{{if hasPrefix .Path "/artists"}} active{{end}}">
|
<div class="nav-item{{if eq .Path "/artists"}} active{{end}}">
|
||||||
<a href="/admin/artists/">artists</a>
|
<a href="/admin/artists">artists</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item{{if hasPrefix .Path "/tracks"}} active{{end}}">
|
<div class="nav-item{{if eq .Path "/tracks"}} active{{end}}">
|
||||||
<a href="/admin/tracks/">tracks</a>
|
<a href="/admin/tracks">tracks</a>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
{{define "head"}}
|
|
||||||
<title>Releases - ari melody 💫</title>
|
|
||||||
<link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon">
|
|
||||||
<link rel="stylesheet" href="/admin/static/index.css">
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{define "content"}}
|
|
||||||
<main>
|
|
||||||
<div class="card-header">
|
|
||||||
<h1><a href="/admin/releases/">Releases</a></h1>
|
|
||||||
<a class="button new" id="create-release">Create New</a>
|
|
||||||
</div>
|
|
||||||
{{range .Releases}}
|
|
||||||
{{block "release" .}}{{end}}
|
|
||||||
{{end}}
|
|
||||||
{{if not .Releases}}
|
|
||||||
<p>There are no releases.</p>
|
|
||||||
{{end}}
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<script type="module" src="/admin/static/admin.js"></script>
|
|
||||||
{{end}}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
{{define "head"}}
|
|
||||||
<title>Releases - ari melody 💫</title>
|
|
||||||
<link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon">
|
|
||||||
<link rel="stylesheet" href="/admin/static/index.css">
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{define "content"}}
|
|
||||||
<main>
|
|
||||||
<h1>Releases</h1>
|
|
||||||
|
|
||||||
<div class="card-header">
|
|
||||||
<h2><a href="/admin/tracks/">Tracks</a></h2>
|
|
||||||
<a class="button new" id="create-track">Create New</a>
|
|
||||||
</div>
|
|
||||||
<p><em>"Orphaned" tracks that have not yet been bound to a release.</em></p>
|
|
||||||
<br>
|
|
||||||
{{range $Track := .Tracks}}
|
|
||||||
<div class="track">
|
|
||||||
<h2 class="track-title">
|
|
||||||
<a href="/admin/tracks/{{$Track.ID}}">{{$Track.Title}}</a>
|
|
||||||
</h2>
|
|
||||||
{{if $Track.Description}}
|
|
||||||
<p class="track-description">{{$Track.GetDescriptionHTML}}</p>
|
|
||||||
{{else}}
|
|
||||||
<p class="track-description empty">No description provided.</p>
|
|
||||||
{{end}}
|
|
||||||
{{if $Track.Lyrics}}
|
|
||||||
<p class="track-lyrics">{{$Track.GetLyricsHTML}}</p>
|
|
||||||
{{else}}
|
|
||||||
<p class="track-lyrics empty">There are no lyrics.</p>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
{{if not .Artists}}
|
|
||||||
<p>There are no artists.</p>
|
|
||||||
{{end}}
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<script type="module" src="/admin/static/admin.js"></script>
|
|
||||||
{{end}}
|
|
||||||
|
|
@ -2,11 +2,11 @@ package templates
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"arimelody-web/log"
|
"arimelody-web/log"
|
||||||
_ "embed"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
_ "embed"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed "html/layout.html"
|
//go:embed "html/layout.html"
|
||||||
|
|
@ -35,18 +35,10 @@ var logsHTML string
|
||||||
|
|
||||||
//go:embed "html/edit-account.html"
|
//go:embed "html/edit-account.html"
|
||||||
var editAccountHTML string
|
var editAccountHTML string
|
||||||
|
|
||||||
//go:embed "html/releases.html"
|
|
||||||
var releasesHTML string
|
|
||||||
//go:embed "html/artists.html"
|
|
||||||
var artistsHTML string
|
|
||||||
//go:embed "html/tracks.html"
|
|
||||||
var tracksHTML string
|
|
||||||
|
|
||||||
//go:embed "html/edit-release.html"
|
|
||||||
var editReleaseHTML string
|
|
||||||
//go:embed "html/edit-artist.html"
|
//go:embed "html/edit-artist.html"
|
||||||
var editArtistHTML string
|
var editArtistHTML string
|
||||||
|
//go:embed "html/edit-release.html"
|
||||||
|
var editReleaseHTML string
|
||||||
//go:embed "html/edit-track.html"
|
//go:embed "html/edit-track.html"
|
||||||
var editTrackHTML string
|
var editTrackHTML string
|
||||||
|
|
||||||
|
|
@ -70,18 +62,9 @@ var componentAddTrackHTML string
|
||||||
//go:embed "html/components/tracks/edittracks.html"
|
//go:embed "html/components/tracks/edittracks.html"
|
||||||
var componentEditTracksHTML string
|
var componentEditTracksHTML string
|
||||||
|
|
||||||
var BaseTemplate = template.Must(
|
var BaseTemplate = template.Must(template.New("base").Parse(
|
||||||
template.New("base").Funcs(
|
strings.Join([]string{ layoutHTML, prideflagHTML }, "\n"),
|
||||||
template.FuncMap{
|
))
|
||||||
"hasPrefix": func(s string, prefix string) bool {
|
|
||||||
fmt.Printf("does \"%s\" start with \"%s\"?\n", s, prefix)
|
|
||||||
return strings.HasPrefix(s, prefix)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
).Parse(strings.Join([]string{
|
|
||||||
layoutHTML,
|
|
||||||
prideflagHTML,
|
|
||||||
}, "\n")))
|
|
||||||
|
|
||||||
var IndexTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(
|
var IndexTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(
|
||||||
strings.Join([]string{
|
strings.Join([]string{
|
||||||
|
|
@ -125,15 +108,6 @@ var LogsTemplate = template.Must(template.Must(BaseTemplate.Clone()).Funcs(templ
|
||||||
},
|
},
|
||||||
}).Parse(logsHTML))
|
}).Parse(logsHTML))
|
||||||
|
|
||||||
var ReleasesTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(
|
|
||||||
strings.Join([]string{
|
|
||||||
releasesHTML,
|
|
||||||
componentReleaseListItemHTML,
|
|
||||||
}, "\n"),
|
|
||||||
))
|
|
||||||
var ArtistsTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(artistsHTML))
|
|
||||||
var TracksTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(tracksHTML))
|
|
||||||
|
|
||||||
var EditReleaseTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(editReleaseHTML))
|
var EditReleaseTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(editReleaseHTML))
|
||||||
var EditArtistTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(editArtistHTML))
|
var EditArtistTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(editArtistHTML))
|
||||||
var EditTrackTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(
|
var EditTrackTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,9 @@ import (
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func serveTracks(app *model.AppState) http.Handler {
|
func serveTrack(app *model.AppState) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
slices := strings.Split(strings.TrimPrefix(r.URL.Path, "/tracks")[1:], "/")
|
slices := strings.Split(r.URL.Path[1:], "/")
|
||||||
id := slices[0]
|
id := slices[0]
|
||||||
track, err := controller.GetTrack(app.DB, id)
|
track, err := controller.GetTrack(app.DB, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,10 @@ func IndexHandler(app *model.AppState) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.URL.Path == "/" || r.URL.Path == "/index.html" {
|
|
||||||
type IndexData struct {
|
type IndexData struct {
|
||||||
TwitchStatus *model.TwitchStreamInfo
|
TwitchStatus *model.TwitchStreamInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
var twitchStatus *model.TwitchStreamInfo = nil
|
var twitchStatus *model.TwitchStreamInfo = nil
|
||||||
if app.Twitch != nil && len(app.Config.Twitch.Broadcaster) > 0 {
|
if app.Twitch != nil && len(app.Config.Twitch.Broadcaster) > 0 {
|
||||||
|
|
@ -28,7 +28,9 @@ func IndexHandler(app *model.AppState) http.Handler {
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to get Twitch status for %s: %v\n", app.Config.Twitch.Broadcaster, err)
|
fmt.Fprintf(os.Stderr, "WARN: Failed to get Twitch status for %s: %v\n", app.Config.Twitch.Broadcaster, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err = templates.IndexTemplate.Execute(w, IndexData{
|
|
||||||
|
if r.URL.Path == "/" || r.URL.Path == "/index.html" {
|
||||||
|
err := templates.IndexTemplate.Execute(w, IndexData{
|
||||||
TwitchStatus: twitchStatus,
|
TwitchStatus: twitchStatus,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue