add artists/tracks pages; more components; css cleanup
This commit is contained in:
parent
065a34a744
commit
b0dd87cad3
37 changed files with 498 additions and 354 deletions
|
|
@ -12,22 +12,57 @@ import (
|
|||
|
||||
func serveArtists(app *model.AppState) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
session := r.Context().Value("session").(*model.Session)
|
||||
|
||||
slices := strings.Split(strings.TrimPrefix(r.URL.Path, "/artists")[1:], "/")
|
||||
id := slices[0]
|
||||
artist, err := controller.GetArtist(app.DB, id)
|
||||
artistID := slices[0]
|
||||
|
||||
if len(artistID) > 0 {
|
||||
serveArtist(app, artistID).ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
artists, err := controller.GetAllArtists(app.DB)
|
||||
if err != nil {
|
||||
fmt.Printf("WARN: Failed to fetch artists: %s\n", err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
type ArtistsResponse struct {
|
||||
adminPageData
|
||||
Artists []*model.Artist
|
||||
}
|
||||
|
||||
err = templates.ArtistsTemplate.Execute(w, ArtistsResponse{
|
||||
adminPageData: adminPageData{ Path: r.URL.Path, Session: session },
|
||||
Artists: artists,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("WARN: Failed to serve admin artists page: %s\n", err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func serveArtist(app *model.AppState, artistID string) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
session := r.Context().Value("session").(*model.Session)
|
||||
|
||||
artist, err := controller.GetArtist(app.DB, artistID)
|
||||
if err != nil {
|
||||
if artist == nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
fmt.Printf("Error rendering admin artist page for %s: %s\n", id, err)
|
||||
fmt.Printf("WARN: Failed to fetch artist %s: %s\n", artistID, err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
credits, err := controller.GetArtistCredits(app.DB, artist.ID, true)
|
||||
if err != nil {
|
||||
fmt.Printf("Error rendering admin track page for %s: %s\n", id, err)
|
||||
fmt.Printf("WARN: Failed to serve admin artist page for %s: %s\n", artistID, err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
|
@ -38,17 +73,14 @@ func serveArtists(app *model.AppState) http.Handler {
|
|||
Credits []*model.Credit
|
||||
}
|
||||
|
||||
session := r.Context().Value("session").(*model.Session)
|
||||
|
||||
err = templates.EditArtistTemplate.Execute(w, ArtistResponse{
|
||||
adminPageData: adminPageData{ Path: r.URL.Path, Session: session },
|
||||
Artist: artist,
|
||||
Credits: credits,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("Error rendering admin track page for %s: %s\n", id, err)
|
||||
fmt.Printf("WARN: Failed to serve admin artist page for %s: %s\n", artistID, err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,9 @@ func Handler(app *model.AppState) http.Handler {
|
|||
|
||||
mux.Handle("/releases", requireAccount(serveReleases(app)))
|
||||
mux.Handle("/releases/", requireAccount(serveReleases(app)))
|
||||
mux.Handle("/artists", requireAccount(serveArtists(app)))
|
||||
mux.Handle("/artists/", requireAccount(serveArtists(app)))
|
||||
mux.Handle("/tracks", requireAccount(serveTracks(app)))
|
||||
mux.Handle("/tracks/", requireAccount(serveTracks(app)))
|
||||
|
||||
mux.Handle("/static/", staticHandler())
|
||||
|
|
@ -79,7 +81,7 @@ func AdminIndexHandler(app *model.AppState) http.Handler {
|
|||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
releaseCount, err := controller.GetReleasesCount(app.DB, false)
|
||||
releaseCount, err := controller.GetReleaseCount(app.DB, false)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "WARN: Failed to pull releases count: %s\n", err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
|
|
@ -92,6 +94,12 @@ func AdminIndexHandler(app *model.AppState) http.Handler {
|
|||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
artistCount, err := controller.GetArtistCount(app.DB)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "WARN: Failed to pull artist count: %s\n", err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
tracks, err := controller.GetOrphanTracks(app.DB)
|
||||
if err != nil {
|
||||
|
|
@ -99,13 +107,21 @@ func AdminIndexHandler(app *model.AppState) http.Handler {
|
|||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
trackCount, err := controller.GetTrackCount(app.DB)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "WARN: Failed to pull track count: %s\n", err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
type IndexData struct {
|
||||
adminPageData
|
||||
Releases []*model.Release
|
||||
ReleaseCount int
|
||||
Artists []*model.Artist
|
||||
ArtistCount int
|
||||
Tracks []*model.Track
|
||||
TrackCount int
|
||||
}
|
||||
|
||||
err = templates.IndexTemplate.Execute(w, IndexData{
|
||||
|
|
@ -113,7 +129,9 @@ func AdminIndexHandler(app *model.AppState) http.Handler {
|
|||
Releases: releases,
|
||||
ReleaseCount: releaseCount,
|
||||
Artists: artists,
|
||||
ArtistCount: artistCount,
|
||||
Tracks: tracks,
|
||||
TrackCount: trackCount,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "WARN: Failed to render admin index: %s\n", err)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ import (
|
|||
|
||||
func serveReleases(app *model.AppState) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
session := r.Context().Value("session").(*model.Session)
|
||||
|
||||
slices := strings.Split(strings.TrimPrefix(r.URL.Path, "/releases")[1:], "/")
|
||||
releaseID := slices[0]
|
||||
|
||||
|
|
@ -26,8 +28,6 @@ func serveReleases(app *model.AppState) http.Handler {
|
|||
return
|
||||
}
|
||||
|
||||
session := r.Context().Value("session").(*model.Session)
|
||||
|
||||
type ReleasesData struct {
|
||||
adminPageData
|
||||
Releases []*model.Release
|
||||
|
|
@ -35,7 +35,7 @@ func serveReleases(app *model.AppState) http.Handler {
|
|||
|
||||
releases, err := controller.GetAllReleases(app.DB, false, 0, true)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "WARN: Failed to pull releases: %s\n", err)
|
||||
fmt.Fprintf(os.Stderr, "WARN: Failed to fetch releases: %s\n", err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
|
@ -48,7 +48,7 @@ func serveReleases(app *model.AppState) http.Handler {
|
|||
Releases: releases,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "WARN: Failed to render releases page: %s\n", err)
|
||||
fmt.Fprintf(os.Stderr, "WARN: Failed to serve releases page: %s\n", err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
|
@ -65,7 +65,7 @@ func serveRelease(app *model.AppState, releaseID string, action string) http.Han
|
|||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
fmt.Printf("WARN: Failed to pull full release data for %s: %s\n", releaseID, err)
|
||||
fmt.Printf("WARN: Failed to fetch full release data for %s: %s\n", releaseID, err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
|
@ -103,12 +103,16 @@ func serveRelease(app *model.AppState, releaseID string, action string) http.Han
|
|||
Release *model.Release
|
||||
}
|
||||
|
||||
for i, track := range release.Tracks {
|
||||
track.Number = i + 1
|
||||
}
|
||||
|
||||
err = templates.EditReleaseTemplate.Execute(w, ReleaseResponse{
|
||||
adminPageData: adminPageData{ Path: r.URL.Path, Session: session },
|
||||
Release: release,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("Error rendering admin release page for %s: %s\n", release.ID, err)
|
||||
fmt.Printf("WARN: Failed to serve admin release page for %s: %s\n", release.ID, err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
}
|
||||
})
|
||||
|
|
@ -119,7 +123,7 @@ func serveEditCredits(release *model.Release) http.Handler {
|
|||
w.Header().Set("Content-Type", "text/html")
|
||||
err := templates.EditCreditsTemplate.Execute(w, release)
|
||||
if err != nil {
|
||||
fmt.Printf("Error rendering edit credits component for %s: %s\n", release.ID, err)
|
||||
fmt.Printf("WARN: Failed to serve edit credits component for %s: %s\n", release.ID, err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
}
|
||||
})
|
||||
|
|
@ -129,7 +133,7 @@ func serveAddCredit(app *model.AppState, release *model.Release) http.Handler {
|
|||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
artists, err := controller.GetArtistsNotOnRelease(app.DB, release.ID)
|
||||
if err != nil {
|
||||
fmt.Printf("WARN: Failed to pull artists not on %s: %s\n", release.ID, err)
|
||||
fmt.Printf("WARN: Failed to fetch artists not on %s: %s\n", release.ID, err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
|
@ -145,7 +149,7 @@ func serveAddCredit(app *model.AppState, release *model.Release) http.Handler {
|
|||
Artists: artists,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("Error rendering add credits component for %s: %s\n", release.ID, err)
|
||||
fmt.Printf("WARN: Failed to serve add credits component for %s: %s\n", release.ID, err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
}
|
||||
})
|
||||
|
|
@ -156,7 +160,7 @@ func serveNewCredit(app *model.AppState) http.Handler {
|
|||
artistID := strings.Split(r.URL.Path, "/")[3]
|
||||
artist, err := controller.GetArtist(app.DB, artistID)
|
||||
if err != nil {
|
||||
fmt.Printf("WARN: Failed to pull artists %s: %s\n", artistID, err)
|
||||
fmt.Printf("WARN: Failed to fetch artist %s: %s\n", artistID, err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
|
@ -168,7 +172,7 @@ func serveNewCredit(app *model.AppState) http.Handler {
|
|||
w.Header().Set("Content-Type", "text/html")
|
||||
err = templates.NewCreditTemplate.Execute(w, artist)
|
||||
if err != nil {
|
||||
fmt.Printf("Error rendering new credit component for %s: %s\n", artist.ID, err)
|
||||
fmt.Printf("WARN: Failed to serve new credit component for %s: %s\n", artist.ID, err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
}
|
||||
})
|
||||
|
|
@ -177,9 +181,9 @@ func serveNewCredit(app *model.AppState) http.Handler {
|
|||
func serveEditLinks(release *model.Release) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
err := templates.EditCreditsTemplate.Execute(w, release)
|
||||
err := templates.EditLinksTemplate.Execute(w, release)
|
||||
if err != nil {
|
||||
fmt.Printf("Error rendering edit links component for %s: %s\n", release.ID, err)
|
||||
fmt.Printf("WARN: Failed to serve edit links component for %s: %s\n", release.ID, err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
}
|
||||
})
|
||||
|
|
@ -199,7 +203,7 @@ func serveEditTracks(release *model.Release) http.Handler {
|
|||
Add: func(a, b int) int { return a + b },
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("Error rendering edit tracks component for %s: %s\n", release.ID, err)
|
||||
fmt.Printf("WARN: Failed to serve edit tracks component for %s: %s\n", release.ID, err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
}
|
||||
})
|
||||
|
|
@ -209,7 +213,7 @@ func serveAddTrack(app *model.AppState, release *model.Release) http.Handler {
|
|||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
tracks, err := controller.GetTracksNotOnRelease(app.DB, release.ID)
|
||||
if err != nil {
|
||||
fmt.Printf("WARN: Failed to pull tracks not on %s: %s\n", release.ID, err)
|
||||
fmt.Printf("WARN: Failed to fetch tracks not on %s: %s\n", release.ID, err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
|
@ -225,7 +229,7 @@ func serveAddTrack(app *model.AppState, release *model.Release) http.Handler {
|
|||
Tracks: tracks,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("Error rendering add tracks component for %s: %s\n", release.ID, err)
|
||||
fmt.Printf("WARN: Failed to add tracks component for %s: %s\n", release.ID, err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
}
|
||||
})
|
||||
|
|
@ -236,7 +240,7 @@ func serveNewTrack(app *model.AppState) http.Handler {
|
|||
trackID := strings.Split(r.URL.Path, "/")[3]
|
||||
track, err := controller.GetTrack(app.DB, trackID)
|
||||
if err != nil {
|
||||
fmt.Printf("Error rendering new track component for %s: %s\n", trackID, err)
|
||||
fmt.Printf("WARN: Failed to fetch track %s: %s\n", trackID, err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
|
@ -248,7 +252,7 @@ func serveNewTrack(app *model.AppState) http.Handler {
|
|||
w.Header().Set("Content-Type", "text/html")
|
||||
err = templates.NewTrackTemplate.Execute(w, track)
|
||||
if err != nil {
|
||||
fmt.Printf("Error rendering new track component for %s: %s\n", track.ID, err)
|
||||
fmt.Printf("WARN: Failed to serve new track component for %s: %s\n", track.ID, err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -95,23 +95,26 @@ h1, h2, h3, h4, h5, h6 {
|
|||
}
|
||||
|
||||
header {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 180px;
|
||||
background-color: var(--bg-1);
|
||||
box-shadow: var(--shadow-md);
|
||||
|
||||
transition: background .1s ease-out, color .1s ease-out;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
nav {
|
||||
height: 100%;
|
||||
margin: 1em 0;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 180px;
|
||||
height: calc(100vh - 2em);
|
||||
margin: 0;
|
||||
padding: 1em 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: left;
|
||||
|
||||
background-color: var(--bg-1);
|
||||
box-shadow: var(--shadow-md);
|
||||
transition: background .1s ease-out, color .1s ease-out;
|
||||
|
||||
user-select: none;
|
||||
}
|
||||
nav .icon {
|
||||
|
|
@ -134,17 +137,18 @@ nav .icon img {
|
|||
color: var(--fg-2);
|
||||
line-height: 2em;
|
||||
font-weight: 500;
|
||||
transition: color .1s, background-color .1s;
|
||||
transition: color .1s ease-out, background-color .1s ease-out;
|
||||
}
|
||||
.nav-item:hover {
|
||||
background: var(--bg-2);
|
||||
color: var(--bg-2);
|
||||
background-color: var(--fg-2);
|
||||
text-decoration: none;
|
||||
}
|
||||
.nav-item.active {
|
||||
border-left: 4px solid var(--fg-2);
|
||||
}
|
||||
.nav-item.active a {
|
||||
padding-left: calc(1em - 4px);
|
||||
padding-left: calc(1em - 3.5px);
|
||||
}
|
||||
nav a {
|
||||
padding: .2em 1em;
|
||||
|
|
|
|||
23
admin/static/artists.css
Normal file
23
admin/static/artists.css
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
.artist {
|
||||
padding: .5em;
|
||||
|
||||
color: var(--fg-3);
|
||||
background: var(--bg-2);
|
||||
box-shadow: var(--shadow-md);
|
||||
border-radius: 16px;
|
||||
text-align: center;
|
||||
|
||||
cursor: pointer;
|
||||
transition: background .1s ease-out, color .1s ease-out;
|
||||
}
|
||||
|
||||
.artist:hover {
|
||||
background: var(--bg-1);
|
||||
text-decoration: hover;
|
||||
}
|
||||
|
||||
.artist .artist-avatar {
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 8px;
|
||||
}
|
||||
7
admin/static/artists.js
Normal file
7
admin/static/artists.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { hijackClickEvent } from "./admin.js";
|
||||
|
||||
document.addEventListener("readystatechange", () => {
|
||||
document.querySelectorAll(".artists-group .artist").forEach(el => {
|
||||
hijackClickEvent(el, el.querySelector("a.artist-name"))
|
||||
});
|
||||
});
|
||||
|
|
@ -133,10 +133,13 @@ input[type="text"] {
|
|||
dialog {
|
||||
width: min(720px, calc(100% - 2em));
|
||||
padding: 2em;
|
||||
border: 1px solid #101010;
|
||||
border: none;
|
||||
border-radius: 16px;
|
||||
color: var(--fg-0);
|
||||
background: var(--bg-0);
|
||||
background-color: var(--bg-0);
|
||||
box-shadow: var(--shadow-lg);
|
||||
|
||||
transition: color .1s ease-out, background-color .1s ease-out;
|
||||
}
|
||||
|
||||
dialog header {
|
||||
|
|
@ -433,68 +436,6 @@ dialog div.dialog-actions {
|
|||
outline: 1px solid #808080;
|
||||
}
|
||||
|
||||
/*
|
||||
* RELEASE TRACKS
|
||||
*/
|
||||
|
||||
#tracks .track {
|
||||
margin-bottom: 1em;
|
||||
padding: 1em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .5em;
|
||||
|
||||
border-radius: 16px;
|
||||
background: var(--bg-2);
|
||||
box-shadow: var(--shadow-md);
|
||||
|
||||
transition: background .1s ease-out, color .1s ease-out;
|
||||
}
|
||||
|
||||
#tracks .track h3,
|
||||
#tracks .track p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#tracks h2.track-title {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
gap: .5em;
|
||||
}
|
||||
|
||||
#tracks h2.track-title .track-number {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
#tracks a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#tracks .track-album {
|
||||
margin-left: auto;
|
||||
font-style: italic;
|
||||
font-size: .75em;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
#tracks .track-album.empty {
|
||||
color: #ff2020;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#tracks .track-description {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
#tracks .track-lyrics {
|
||||
max-height: 10em;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
#tracks .track .empty {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
#edittracks ul {
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
|
|
|
|||
|
|
@ -1,84 +0,0 @@
|
|||
@import url("/admin/static/release-list-item.css");
|
||||
|
||||
.artist {
|
||||
padding: .5em;
|
||||
|
||||
color: var(--fg-3);
|
||||
background: var(--bg-2);
|
||||
box-shadow: var(--shadow-md);
|
||||
border-radius: 16px;
|
||||
text-align: center;
|
||||
|
||||
cursor: pointer;
|
||||
transition: background .1s ease-out, color .1s ease-out;
|
||||
}
|
||||
|
||||
.artist:hover {
|
||||
background: var(--bg-1);
|
||||
text-decoration: hover;
|
||||
}
|
||||
|
||||
.artist-avatar {
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.track {
|
||||
margin-bottom: 1em;
|
||||
padding: 1em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .5em;
|
||||
|
||||
border-radius: 8px;
|
||||
background: #f8f8f8f8;
|
||||
border: 1px solid #808080;
|
||||
|
||||
transition: background .1s ease-out, color .1s ease-out;
|
||||
}
|
||||
|
||||
.track p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.card h2.track-title {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.track-id {
|
||||
width: fit-content;
|
||||
font-family: "Monaspace Argon", monospace;
|
||||
font-size: .8em;
|
||||
font-style: italic;
|
||||
line-height: 1em;
|
||||
user-select: all;
|
||||
}
|
||||
|
||||
.track-album {
|
||||
margin-left: auto;
|
||||
font-style: italic;
|
||||
font-size: .75em;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.track-album.empty {
|
||||
color: #ff2020;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.track-description {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.track-lyrics {
|
||||
max-height: 10em;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.track .empty {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
import { hijackClickEvent } from "./admin.js";
|
||||
|
||||
const newReleaseBtn = document.getElementById("create-release");
|
||||
const newArtistBtn = document.getElementById("create-artist");
|
||||
const newTrackBtn = document.getElementById("create-track");
|
||||
|
|
@ -74,9 +72,3 @@ newTrackBtn.addEventListener("click", event => {
|
|||
console.error(err);
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener("readystatechange", () => {
|
||||
document.querySelectorAll("#artists .artist").forEach(el => {
|
||||
hijackClickEvent(el, el.querySelector("a.artist-name"))
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
.release-artwork {
|
||||
.release .release-artwork {
|
||||
margin: auto 0;
|
||||
width: 96px;
|
||||
|
||||
|
|
@ -29,16 +29,16 @@
|
|||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.release-artwork img {
|
||||
.release .release-artwork img {
|
||||
width: 100%;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
.release-title small {
|
||||
.release .release-title small {
|
||||
opacity: .75;
|
||||
}
|
||||
|
||||
.release-links {
|
||||
.release .release-links {
|
||||
margin: .5em 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
|
|
@ -48,13 +48,13 @@
|
|||
gap: .5em;
|
||||
}
|
||||
|
||||
.release-actions {
|
||||
.release .release-actions {
|
||||
margin-top: .5em;
|
||||
user-select: none;
|
||||
color: var(--fg-3);
|
||||
}
|
||||
|
||||
.release-actions a {
|
||||
.release .release-actions a {
|
||||
margin-right: .3em;
|
||||
padding: .3em .5em;
|
||||
display: inline-block;
|
||||
|
|
@ -66,7 +66,7 @@
|
|||
transition: color .1s ease-out, background .1s ease-out;
|
||||
}
|
||||
|
||||
.release-actions a:hover {
|
||||
.release .release-actions a:hover {
|
||||
background: var(--bg-0);
|
||||
color: var(--fg-3);
|
||||
text-decoration: none;
|
||||
127
admin/static/tracks.css
Normal file
127
admin/static/tracks.css
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
#tracks h2.track-title {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
gap: .5em;
|
||||
}
|
||||
|
||||
#tracks .track {
|
||||
margin-bottom: 1em;
|
||||
padding: 1em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .5em;
|
||||
|
||||
border-radius: 16px;
|
||||
background: var(--bg-2);
|
||||
box-shadow: var(--shadow-md);
|
||||
|
||||
transition: background .1s ease-out, color .1s ease-out;
|
||||
}
|
||||
|
||||
#tracks .track h3,
|
||||
#tracks .track p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#tracks h2.track-title {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
gap: .5em;
|
||||
}
|
||||
|
||||
#tracks h2.track-title .track-number {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
#tracks a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#tracks .track-album {
|
||||
margin-left: auto;
|
||||
font-style: italic;
|
||||
font-size: .75em;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
#tracks .track-album.empty {
|
||||
color: #ff2020;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#tracks .track-description {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
#tracks .track-lyrics {
|
||||
max-height: 10em;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
#tracks .track .empty {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
|
||||
.card h2.track-title {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
/*
|
||||
justify-content: space-between;
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
.track {
|
||||
margin-bottom: 1em;
|
||||
padding: 1em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .5em;
|
||||
|
||||
border-radius: 8px;
|
||||
background-color: var(--bg-2);
|
||||
box-shadow: var(--shadow-md);
|
||||
|
||||
transition: color .1s ease-out, background-color .1s ease-out;
|
||||
}
|
||||
|
||||
.track p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.track-id {
|
||||
width: fit-content;
|
||||
font-family: "Monaspace Argon", monospace;
|
||||
font-size: .8em;
|
||||
font-style: italic;
|
||||
line-height: 1em;
|
||||
user-select: all;
|
||||
}
|
||||
|
||||
.track-album {
|
||||
margin-left: auto;
|
||||
font-style: italic;
|
||||
font-size: .75em;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.track-album.empty {
|
||||
color: #ff2020;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.track-description {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.track-lyrics {
|
||||
max-height: 10em;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.track .empty {
|
||||
opacity: 0.75;
|
||||
}
|
||||
*/
|
||||
|
|
@ -1,24 +1,21 @@
|
|||
{{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">
|
||||
<link rel="stylesheet" href="/admin/static/admin.css">
|
||||
<link rel="stylesheet" href="/admin/static/artists.css">
|
||||
{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<main>
|
||||
<h1>Artists</h1>
|
||||
|
||||
<div class="card-header">
|
||||
<h2><a href="/admin/artists/">Artists</a></h2>
|
||||
<header>
|
||||
<h1><a href="/admin/artists/">Artists</a></h2>
|
||||
<a class="button new" id="create-artist">Create New</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{{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>
|
||||
{{range .Artists}}
|
||||
{{block "artist" .}}{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
{{else}}
|
||||
|
|
@ -27,4 +24,5 @@
|
|||
</main>
|
||||
|
||||
<script type="module" src="/admin/static/admin.js"></script>
|
||||
<script type="module" src="/admin/static/artists.js"></script>
|
||||
{{end}}
|
||||
|
|
|
|||
6
admin/templates/html/components/artist/artist.html
Normal file
6
admin/templates/html/components/artist/artist.html
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{{define "artist"}}
|
||||
<div class="artist">
|
||||
<img src="{{.GetAvatar}}" alt="" width="64" loading="lazy" class="artist-avatar">
|
||||
<a href="/admin/artists/{{.ID}}" class="artist-name">{{.Name}}</a>
|
||||
</div>
|
||||
{{end}}
|
||||
24
admin/templates/html/components/track/track.html
Normal file
24
admin/templates/html/components/track/track.html
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{{define "track"}}
|
||||
<div class="track" data-id="{{.ID}}">
|
||||
<h2 class="track-title">
|
||||
{{if .Number}}
|
||||
<span class="track-number">{{.Number}}</span>
|
||||
{{end}}
|
||||
<a href="/admin/tracks/{{.ID}}">{{.Title}}</a>
|
||||
</h2>
|
||||
|
||||
<h3>Description</h3>
|
||||
{{if .Description}}
|
||||
<p class="track-description">{{.GetDescriptionHTML}}</p>
|
||||
{{else}}
|
||||
<p class="track-description empty">No description provided.</p>
|
||||
{{end}}
|
||||
|
||||
<h3>Lyrics</h3>
|
||||
{{if .Lyrics}}
|
||||
<p class="track-lyrics">{{.GetLyricsHTML}}</p>
|
||||
{{else}}
|
||||
<p class="track-lyrics empty">There are no lyrics.</p>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
<title>Editing {{.Artist.Name}} - ari melody 💫</title>
|
||||
<link rel="shortcut icon" href="{{.Artist.GetAvatar}}" type="image/x-icon">
|
||||
<link rel="stylesheet" href="/admin/static/edit-artist.css">
|
||||
<link rel="stylesheet" href="/admin/static/artists.css">
|
||||
{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
<title>Editing {{.Release.Title}} - ari melody 💫</title>
|
||||
<link rel="shortcut icon" href="{{.Release.GetArtwork}}" type="image/x-icon">
|
||||
<link rel="stylesheet" href="/admin/static/edit-release.css">
|
||||
<link rel="stylesheet" href="/admin/static/releases.css">
|
||||
<link rel="stylesheet" href="/admin/static/tracks.css">
|
||||
{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
|
|
@ -154,26 +156,7 @@
|
|||
>Edit</a>
|
||||
</div>
|
||||
{{range $i, $track := .Release.Tracks}}
|
||||
<div class="track" data-id="{{$track.ID}}">
|
||||
<h2 class="track-title">
|
||||
<span class="track-number">{{.Add $i 1}}</span>
|
||||
<a href="/admin/tracks/{{$track.ID}}">{{$track.Title}}</a>
|
||||
</h2>
|
||||
|
||||
<h3>Description</h3>
|
||||
{{if $track.Description}}
|
||||
<p class="track-description">{{$track.GetDescriptionHTML}}</p>
|
||||
{{else}}
|
||||
<p class="track-description empty">No description provided.</p>
|
||||
{{end}}
|
||||
|
||||
<h3>Lyrics</h3>
|
||||
{{if $track.Lyrics}}
|
||||
<p class="track-lyrics">{{$track.GetLyricsHTML}}</p>
|
||||
{{else}}
|
||||
<p class="track-lyrics empty">There are no lyrics.</p>
|
||||
{{end}}
|
||||
</div>
|
||||
{{block "track" .}}{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
<title>Editing Track - ari melody 💫</title>
|
||||
<link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon">
|
||||
<link rel="stylesheet" href="/admin/static/edit-track.css">
|
||||
<link rel="stylesheet" href="/admin/static/tracks.css">
|
||||
<link rel="stylesheet" href="/admin/static/releases.css">
|
||||
{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
{{define "head"}}
|
||||
<title>Admin - ari melody 💫</title>
|
||||
<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/admin.css">
|
||||
<link rel="stylesheet" href="/admin/static/releases.css">
|
||||
<link rel="stylesheet" href="/admin/static/artists.css">
|
||||
<link rel="stylesheet" href="/admin/static/tracks.css">
|
||||
{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
|
|
@ -14,26 +17,24 @@
|
|||
<h2><a href="/admin/releases/">Releases</a> <small>({{.ReleaseCount}} total)</small></h2>
|
||||
<a class="button new" id="create-release">Create New</a>
|
||||
</div>
|
||||
{{if .Artists}}
|
||||
{{range .Releases}}
|
||||
{{block "release" .}}{{end}}
|
||||
{{end}}
|
||||
{{if not .Releases}}
|
||||
{{else}}
|
||||
<p>There are no releases.</p>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<div class="card" id="artists">
|
||||
<div class="card-header">
|
||||
<h2><a href="/admin/artists/">Artists</a></h2>
|
||||
<h2><a href="/admin/artists/">Artists</a> <small>({{.ArtistCount}} total)</small></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>
|
||||
{{range .Artists}}
|
||||
{{block "artist" .}}{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
{{else}}
|
||||
|
|
@ -43,30 +44,13 @@
|
|||
|
||||
<div class="card" id="tracks">
|
||||
<div class="card-header">
|
||||
<h2><a href="/admin/tracks/">Tracks</a></h2>
|
||||
<h2><a href="/admin/tracks/">Tracks</a> <small>({{.TrackCount}} total)</small></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>
|
||||
{{range .Tracks}}
|
||||
{{block "track" .}}{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -74,5 +58,6 @@
|
|||
</main>
|
||||
|
||||
<script type="module" src="/admin/static/admin.js"></script>
|
||||
<script type="module" src="/admin/static/artists.js"></script>
|
||||
<script type="module" src="/admin/static/index.js"></script>
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
{{range .Logs}}
|
||||
<tr class="log {{lower (parseLevel .Level)}}">
|
||||
<tr class="log {{toLower (parseLevel .Level)}}">
|
||||
<td class="log-time">{{prettyTime .CreatedAt}}</td>
|
||||
<td class="log-level">{{parseLevel .Level}}</td>
|
||||
<td class="log-type">{{titleCase .Type}}</td>
|
||||
|
|
|
|||
|
|
@ -1,19 +1,24 @@
|
|||
{{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">
|
||||
<link rel="stylesheet" href="/admin/static/admin.css">
|
||||
<link rel="stylesheet" href="/admin/static/releases.css">
|
||||
{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<main>
|
||||
<div class="card-header">
|
||||
<header>
|
||||
<h1><a href="/admin/releases/">Releases</a></h1>
|
||||
<a class="button new" id="create-release">Create New</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{{if .Releases}}
|
||||
<div id="releases">
|
||||
{{range .Releases}}
|
||||
{{block "release" .}}{{end}}
|
||||
{{end}}
|
||||
{{if not .Releases}}
|
||||
</div>
|
||||
{{else}}
|
||||
<p>There are no releases.</p>
|
||||
{{end}}
|
||||
</main>
|
||||
|
|
|
|||
|
|
@ -1,19 +1,18 @@
|
|||
{{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">
|
||||
<link rel="stylesheet" href="/admin/static/admin.css">
|
||||
<link rel="stylesheet" href="/admin/static/tracks.css">
|
||||
{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<main>
|
||||
<h1>Releases</h1>
|
||||
|
||||
<div class="card-header">
|
||||
<h2><a href="/admin/tracks/">Tracks</a></h2>
|
||||
<header>
|
||||
<h1><a href="/admin/tracks/">Tracks</a></h1>
|
||||
<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>
|
||||
</header>
|
||||
|
||||
<div id="tracks">
|
||||
{{range $Track := .Tracks}}
|
||||
<div class="track">
|
||||
<h2 class="track-title">
|
||||
|
|
@ -31,9 +30,7 @@
|
|||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{if not .Artists}}
|
||||
<p>There are no artists.</p>
|
||||
{{end}}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script type="module" src="/admin/static/admin.js"></script>
|
||||
|
|
|
|||
|
|
@ -50,33 +50,34 @@ var editArtistHTML string
|
|||
//go:embed "html/edit-track.html"
|
||||
var editTrackHTML string
|
||||
|
||||
//go:embed "html/components/credits/newcredit.html"
|
||||
//go:embed "html/components/credit/newcredit.html"
|
||||
var componentNewCreditHTML string
|
||||
//go:embed "html/components/credits/addcredit.html"
|
||||
//go:embed "html/components/credit/addcredit.html"
|
||||
var componentAddCreditHTML string
|
||||
//go:embed "html/components/credits/editcredits.html"
|
||||
//go:embed "html/components/credit/editcredits.html"
|
||||
var componentEditCreditsHTML string
|
||||
|
||||
//go:embed "html/components/links/editlinks.html"
|
||||
//go:embed "html/components/link/editlinks.html"
|
||||
var componentEditLinksHTML string
|
||||
|
||||
//go:embed "html/components/release/release-list-item.html"
|
||||
var componentReleaseListItemHTML string
|
||||
//go:embed "html/components/release/release.html"
|
||||
var componentReleaseHTML string
|
||||
//go:embed "html/components/artist/artist.html"
|
||||
var componentArtistHTML string
|
||||
//go:embed "html/components/track/track.html"
|
||||
var componentTrackHTML string
|
||||
|
||||
//go:embed "html/components/tracks/newtrack.html"
|
||||
//go:embed "html/components/track/newtrack.html"
|
||||
var componentNewTrackHTML string
|
||||
//go:embed "html/components/tracks/addtrack.html"
|
||||
//go:embed "html/components/track/addtrack.html"
|
||||
var componentAddTrackHTML string
|
||||
//go:embed "html/components/tracks/edittracks.html"
|
||||
//go:embed "html/components/track/edittracks.html"
|
||||
var componentEditTracksHTML string
|
||||
|
||||
var BaseTemplate = template.Must(
|
||||
template.New("base").Funcs(
|
||||
template.FuncMap{
|
||||
"hasPrefix": func(s string, prefix string) bool {
|
||||
fmt.Printf("does \"%s\" start with \"%s\"?\n", s, prefix)
|
||||
return strings.HasPrefix(s, prefix)
|
||||
},
|
||||
"hasPrefix": strings.HasPrefix,
|
||||
},
|
||||
).Parse(strings.Join([]string{
|
||||
layoutHTML,
|
||||
|
|
@ -86,10 +87,14 @@ var BaseTemplate = template.Must(
|
|||
var IndexTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(
|
||||
strings.Join([]string{
|
||||
indexHTML,
|
||||
componentReleaseListItemHTML,
|
||||
componentReleaseHTML,
|
||||
componentArtistHTML,
|
||||
componentTrackHTML,
|
||||
}, "\n"),
|
||||
))
|
||||
|
||||
|
||||
|
||||
var LoginTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(loginHTML))
|
||||
var LoginTOTPTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(loginTotpHTML))
|
||||
var RegisterTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(registerHTML))
|
||||
|
|
@ -98,51 +103,54 @@ var AccountTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(ed
|
|||
var TOTPSetupTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(totpSetupHTML))
|
||||
var TOTPConfirmTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(totpConfirmHTML))
|
||||
|
||||
|
||||
|
||||
var LogsTemplate = template.Must(template.Must(BaseTemplate.Clone()).Funcs(template.FuncMap{
|
||||
"parseLevel": func(level log.LogLevel) string {
|
||||
switch level {
|
||||
case log.LEVEL_INFO:
|
||||
return "INFO"
|
||||
case log.LEVEL_WARN:
|
||||
return "WARN"
|
||||
}
|
||||
return fmt.Sprintf("%d?", level)
|
||||
},
|
||||
"titleCase": func(logType string) string {
|
||||
runes := []rune(logType)
|
||||
for i, r := range runes {
|
||||
if (i == 0 || runes[i - 1] == ' ') && r >= 'a' && r <= 'z' {
|
||||
runes[i] = r + ('A' - 'a')
|
||||
}
|
||||
}
|
||||
return string(runes)
|
||||
},
|
||||
"lower": func(str string) string { return strings.ToLower(str) },
|
||||
"prettyTime": func(t time.Time) string {
|
||||
// return t.Format("2006-01-02 15:04:05")
|
||||
// return t.Format("15:04:05, 2 Jan 2006")
|
||||
return t.Format("02 Jan 2006, 15:04:05")
|
||||
},
|
||||
"parseLevel": parseLevel,
|
||||
"titleCase": titleCase,
|
||||
"toLower": toLower,
|
||||
"prettyTime": prettyTime,
|
||||
}).Parse(logsHTML))
|
||||
|
||||
|
||||
|
||||
var ReleasesTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(
|
||||
strings.Join([]string{
|
||||
releasesHTML,
|
||||
componentReleaseListItemHTML,
|
||||
componentReleaseHTML,
|
||||
}, "\n"),
|
||||
))
|
||||
var ArtistsTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(
|
||||
strings.Join([]string{
|
||||
artistsHTML,
|
||||
componentArtistHTML,
|
||||
}, "\n"),
|
||||
))
|
||||
var TracksTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(
|
||||
strings.Join([]string{
|
||||
tracksHTML,
|
||||
componentTrackHTML,
|
||||
}, "\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(
|
||||
strings.Join([]string{
|
||||
editReleaseHTML,
|
||||
componentTrackHTML,
|
||||
}, "\n"),
|
||||
))
|
||||
var EditArtistTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(editArtistHTML))
|
||||
var EditTrackTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(
|
||||
strings.Join([]string{
|
||||
editTrackHTML,
|
||||
componentReleaseListItemHTML,
|
||||
componentReleaseHTML,
|
||||
}, "\n"),
|
||||
))
|
||||
|
||||
|
||||
|
||||
var EditCreditsTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(componentEditCreditsHTML))
|
||||
var AddCreditTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(componentAddCreditHTML))
|
||||
var NewCreditTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(componentNewCreditHTML))
|
||||
|
|
@ -152,3 +160,32 @@ var EditLinksTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(
|
|||
var EditTracksTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(componentEditTracksHTML))
|
||||
var AddTrackTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(componentAddTrackHTML))
|
||||
var NewTrackTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(componentNewTrackHTML))
|
||||
|
||||
|
||||
|
||||
func parseLevel(level log.LogLevel) string {
|
||||
switch level {
|
||||
case log.LEVEL_INFO:
|
||||
return "INFO"
|
||||
case log.LEVEL_WARN:
|
||||
return "WARN"
|
||||
}
|
||||
return fmt.Sprintf("%d?", level)
|
||||
}
|
||||
func titleCase(logType string) string {
|
||||
runes := []rune(logType)
|
||||
for i, r := range runes {
|
||||
if (i == 0 || runes[i - 1] == ' ') && r >= 'a' && r <= 'z' {
|
||||
runes[i] = r + ('A' - 'a')
|
||||
}
|
||||
}
|
||||
return string(runes)
|
||||
}
|
||||
func toLower(str string) string {
|
||||
return strings.ToLower(str)
|
||||
}
|
||||
func prettyTime(t time.Time) string {
|
||||
// return t.Format("2006-01-02 15:04:05")
|
||||
// return t.Format("15:04:05, 2 Jan 2006")
|
||||
return t.Format("02 Jan 2006, 15:04:05")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,11 +12,46 @@ import (
|
|||
|
||||
func serveTracks(app *model.AppState) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
session := r.Context().Value("session").(*model.Session)
|
||||
|
||||
slices := strings.Split(strings.TrimPrefix(r.URL.Path, "/tracks")[1:], "/")
|
||||
id := slices[0]
|
||||
track, err := controller.GetTrack(app.DB, id)
|
||||
trackID := slices[0]
|
||||
|
||||
if len(trackID) > 0 {
|
||||
serveTrack(app, trackID).ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
tracks, err := controller.GetAllTracks(app.DB)
|
||||
if err != nil {
|
||||
fmt.Printf("Error rendering admin track page for %s: %s\n", id, err)
|
||||
fmt.Printf("WARN: Failed to fetch tracks: %s\n", err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
type TracksResponse struct {
|
||||
adminPageData
|
||||
Tracks []*model.Track
|
||||
}
|
||||
|
||||
err = templates.TracksTemplate.Execute(w, TracksResponse{
|
||||
adminPageData: adminPageData{ Path: r.URL.Path, Session: session },
|
||||
Tracks: tracks,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("WARN: Failed to serve admin tracks page: %s\n", err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func serveTrack(app *model.AppState, trackID string) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
session := r.Context().Value("session").(*model.Session)
|
||||
|
||||
track, err := controller.GetTrack(app.DB, trackID)
|
||||
if err != nil {
|
||||
fmt.Printf("WARN: Failed to serve admin track page for %s: %s\n", trackID, err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
|
@ -27,7 +62,7 @@ func serveTracks(app *model.AppState) http.Handler {
|
|||
|
||||
releases, err := controller.GetTrackReleases(app.DB, track.ID, true)
|
||||
if err != nil {
|
||||
fmt.Printf("FATAL: Failed to pull releases for %s: %s\n", id, err)
|
||||
fmt.Printf("WARN: Failed to fetch releases for track %s: %s\n", trackID, err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
|
@ -38,17 +73,14 @@ func serveTracks(app *model.AppState) http.Handler {
|
|||
Releases []*model.Release
|
||||
}
|
||||
|
||||
session := r.Context().Value("session").(*model.Session)
|
||||
|
||||
err = templates.EditTrackTemplate.Execute(w, TrackResponse{
|
||||
adminPageData: adminPageData{ Path: r.URL.Path, Session: session },
|
||||
Track: track,
|
||||
Releases: releases,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("Error rendering admin track page for %s: %s\n", id, err)
|
||||
fmt.Printf("WARN: Failed to serve admin track page for %s: %s\n", trackID, err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,11 @@ func GetAllArtists(db *sqlx.DB) ([]*model.Artist, error) {
|
|||
|
||||
return artists, nil
|
||||
}
|
||||
func GetArtistCount(db *sqlx.DB) (int, error) {
|
||||
var count int
|
||||
err := db.Get(&count, "SELECT count(*) FROM artist")
|
||||
return count, err
|
||||
}
|
||||
|
||||
func GetArtistsNotOnRelease(db *sqlx.DB, releaseID string) ([]*model.Artist, error) {
|
||||
var artists = []*model.Artist{}
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ func GetAllReleases(db *sqlx.DB, onlyVisible bool, limit int, full bool) ([]*mod
|
|||
|
||||
return releases, nil
|
||||
}
|
||||
func GetReleasesCount(db *sqlx.DB, onlyVisible bool) (int, error) {
|
||||
func GetReleaseCount(db *sqlx.DB, onlyVisible bool) (int, error) {
|
||||
query := "SELECT count(*) FROM musicrelease"
|
||||
if onlyVisible {
|
||||
query += " WHERE visible=true"
|
||||
|
|
|
|||
|
|
@ -29,6 +29,11 @@ func GetAllTracks(db *sqlx.DB) ([]*model.Track, error) {
|
|||
|
||||
return tracks, nil
|
||||
}
|
||||
func GetTrackCount(db *sqlx.DB) (int, error) {
|
||||
var count int
|
||||
err := db.Get(&count, "SELECT count(*) FROM musictrack")
|
||||
return count, err
|
||||
}
|
||||
|
||||
func GetOrphanTracks(db *sqlx.DB) ([]*model.Track, error) {
|
||||
var tracks = []*model.Track{}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ const (
|
|||
// GETTERS
|
||||
|
||||
func (release Release) GetDescriptionHTML() template.HTML {
|
||||
return template.HTML(strings.Replace(release.Description, "\n", "<br>", -1))
|
||||
return template.HTML(strings.ReplaceAll(release.Description, "\n", "<br>"))
|
||||
}
|
||||
|
||||
func (release Release) TextReleaseDate() string {
|
||||
|
|
|
|||
|
|
@ -13,16 +13,16 @@ type (
|
|||
Lyrics string `json:"lyrics" db:"lyrics"`
|
||||
PreviewURL string `json:"previewURL" db:"preview_url"`
|
||||
|
||||
Number int
|
||||
Number int `json:"-"`
|
||||
}
|
||||
)
|
||||
|
||||
func (track Track) GetDescriptionHTML() template.HTML {
|
||||
return template.HTML(strings.Replace(track.Description, "\n", "<br>", -1))
|
||||
return template.HTML(strings.ReplaceAll(track.Description, "\n", "<br>"))
|
||||
}
|
||||
|
||||
func (track Track) GetLyricsHTML() template.HTML {
|
||||
return template.HTML(strings.Replace(track.Lyrics, "\n", "<br>", -1))
|
||||
return template.HTML(strings.ReplaceAll(track.Lyrics, "\n", "<br>"))
|
||||
}
|
||||
|
||||
// this function is stupid and i hate that i need it
|
||||
|
|
|
|||
|
|
@ -333,7 +333,7 @@ ul#links a {
|
|||
background-color: #fff;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
transition: filter .1s,-webkit-filter .1s
|
||||
transition: filter .1s ease-out, -webkit-filter .1s ease-out;
|
||||
}
|
||||
|
||||
#buylink {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue