home to your local SPACEGIRL 💫 arimelody.space
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

more admin dashboard polish, some code cleanup

+187 -270
+7 -5
admin/releasehttp.go
··· 103 103 Release *model.Release 104 104 } 105 105 106 - for i, track := range release.Tracks { 107 - track.Number = i + 1 108 - } 106 + for i, track := range release.Tracks { track.Number = i + 1 } 109 107 110 108 err = templates.EditReleaseTemplate.Execute(w, ReleaseResponse{ 111 109 adminPageData: adminPageData{ Path: r.URL.Path, Session: session }, ··· 157 155 158 156 func serveNewCredit(app *model.AppState) http.Handler { 159 157 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 160 - artistID := strings.Split(r.URL.Path, "/")[3] 158 + split := strings.Split(r.URL.Path, "/") 159 + artistID := split[len(split) - 1] 161 160 artist, err := controller.GetArtist(app.DB, artistID) 162 161 if err != nil { 163 162 fmt.Printf("WARN: Failed to fetch artist %s: %s\n", artistID, err) ··· 194 193 w.Header().Set("Content-Type", "text/html") 195 194 196 195 type editTracksData struct { Release *model.Release } 196 + 197 + for i, track := range release.Tracks { track.Number = i + 1 } 197 198 198 199 err := templates.EditTracksTemplate.Execute(w, editTracksData{ Release: release }) 199 200 if err != nil { ··· 231 232 232 233 func serveNewTrack(app *model.AppState) http.Handler { 233 234 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 234 - trackID := strings.Split(r.URL.Path, "/")[3] 235 + split := strings.Split(r.URL.Path, "/") 236 + trackID := split[len(split) - 1] 235 237 track, err := controller.GetTrack(app.DB, trackID) 236 238 if err != nil { 237 239 fmt.Printf("WARN: Failed to fetch track %s: %s\n", trackID, err)
+9 -15
admin/static/admin.css
··· 111 111 font-family: "Inter", sans-serif; 112 112 font-size: 16px; 113 113 color: var(--fg-0); 114 - background: var(--bg-0); 114 + background-color: var(--bg-0); 115 115 116 116 transition: background .1s ease-out, color .1s ease-out; 117 117 } ··· 252 252 transition: color .1s ease-out, background-color .1s ease-out; 253 253 } 254 254 255 - /* 256 - a:hover { 257 - text-decoration: underline; 258 - } 259 - */ 260 - 261 255 img.icon { 262 256 height: .8em; 263 257 transition: filter .1s ease-out; ··· 283 277 .card { 284 278 flex-basis: 40em; 285 279 padding: 1em; 286 - background: var(--bg-1); 280 + background-color: var(--bg-1); 287 281 border-radius: 16px; 288 282 box-shadow: var(--shadow-lg); 289 283 ··· 361 355 font-size: inherit; 362 356 363 357 color: inherit; 364 - background: var(--bg-2); 358 + background-color: var(--bg-2); 365 359 border: none; 366 360 border-radius: 10em; 367 361 box-shadow: var(--shadow-sm); ··· 380 374 381 375 .button.new, button.new { 382 376 color: var(--col-on-new); 383 - background: var(--col-new); 377 + background-color: var(--col-new); 384 378 } 385 379 .button.save, button.save { 386 380 color: var(--col-on-save); 387 - background: var(--col-save); 381 + background-color: var(--col-save); 388 382 } 389 383 .button.delete, button.delete { 390 384 color: var(--col-on-delete); 391 - background: var(--col-delete); 385 + background-color: var(--col-delete); 392 386 } 393 387 .button:hover, button:hover { 394 388 color: var(--bg-3); 395 - background: var(--fg-3); 389 + background-color: var(--fg-3); 396 390 } 397 391 .button:active, button:active { 398 392 color: var(--bg-2); 399 - background: var(--fg-0); 393 + background-color: var(--fg-0); 400 394 } 401 395 .button[disabled], button[disabled] { 402 396 color: var(--fg-0) !important; 403 - background: var(--bg-3) !important; 397 + background-color: var(--bg-3) !important; 404 398 opacity: .5; 405 399 cursor: default !important; 406 400 }
+2 -2
admin/static/artists.css
··· 2 2 padding: .5em; 3 3 4 4 color: var(--fg-3); 5 - background: var(--bg-2); 5 + background-color: var(--bg-2); 6 6 box-shadow: var(--shadow-md); 7 7 border-radius: 16px; 8 8 text-align: center; ··· 12 12 } 13 13 14 14 .artist:hover { 15 - background: var(--bg-1); 15 + background-color: var(--bg-1); 16 16 text-decoration: hover; 17 17 } 18 18
+25
admin/static/artists.js
··· 4 4 document.querySelectorAll(".artists-group .artist").forEach(el => { 5 5 hijackClickEvent(el, el.querySelector("a.artist-name")) 6 6 }); 7 + 8 + const newArtistBtn = document.getElementById("create-artist"); 9 + if (newArtistBtn) newArtistBtn.addEventListener("click", event => { 10 + event.preventDefault(); 11 + const id = prompt("Enter an ID for this artist:"); 12 + if (id == null || id == "") return; 13 + 14 + fetch("/api/v1/artist", { 15 + method: "POST", 16 + headers: { "Content-Type": "application/json" }, 17 + body: JSON.stringify({id}) 18 + }).then(res => { 19 + res.text().then(text => { 20 + if (res.ok) { 21 + location = "/admin/artists/" + id; 22 + } else { 23 + alert(text); 24 + console.error(text); 25 + } 26 + }) 27 + }).catch(err => { 28 + alert("Failed to create artist. Check the console for details."); 29 + console.error(err); 30 + }); 31 + }); 7 32 });
+1 -1
admin/static/edit-account.css
··· 33 33 justify-content: space-between; 34 34 35 35 color: var(--fg-3); 36 - background: var(--bg-2); 36 + background-color: var(--bg-2); 37 37 box-shadow: var(--shadow-md); 38 38 border-radius: 16px; 39 39 }
+4 -11
admin/static/edit-artist.css
··· 6 6 gap: 1.2em; 7 7 8 8 border-radius: 16px; 9 - background: var(--bg-2); 9 + background-color: var(--bg-2); 10 10 box-shadow: var(--shadow-md); 11 11 } 12 12 ··· 50 50 font-family: inherit; 51 51 font-weight: inherit; 52 52 color: inherit; 53 - background: var(--bg-0); 53 + background-color: var(--bg-0); 54 54 border: none; 55 55 border-radius: 4px; 56 56 outline: none; 57 57 } 58 - input[type="text"]:hover { 59 - border-color: #80808080; 60 - } 61 - input[type="text"]:active, 62 - input[type="text"]:focus { 63 - border-color: #808080; 64 - } 65 58 66 59 .artist-actions { 67 60 margin-top: auto; ··· 84 77 align-items: center; 85 78 86 79 border-radius: 16px; 87 - background: var(--bg-2); 80 + background-color: var(--bg-2); 88 81 box-shadow: var(--shadow-md); 89 82 90 83 cursor: pointer; ··· 92 85 } 93 86 94 87 .credit:hover { 95 - background: var(--bg-1); 88 + background-color: var(--bg-1); 96 89 } 97 90 98 91 .release-artwork {
+22 -11
admin/static/edit-release.css
··· 12 12 gap: 1.2em; 13 13 14 14 border-radius: 8px; 15 - background: var(--bg-2); 15 + background-color: var(--bg-2); 16 16 box-shadow: var(--shadow-md); 17 17 18 18 transition: background .1s ease-out, color .1s ease-out; ··· 33 33 .release-artwork #remove-artwork { 34 34 margin-top: .5em; 35 35 padding: .3em .6em; 36 - background: var(--bg-3); 36 + background-color: var(--bg-3); 37 37 } 38 38 39 39 .release-info { ··· 62 62 } 63 63 64 64 #title:hover { 65 - background: var(--bg-3); 65 + background-color: var(--bg-3); 66 66 border-color: var(--fg-0); 67 67 } 68 68 69 69 #title:active, 70 70 #title:focus { 71 - background: var(--bg-3); 71 + background-color: var(--bg-3); 72 72 } 73 73 74 74 .release-title small { ··· 93 93 .release-info table tr td:not(:first-child) select:hover, 94 94 .release-info table tr td:not(:first-child) input:hover, 95 95 .release-info table tr td:not(:first-child) textarea:hover { 96 - background: var(--bg-3); 96 + background-color: var(--bg-3); 97 97 cursor: pointer; 98 98 } 99 99 .release-info table td select, ··· 127 127 .release-actions button, 128 128 .release-actions .button { 129 129 color: var(--fg-2); 130 - background: var(--bg-3); 130 + background-color: var(--bg-3); 131 131 } 132 132 133 133 dialog { ··· 234 234 gap: 1em; 235 235 236 236 border-radius: 8px; 237 - background: var(--bg-2); 237 + background-color: var(--bg-2); 238 238 box-shadow: var(--shadow-md); 239 239 } 240 240 ··· 280 280 border: none; 281 281 border-radius: 4px; 282 282 color: var(--fg-2); 283 - background: var(--bg-0); 283 + background-color: var(--bg-0); 284 284 } 285 285 #editcredits .credit .credit-info .credit-attribute input[type="checkbox"] { 286 286 margin: 0 .3em; ··· 299 299 #editcredits .credit .delete { 300 300 margin-right: .5em; 301 301 cursor: pointer; 302 + overflow: visible; 302 303 } 303 304 #editcredits .credit .delete:hover { 304 305 text-decoration: underline; ··· 315 316 display: flex; 316 317 gap: .5em; 317 318 cursor: pointer; 319 + background-color: var(--bg-2); 318 320 } 319 321 320 322 #addcredit ul li.new-artist:nth-child(even) { 321 323 background: #f0f0f0; 324 + background-color: var(--bg-1); 322 325 } 323 326 324 327 #addcredit ul li.new-artist:hover { 325 328 background: #e0e0e0; 329 + background-color: var(--bg-2); 326 330 } 327 331 328 332 #addcredit .new-artist .artist-id { ··· 375 379 376 380 #editlinks tr { 377 381 display: flex; 382 + background-color: var(--bg-1); 383 + transition: background-color .1s ease-out; 378 384 } 379 385 380 386 #editlinks th { ··· 385 391 } 386 392 387 393 #editlinks tr:nth-child(odd) { 388 - background: #f8f8f8; 394 + background-color: var(--bg-2); 389 395 } 390 396 391 397 #editlinks tr th, ··· 416 422 width: 1em; 417 423 pointer-events: none; 418 424 } 425 + @media (prefers-color-scheme: dark) { 426 + #editlinks tr .grabber img { 427 + filter: invert(); 428 + } 429 + } 419 430 #editlinks tr .link-name { 420 431 width: 8em; 421 432 } ··· 454 465 } 455 466 456 467 #edittracks .track { 468 + background-color: var(--bg-2); 457 469 transition: transform .2s ease-out, opacity .2s; 458 470 } 459 471 ··· 476 488 } 477 489 478 490 #edittracks .track:nth-child(even) { 479 - background: #f0f0f0; 491 + background-color: var(--bg-1); 480 492 } 481 493 482 494 #edittracks .track-number { ··· 492 504 #addtrack ul { 493 505 padding: 0; 494 506 list-style: none; 495 - background: #f8f8f8; 496 507 } 497 508 498 509 #addtrack ul li.new-track {
+2 -14
admin/static/edit-track.css
··· 8 8 gap: 1.2em; 9 9 10 10 border-radius: 16px; 11 - background: var(--bg-2); 11 + background-color: var(--bg-2); 12 12 box-shadow: var(--shadow-md); 13 13 } 14 14 ··· 45 45 font-weight: inherit; 46 46 font-family: inherit; 47 47 font-size: inherit; 48 - background: var(--bg-0); 48 + background-color: var(--bg-0); 49 49 border: none; 50 50 border-radius: 4px; 51 51 outline: none; 52 52 color: inherit; 53 - } 54 - 55 - .track-info input[type="text"]:hover, 56 - .track-info textarea:hover { 57 - border-color: #80808080; 58 - } 59 - 60 - .track-info input[type="text"]:active, 61 - .track-info textarea:active, 62 - .track-info input[type="text"]:focus, 63 - .track-info textarea:focus { 64 - border-color: #808080; 65 53 } 66 54 67 55 .track-actions {
-74
admin/static/index.js
··· 1 - const newReleaseBtn = document.getElementById("create-release"); 2 - const newArtistBtn = document.getElementById("create-artist"); 3 - const newTrackBtn = document.getElementById("create-track"); 4 - 5 - newReleaseBtn.addEventListener("click", event => { 6 - event.preventDefault(); 7 - const id = prompt("Enter an ID for this release:"); 8 - if (id == null || id == "") return; 9 - 10 - fetch("/api/v1/music", { 11 - method: "POST", 12 - headers: { "Content-Type": "application/json" }, 13 - body: JSON.stringify({id}) 14 - }).then(res => { 15 - if (res.ok) location = "/admin/releases/" + id; 16 - else { 17 - res.text().then(err => { 18 - alert("Request failed: " + err); 19 - console.error(err); 20 - }); 21 - } 22 - }).catch(err => { 23 - alert("Failed to create release. Check the console for details."); 24 - console.error(err); 25 - }); 26 - }); 27 - 28 - newArtistBtn.addEventListener("click", event => { 29 - event.preventDefault(); 30 - const id = prompt("Enter an ID for this artist:"); 31 - if (id == null || id == "") return; 32 - 33 - fetch("/api/v1/artist", { 34 - method: "POST", 35 - headers: { "Content-Type": "application/json" }, 36 - body: JSON.stringify({id}) 37 - }).then(res => { 38 - res.text().then(text => { 39 - if (res.ok) { 40 - location = "/admin/artists/" + id; 41 - } else { 42 - alert("Request failed: " + text); 43 - console.error(text); 44 - } 45 - }) 46 - }).catch(err => { 47 - alert("Failed to create artist. Check the console for details."); 48 - console.error(err); 49 - }); 50 - }); 51 - 52 - newTrackBtn.addEventListener("click", event => { 53 - event.preventDefault(); 54 - const title = prompt("Enter an title for this track:"); 55 - if (title == null || title == "") return; 56 - 57 - fetch("/api/v1/track", { 58 - method: "POST", 59 - headers: { "Content-Type": "application/json" }, 60 - body: JSON.stringify({title}) 61 - }).then(res => { 62 - res.text().then(text => { 63 - if (res.ok) { 64 - location = "/admin/tracks/" + text; 65 - } else { 66 - alert("Request failed: " + text); 67 - console.error(text); 68 - } 69 - }) 70 - }).catch(err => { 71 - alert("Failed to create track. Check the console for details."); 72 - console.error(err); 73 - }); 74 - });
+4 -4
admin/static/logs.css
··· 8 8 padding: 1em; 9 9 border-radius: 16px; 10 10 color: var(--fg-0); 11 - background: var(--bg-2); 11 + background-color: var(--bg-2); 12 12 box-shadow: var(--shadow-md); 13 13 } 14 14 ··· 23 23 border: none; 24 24 border-radius: 16px; 25 25 color: var(--fg-1); 26 - background: var(--bg-0); 26 + background-color: var(--bg-0); 27 27 box-shadow: var(--shadow-sm); 28 28 } 29 29 ··· 100 100 101 101 #logs .log.warn { 102 102 color: var(--col-on-warn); 103 - background: var(--col-warn); 103 + background-color: var(--col-warn); 104 104 } 105 105 #logs .log.warn:hover { 106 - background: var(--col-warn-hover); 106 + background-color: var(--col-warn-hover); 107 107 }
+3 -3
admin/static/releases.css
··· 6 6 gap: 1em; 7 7 8 8 border-radius: 16px; 9 - background: var(--bg-2); 9 + background-color: var(--bg-2); 10 10 box-shadow: var(--shadow-md); 11 11 12 12 transition: background .1s ease-out, color .1s ease-out; ··· 67 67 display: inline-block; 68 68 69 69 border-radius: 4px; 70 - background: var(--bg-3); 70 + background-color: var(--bg-3); 71 71 box-shadow: var(--shadow-sm); 72 72 73 73 transition: color .1s ease-out, background .1s ease-out; 74 74 } 75 75 76 76 .release .release-actions a:hover { 77 - background: var(--bg-0); 77 + background-color: var(--bg-0); 78 78 color: var(--fg-3); 79 79 text-decoration: none; 80 80 }
+25
admin/static/releases.js
··· 1 + document.addEventListener('readystatechange', () => { 2 + const newReleaseBtn = document.getElementById("create-release"); 3 + if (newReleaseBtn) newReleaseBtn.addEventListener("click", event => { 4 + event.preventDefault(); 5 + const id = prompt("Enter an ID for this release:"); 6 + if (id == null || id == "") return; 7 + 8 + fetch("/api/v1/music", { 9 + method: "POST", 10 + headers: { "Content-Type": "application/json" }, 11 + body: JSON.stringify({id}) 12 + }).then(res => { 13 + if (res.ok) location = "/admin/releases/" + id; 14 + else { 15 + res.text().then(err => { 16 + alert(err); 17 + console.error(err); 18 + }); 19 + } 20 + }).catch(err => { 21 + alert("Failed to create release. Check the console for details."); 22 + console.error(err); 23 + }); 24 + }); 25 + });
+1 -63
admin/static/tracks.css
··· 12 12 gap: .5em; 13 13 14 14 border-radius: 16px; 15 - background: var(--bg-2); 15 + background-color: var(--bg-2); 16 16 box-shadow: var(--shadow-md); 17 17 18 18 transition: background .1s ease-out, color .1s ease-out; ··· 44 44 opacity: .5; 45 45 } 46 46 47 - #tracks .track-album.empty { 48 - color: #ff2020; 49 - opacity: 1; 50 - } 51 - 52 47 #tracks .track-description { 53 48 font-style: italic; 54 49 } ··· 67 62 margin: 0; 68 63 display: flex; 69 64 flex-direction: row; 70 - /* 71 - justify-content: space-between; 72 - */ 73 65 } 74 - 75 - /* 76 - .track { 77 - margin-bottom: 1em; 78 - padding: 1em; 79 - display: flex; 80 - flex-direction: column; 81 - gap: .5em; 82 - 83 - border-radius: 8px; 84 - background-color: var(--bg-2); 85 - box-shadow: var(--shadow-md); 86 - 87 - transition: color .1s ease-out, background-color .1s ease-out; 88 - } 89 - 90 - .track p { 91 - margin: 0; 92 - } 93 - 94 - .track-id { 95 - width: fit-content; 96 - font-family: "Monaspace Argon", monospace; 97 - font-size: .8em; 98 - font-style: italic; 99 - line-height: 1em; 100 - user-select: all; 101 - } 102 - 103 - .track-album { 104 - margin-left: auto; 105 - font-style: italic; 106 - font-size: .75em; 107 - opacity: .5; 108 - } 109 - 110 - .track-album.empty { 111 - color: #ff2020; 112 - opacity: 1; 113 - } 114 - 115 - .track-description { 116 - font-style: italic; 117 - } 118 - 119 - .track-lyrics { 120 - max-height: 10em; 121 - overflow-y: scroll; 122 - } 123 - 124 - .track .empty { 125 - opacity: 0.75; 126 - } 127 - */
+24
admin/static/tracks.js
··· 1 + const newTrackBtn = document.getElementById("create-track"); 2 + if (newTrackBtn) newTrackBtn.addEventListener("click", event => { 3 + event.preventDefault(); 4 + const title = prompt("Enter an title for this track:"); 5 + if (title == null || title == "") return; 6 + 7 + fetch("/api/v1/track", { 8 + method: "POST", 9 + headers: { "Content-Type": "application/json" }, 10 + body: JSON.stringify({title}) 11 + }).then(res => { 12 + res.text().then(text => { 13 + if (res.ok) { 14 + location = "/admin/tracks/" + text; 15 + } else { 16 + alert(text); 17 + console.error(text); 18 + } 19 + }) 20 + }).catch(err => { 21 + alert("Failed to create track. Check the console for details."); 22 + console.error(err); 23 + }); 24 + });
+4 -5
admin/templates/html/components/track/edittracks.html
··· 12 12 13 13 <form action="/api/v1/music/{{.Release.ID}}/tracks"> 14 14 <ul> 15 - {{range $i, $track := .Release.Tracks}} 16 - <li class="track" data-track="{{$track.ID}}" data-title="{{$track.Title}}" data-number="{{$track.Add $i 1}}" draggable="true"> 15 + {{range .Release.Tracks}} 16 + <li class="track" data-track="{{.ID}}" data-title="{{.Title}}" data-number="{{.Number}}" draggable="true"> 17 17 <div> 18 18 <p class="track-name"> 19 - <span class="track-number">{{.Add $i 1}}</span> 20 - {{$track.Title}} 19 + <span class="track-number">{{.Number}}</span> 20 + {{.Title}} 21 21 </p> 22 22 <a class="delete">Delete</a> 23 23 </div> ··· 49 49 50 50 deleteBtn.addEventListener("click", e => { 51 51 e.preventDefault(); 52 - if (!confirm("Are you sure you want to remove " + trackTitle + "?")) return; 53 52 trackItem.remove(); 54 53 refreshTrackNumbers(); 55 54 });
+18 -8
admin/templates/html/edit-release.html
··· 100 100 </div> 101 101 </div> 102 102 103 - <div class="card" id="credits"> 103 + <div id="credits" class="card"> 104 104 <div class="card-header"> 105 105 <h2>Credits <small>({{len .Release.Credits}} total)</small></h2> 106 106 <a class="button edit" ··· 110 110 hx-swap="beforeend" 111 111 >Edit</a> 112 112 </div> 113 + 113 114 {{range .Release.Credits}} 114 115 <div class="credit"> 115 116 <img src="{{.Artist.GetAvatar}}" alt="" width="64" loading="lazy" class="artist-avatar"> ··· 125 126 </div> 126 127 {{end}} 127 128 {{if not .Release.Credits}} 128 - <p>There are no credits.</p> 129 + <p>This release has no credits.</p> 129 130 {{end}} 130 131 </div> 131 132 132 - <div class="card" id="links"> 133 + <div id="links" class="card"> 133 134 <div class="card-header"> 134 - <h2>Links ({{len .Release.Links}})</h2> 135 + <h2>Links <small>({{len .Release.Links}} total)</small></h2> 135 136 <a class="button edit" 136 137 href="/admin/releases/{{.Release.ID}}/editlinks" 137 138 hx-get="/admin/releases/{{.Release.ID}}/editlinks" ··· 139 140 hx-swap="beforeend" 140 141 >Edit</a> 141 142 </div> 143 + 144 + {{if .Release.Links}} 142 145 <ul> 143 146 {{range .Release.Links}} 144 147 <a href="{{.URL}}" target="_blank" class="button" data-name="{{.Name}}">{{.Name}} <img class="icon" src="/img/external-link.svg"/></a> 145 148 {{end}} 146 149 </ul> 150 + {{else}} 151 + <p>This release has no links.</p> 152 + {{end}} 147 153 </div> 148 154 149 - <div class="card" id="tracks"> 150 - <div class="card-header" id="tracks"> 151 - <h2>Tracklist ({{len .Release.Tracks}})</h2> 155 + <div id="tracks" class="card"> 156 + <div class="card-header"> 157 + <h2>Tracks <small>({{len .Release.Tracks}} total)</small></h2> 152 158 <a class="button edit" 153 159 href="/admin/releases/{{.Release.ID}}/edittracks" 154 160 hx-get="/admin/releases/{{.Release.ID}}/edittracks" ··· 156 162 hx-swap="beforeend" 157 163 >Edit</a> 158 164 </div> 159 - {{range $i, $track := .Release.Tracks}} 165 + 166 + {{range .Release.Tracks}} 160 167 {{block "track" .}}{{end}} 168 + {{end}} 169 + {{if not .Release.Tracks}} 170 + <p>This release has no tracks.</p> 161 171 {{end}} 162 172 </div> 163 173
+2 -1
admin/templates/html/index.html
··· 56 56 57 57 </main> 58 58 59 + <script type="module" src="/admin/static/releases.js"></script> 59 60 <script type="module" src="/admin/static/artists.js"></script> 60 - <script type="module" src="/admin/static/index.js"></script> 61 + <script type="module" src="/admin/static/tracks.js"></script> 61 62 {{end}}
+2
admin/templates/html/releases.html
··· 21 21 <p>There are no releases.</p> 22 22 {{end}} 23 23 </main> 24 + 25 + <script type="module" src="/admin/static/releases.js"></script> 24 26 {{end}}
+2 -16
admin/templates/html/tracks.html
··· 12 12 </header> 13 13 14 14 <div id="tracks"> 15 - {{range $Track := .Tracks}} 16 - <div class="track"> 17 - <h2 class="track-title"> 18 - <a href="/admin/tracks/{{$Track.ID}}">{{$Track.Title}}</a> 19 - </h2> 20 - {{if $Track.Description}} 21 - <p class="track-description">{{$Track.GetDescriptionHTML}}</p> 22 - {{else}} 23 - <p class="track-description empty">No description provided.</p> 24 - {{end}} 25 - {{if $Track.Lyrics}} 26 - <p class="track-lyrics">{{$Track.GetLyricsHTML}}</p> 27 - {{else}} 28 - <p class="track-lyrics empty">There are no lyrics.</p> 29 - {{end}} 30 - </div> 15 + {{range .Tracks}} 16 + {{block "track" .}}{{end}} 31 17 {{end}} 32 18 </div> 33 19 </main>
+10 -2
api/release.go
··· 324 324 325 325 err = controller.UpdateReleaseTracks(app.DB, release.ID, trackIDs) 326 326 if err != nil { 327 + if strings.Contains(err.Error(), "duplicate key") { 328 + http.Error(w, "Release cannot have duplicate tracks", http.StatusBadRequest) 329 + return 330 + } 327 331 if strings.Contains(err.Error(), "no rows") { 328 332 http.NotFound(w, r) 329 333 return ··· 366 370 err = controller.UpdateReleaseCredits(app.DB, release.ID, credits) 367 371 if err != nil { 368 372 if strings.Contains(err.Error(), "duplicate key") { 369 - http.Error(w, "Artists may only be credited once\n", http.StatusBadRequest) 373 + http.Error(w, "Artists may only be credited once", http.StatusBadRequest) 370 374 return 371 375 } 372 376 if strings.Contains(err.Error(), "no rows") { 373 377 http.NotFound(w, r) 374 378 return 375 379 } 376 - fmt.Printf("WARN: Failed to update links for %s: %s\n", release.ID, err) 380 + fmt.Printf("WARN: Failed to update credits for %s: %s\n", release.ID, err) 377 381 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 378 382 } 379 383 ··· 394 398 395 399 err = controller.UpdateReleaseLinks(app.DB, release.ID, links) 396 400 if err != nil { 401 + if strings.Contains(err.Error(), "duplicate key") { 402 + http.Error(w, "Release cannot have duplicate link names", http.StatusBadRequest) 403 + return 404 + } 397 405 if strings.Contains(err.Error(), "no rows") { 398 406 http.NotFound(w, r) 399 407 return
-5
model/track.go
··· 24 24 func (track Track) GetLyricsHTML() template.HTML { 25 25 return template.HTML(strings.ReplaceAll(track.Lyrics, "\n", "<br>")) 26 26 } 27 - 28 - // this function is stupid and i hate that i need it 29 - func (track Track) Add(a int, b int) int { 30 - return a + b 31 - }
-10
model/track_test.go
··· 31 31 t.Errorf(`track lyrics incorrectly formatted (want "%s", got "%s")`, want, got) 32 32 } 33 33 } 34 - 35 - func Test_Track_Add(t *testing.T) { 36 - track := Track{} 37 - 38 - want := 4 39 - got := track.Add(2, 2) 40 - if want != got { 41 - t.Errorf(`somehow, we screwed up addition. (want %d, got %d)`, want, got) 42 - } 43 - }
+1 -1
public/style/header.css
··· 153 153 flex-direction: column; 154 154 gap: 1rem; 155 155 border-bottom: 1px solid #888; 156 - background: var(--background); 156 + background-color: var(--background); 157 157 display: none; 158 158 } 159 159
+2 -2
public/style/main.css
··· 16 16 body { 17 17 margin: 0; 18 18 padding: 0; 19 - background: var(--background); 19 + background-color: var(--background); 20 20 color: var(--on-background); 21 21 font-family: "Monaspace Argon", monospace; 22 22 font-size: 18px; ··· 150 150 @keyframes list-item-fadein { 151 151 from { 152 152 opacity: 1; 153 - background: var(--links); 153 + background-color: var(--links); 154 154 } 155 155 156 156 to {
+1 -1
public/style/music-gateway.css
··· 30 30 background-size: cover; 31 31 background-position: center; 32 32 filter: blur(25px) saturate(25%) brightness(0.5); 33 - -webkit-filter: blur(25px) saturate(25%) brightness(0.5);; 33 + -webkit-filter: blur(25px) saturate(25%) brightness(0.5); 34 34 animation: background-init .5s forwards,background-loop 30s ease-in-out infinite 35 35 } 36 36
+9 -9
templates/html/music-gateway.html
··· 83 83 84 84 {{else if .IsSingle}} 85 85 86 - {{$Track := index .Tracks 0}} 87 - {{if $Track.Description}} 88 - <p id="description">{{$Track.Description}}</p> 86 + {{index .Tracks 0}} 87 + {{if .Description}} 88 + <p id="description">{{.Description}}</p> 89 89 {{end}} 90 90 91 91 {{end}} ··· 132 132 {{else if .Tracks}} 133 133 <div id="tracks"> 134 134 <h2>TRACKS</h2> 135 - {{range $i, $track := .Tracks}} 135 + {{range .Tracks}} 136 136 <details> 137 - <summary class="album-track-title">{{$track.Add $i 1}}. {{$track.Title}}</summary> 137 + <summary class="album-track-title">{{.Number}}. {{.Title}}</summary> 138 138 139 - {{if $track.Description}} 139 + {{if .Description}} 140 140 <p class="album-track-subheading">DESCRIPTION</p> 141 - {{$track.Description}} 141 + {{.Description}} 142 142 {{end}} 143 143 144 144 <p class="album-track-subheading">LYRICS</p> 145 - {{if $track.Lyrics}} 146 - {{$track.GetLyricsHTML}} 145 + {{if .Lyrics}} 146 + {{.GetLyricsHTML}} 147 147 {{else}} 148 148 <span class="empty">No lyrics.</span> 149 149 {{end}}
+7 -7
view/music.go
··· 78 78 } 79 79 } 80 80 81 - response := *release 82 - 83 - if release.IsReleased() || privileged { 84 - response.Tracks = release.Tracks 85 - response.Credits = release.Credits 86 - response.Links = release.Links 81 + if !release.IsReleased() && !privileged { 82 + release.Tracks = nil 83 + release.Credits = nil 84 + release.Links = nil 87 85 } 88 86 89 - err := templates.MusicGatewayTemplate.Execute(w, response) 87 + for i, track := range release.Tracks { track.Number = i + 1 } 88 + 89 + err := templates.MusicGatewayTemplate.Execute(w, release) 90 90 91 91 if err != nil { 92 92 fmt.Printf("Error rendering music gateway for %s: %s\n", release.ID, err)