Alter 9 files

Add _colours.scss
Update main.css
Update main.css.map
Update main.scss
Update index.templ
Update index_templ.go
Update recentlyListened.go
Update basePage.templ
Update basePage_templ.go
This commit is contained in:
akp 2025-07-23 23:44:48 +01:00
parent 6a875894a4
commit 0bce16dc09
9 changed files with 678 additions and 133 deletions

View file

@ -0,0 +1,244 @@
$red-50: oklch(0.971 0.013 17.38);
$red-100: oklch(0.936 0.032 17.717);
$red-200: oklch(0.885 0.062 18.334);
$red-300: oklch(0.808 0.114 19.571);
$red-400: oklch(0.704 0.191 22.216);
$red-500: oklch(0.637 0.237 25.331);
$red-600: oklch(0.577 0.245 27.325);
$red-700: oklch(0.505 0.213 27.518);
$red-800: oklch(0.444 0.177 26.899);
$red-900: oklch(0.396 0.141 25.723);
$red-950: oklch(0.258 0.092 26.042);
$orange-50: oklch(0.98 0.016 73.684);
$orange-100: oklch(0.954 0.038 75.164);
$orange-200: oklch(0.901 0.076 70.697);
$orange-300: oklch(0.837 0.128 66.29);
$orange-400: oklch(0.75 0.183 55.934);
$orange-500: oklch(0.705 0.213 47.604);
$orange-600: oklch(0.646 0.222 41.116);
$orange-700: oklch(0.553 0.195 38.402);
$orange-800: oklch(0.47 0.157 37.304);
$orange-900: oklch(0.408 0.123 38.172);
$orange-950: oklch(0.266 0.079 36.259);
$amber-50: oklch(0.987 0.022 95.277);
$amber-100: oklch(0.962 0.059 95.617);
$amber-200: oklch(0.924 0.12 95.746);
$amber-300: oklch(0.879 0.169 91.605);
$amber-400: oklch(0.828 0.189 84.429);
$amber-500: oklch(0.769 0.188 70.08);
$amber-600: oklch(0.666 0.179 58.318);
$amber-700: oklch(0.555 0.163 48.998);
$amber-800: oklch(0.473 0.137 46.201);
$amber-900: oklch(0.414 0.112 45.904);
$amber-950: oklch(0.279 0.077 45.635);
$yellow-50: oklch(0.987 0.026 102.212);
$yellow-100: oklch(0.973 0.071 103.193);
$yellow-200: oklch(0.945 0.129 101.54);
$yellow-300: oklch(0.905 0.182 98.111);
$yellow-400: oklch(0.852 0.199 91.936);
$yellow-500: oklch(0.795 0.184 86.047);
$yellow-600: oklch(0.681 0.162 75.834);
$yellow-700: oklch(0.554 0.135 66.442);
$yellow-800: oklch(0.476 0.114 61.907);
$yellow-900: oklch(0.421 0.095 57.708);
$yellow-950: oklch(0.286 0.066 53.813);
$lime-50: oklch(0.986 0.031 120.757);
$lime-100: oklch(0.967 0.067 122.328);
$lime-200: oklch(0.938 0.127 124.321);
$lime-300: oklch(0.897 0.196 126.665);
$lime-400: oklch(0.841 0.238 128.85);
$lime-500: oklch(0.768 0.233 130.85);
$lime-600: oklch(0.648 0.2 131.684);
$lime-700: oklch(0.532 0.157 131.589);
$lime-800: oklch(0.453 0.124 130.933);
$lime-900: oklch(0.405 0.101 131.063);
$lime-950: oklch(0.274 0.072 132.109);
$green-50: oklch(0.982 0.018 155.826);
$green-100: oklch(0.962 0.044 156.743);
$green-200: oklch(0.925 0.084 155.995);
$green-300: oklch(0.871 0.15 154.449);
$green-400: oklch(0.792 0.209 151.711);
$green-500: oklch(0.723 0.219 149.579);
$green-600: oklch(0.627 0.194 149.214);
$green-700: oklch(0.527 0.154 150.069);
$green-800: oklch(0.448 0.119 151.328);
$green-900: oklch(0.393 0.095 152.535);
$green-950: oklch(0.266 0.065 152.934);
$emerald-50: oklch(0.979 0.021 166.113);
$emerald-100: oklch(0.95 0.052 163.051);
$emerald-200: oklch(0.905 0.093 164.15);
$emerald-300: oklch(0.845 0.143 164.978);
$emerald-400: oklch(0.765 0.177 163.223);
$emerald-500: oklch(0.696 0.17 162.48);
$emerald-600: oklch(0.596 0.145 163.225);
$emerald-700: oklch(0.508 0.118 165.612);
$emerald-800: oklch(0.432 0.095 166.913);
$emerald-900: oklch(0.378 0.077 168.94);
$emerald-950: oklch(0.262 0.051 172.552);
$teal-50: oklch(0.984 0.014 180.72);
$teal-100: oklch(0.953 0.051 180.801);
$teal-200: oklch(0.91 0.096 180.426);
$teal-300: oklch(0.855 0.138 181.071);
$teal-400: oklch(0.777 0.152 181.912);
$teal-500: oklch(0.704 0.14 182.503);
$teal-600: oklch(0.6 0.118 184.704);
$teal-700: oklch(0.511 0.096 186.391);
$teal-800: oklch(0.437 0.078 188.216);
$teal-900: oklch(0.386 0.063 188.416);
$teal-950: oklch(0.277 0.046 192.524);
$cyan-50: oklch(0.984 0.019 200.873);
$cyan-100: oklch(0.956 0.045 203.388);
$cyan-200: oklch(0.917 0.08 205.041);
$cyan-300: oklch(0.865 0.127 207.078);
$cyan-400: oklch(0.789 0.154 211.53);
$cyan-500: oklch(0.715 0.143 215.221);
$cyan-600: oklch(0.609 0.126 221.723);
$cyan-700: oklch(0.52 0.105 223.128);
$cyan-800: oklch(0.45 0.085 224.283);
$cyan-900: oklch(0.398 0.07 227.392);
$cyan-950: oklch(0.302 0.056 229.695);
$sky-50: oklch(0.977 0.013 236.62);
$sky-100: oklch(0.951 0.026 236.824);
$sky-200: oklch(0.901 0.058 230.902);
$sky-300: oklch(0.828 0.111 230.318);
$sky-400: oklch(0.746 0.16 232.661);
$sky-500: oklch(0.685 0.169 237.323);
$sky-600: oklch(0.588 0.158 241.966);
$sky-700: oklch(0.5 0.134 242.749);
$sky-800: oklch(0.443 0.11 240.79);
$sky-900: oklch(0.391 0.09 240.876);
$sky-950: oklch(0.293 0.066 243.157);
$blue-50: oklch(0.97 0.014 254.604);
$blue-100: oklch(0.932 0.032 255.585);
$blue-200: oklch(0.882 0.059 254.128);
$blue-300: oklch(0.809 0.105 251.813);
$blue-400: oklch(0.707 0.165 254.624);
$blue-500: oklch(0.623 0.214 259.815);
$blue-600: oklch(0.546 0.245 262.881);
$blue-700: oklch(0.488 0.243 264.376);
$blue-800: oklch(0.424 0.199 265.638);
$blue-900: oklch(0.379 0.146 265.522);
$blue-950: oklch(0.282 0.091 267.935);
$indigo-50: oklch(0.962 0.018 272.314);
$indigo-100: oklch(0.93 0.034 272.788);
$indigo-200: oklch(0.87 0.065 274.039);
$indigo-300: oklch(0.785 0.115 274.713);
$indigo-400: oklch(0.673 0.182 276.935);
$indigo-500: oklch(0.585 0.233 277.117);
$indigo-600: oklch(0.511 0.262 276.966);
$indigo-700: oklch(0.457 0.24 277.023);
$indigo-800: oklch(0.398 0.195 277.366);
$indigo-900: oklch(0.359 0.144 278.697);
$indigo-950: oklch(0.257 0.09 281.288);
$violet-50: oklch(0.969 0.016 293.756);
$violet-100: oklch(0.943 0.029 294.588);
$violet-200: oklch(0.894 0.057 293.283);
$violet-300: oklch(0.811 0.111 293.571);
$violet-400: oklch(0.702 0.183 293.541);
$violet-500: oklch(0.606 0.25 292.717);
$violet-600: oklch(0.541 0.281 293.009);
$violet-700: oklch(0.491 0.27 292.581);
$violet-800: oklch(0.432 0.232 292.759);
$violet-900: oklch(0.38 0.189 293.745);
$violet-950: oklch(0.283 0.141 291.089);
$purple-50: oklch(0.977 0.014 308.299);
$purple-100: oklch(0.946 0.033 307.174);
$purple-200: oklch(0.902 0.063 306.703);
$purple-300: oklch(0.827 0.119 306.383);
$purple-400: oklch(0.714 0.203 305.504);
$purple-500: oklch(0.627 0.265 303.9);
$purple-600: oklch(0.558 0.288 302.321);
$purple-700: oklch(0.496 0.265 301.924);
$purple-800: oklch(0.438 0.218 303.724);
$purple-900: oklch(0.381 0.176 304.987);
$purple-950: oklch(0.291 0.149 302.717);
$fuchsia-50: oklch(0.977 0.017 320.058);
$fuchsia-100: oklch(0.952 0.037 318.852);
$fuchsia-200: oklch(0.903 0.076 319.62);
$fuchsia-300: oklch(0.833 0.145 321.434);
$fuchsia-400: oklch(0.74 0.238 322.16);
$fuchsia-500: oklch(0.667 0.295 322.15);
$fuchsia-600: oklch(0.591 0.293 322.896);
$fuchsia-700: oklch(0.518 0.253 323.949);
$fuchsia-800: oklch(0.452 0.211 324.591);
$fuchsia-900: oklch(0.401 0.17 325.612);
$fuchsia-950: oklch(0.293 0.136 325.661);
$pink-50: oklch(0.971 0.014 343.198);
$pink-100: oklch(0.948 0.028 342.258);
$pink-200: oklch(0.899 0.061 343.231);
$pink-300: oklch(0.823 0.12 346.018);
$pink-400: oklch(0.718 0.202 349.761);
$pink-500: oklch(0.656 0.241 354.308);
$pink-600: oklch(0.592 0.249 0.584);
$pink-700: oklch(0.525 0.223 3.958);
$pink-800: oklch(0.459 0.187 3.815);
$pink-900: oklch(0.408 0.153 2.432);
$pink-950: oklch(0.284 0.109 3.907);
$rose-50: oklch(0.969 0.015 12.422);
$rose-100: oklch(0.941 0.03 12.58);
$rose-200: oklch(0.892 0.058 10.001);
$rose-300: oklch(0.81 0.117 11.638);
$rose-400: oklch(0.712 0.194 13.428);
$rose-500: oklch(0.645 0.246 16.439);
$rose-600: oklch(0.586 0.253 17.585);
$rose-700: oklch(0.514 0.222 16.935);
$rose-800: oklch(0.455 0.188 13.697);
$rose-900: oklch(0.41 0.159 10.272);
$rose-950: oklch(0.271 0.105 12.094);
$slate-50: oklch(0.984 0.003 247.858);
$slate-100: oklch(0.968 0.007 247.896);
$slate-200: oklch(0.929 0.013 255.508);
$slate-300: oklch(0.869 0.022 252.894);
$slate-400: oklch(0.704 0.04 256.788);
$slate-500: oklch(0.554 0.046 257.417);
$slate-600: oklch(0.446 0.043 257.281);
$slate-700: oklch(0.372 0.044 257.287);
$slate-800: oklch(0.279 0.041 260.031);
$slate-900: oklch(0.208 0.042 265.755);
$slate-950: oklch(0.129 0.042 264.695);
$gray-50: oklch(0.985 0.002 247.839);
$gray-100: oklch(0.967 0.003 264.542);
$gray-200: oklch(0.928 0.006 264.531);
$gray-300: oklch(0.872 0.01 258.338);
$gray-400: oklch(0.707 0.022 261.325);
$gray-500: oklch(0.551 0.027 264.364);
$gray-600: oklch(0.446 0.03 256.802);
$gray-700: oklch(0.373 0.034 259.733);
$gray-800: oklch(0.278 0.033 256.848);
$gray-900: oklch(0.21 0.034 264.665);
$gray-950: oklch(0.13 0.028 261.692);
$zinc-50: oklch(0.985 0 0);
$zinc-100: oklch(0.967 0.001 286.375);
$zinc-200: oklch(0.92 0.004 286.32);
$zinc-300: oklch(0.871 0.006 286.286);
$zinc-400: oklch(0.705 0.015 286.067);
$zinc-500: oklch(0.552 0.016 285.938);
$zinc-600: oklch(0.442 0.017 285.786);
$zinc-700: oklch(0.37 0.013 285.805);
$zinc-800: oklch(0.274 0.006 286.033);
$zinc-900: oklch(0.21 0.006 285.885);
$zinc-950: oklch(0.141 0.005 285.823);
$neutral-50: oklch(0.985 0 0);
$neutral-100: oklch(0.97 0 0);
$neutral-200: oklch(0.922 0 0);
$neutral-300: oklch(0.87 0 0);
$neutral-400: oklch(0.708 0 0);
$neutral-500: oklch(0.556 0 0);
$neutral-600: oklch(0.439 0 0);
$neutral-700: oklch(0.371 0 0);
$neutral-800: oklch(0.269 0 0);
$neutral-900: oklch(0.205 0 0);
$neutral-950: oklch(0.145 0 0);
$stone-50: oklch(0.985 0.001 106.423);
$stone-100: oklch(0.97 0.001 106.424);
$stone-200: oklch(0.923 0.003 48.717);
$stone-300: oklch(0.869 0.005 56.366);
$stone-400: oklch(0.709 0.01 56.259);
$stone-500: oklch(0.553 0.013 58.071);
$stone-600: oklch(0.444 0.011 73.639);
$stone-700: oklch(0.374 0.01 67.558);
$stone-800: oklch(0.268 0.007 34.298);
$stone-900: oklch(0.216 0.006 56.043);
$stone-950: oklch(0.147 0.004 49.25);
$black: #000;
$white: #fff;

View file

@ -2,7 +2,7 @@
html, body {
width: 100%;
min-height: 100%;
background-color: lightblue;
background-color: oklch(88.2% 0.059 254.128deg);
font-family: sans-serif;
}
@ -24,7 +24,7 @@ ul.track-list {
}
ul.track-list li {
display: grid;
grid-template-columns: 1fr repeat(3, 3fr);
grid-template-columns: 1fr 1fr repeat(3, 4fr);
align-items: center;
gap: 0.5em;
padding: 0.5em;
@ -38,13 +38,33 @@ ul.track-list li > div:not(.display-none):has(img) img {
height: 3em;
}
ul.track-list li:nth-of-type(odd) {
background-color: lightgray;
background-color: oklch(92.8% 0.006 264.531deg);
}
ul.track-list li:has(> input[type=checkbox]:checked) {
background-color: yellow;
background-color: oklch(87.1% 0.15 154.449deg);
}
ul.track-list li:has(> input[type=checkbox]:checked):nth-of-type(odd) {
background-color: yellowgreen;
background-color: oklch(92.5% 0.084 155.995deg);
}
.loader {
width: 40px;
height: 40px;
border: 4px solid oklch(37.3% 0.034 259.733deg);
border-bottom-color: transparent;
border-radius: 50%;
display: inline-block;
box-sizing: border-box;
animation: rotation 0.8s linear infinite;
}
@keyframes rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/*# sourceMappingURL=main.css.map */

View file

@ -1 +1 @@
{"version":3,"sourceRoot":"","sources":["main.scss"],"names":[],"mappings":"AAAA;AAEA;EACI;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;;AAEA;EACI;EACA;EAIA;EACA;EACA;;AAEA;EAII;EACA;EACA;;AALA;EACI;;AAOR;EACI;;AAGJ;EACI;;AACA;EACI","file":"main.css"}
{"version":3,"sourceRoot":"","sources":["main.scss","_colours.scss"],"names":[],"mappings":"AAAA;AAGA;EACI;EACA;EACA,kBC0GO;EDzGP;;;AAGJ;EACI;EACA;EACA;EACA;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;;AAEA;EACI;EACA;EAEA;EACA;EACA;;AAEA;EAII;EACA;EACA;;AALA;EACI;;AAOR;EACI,kBC4JD;;ADzJH;EACI,kBCUA;;ADTA;EACI,kBCOJ;;;ADDZ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGJ;EACI;IACI;;EAEJ;IACI","file":"main.css"}

View file

@ -1,9 +1,10 @@
/* hello */
@import "_colours";
html, body {
width: 100%;
min-height: 100%;
background-color: lightblue;
background-color: $blue-200;
font-family: sans-serif;
}
@ -25,10 +26,8 @@ ul.track-list {
li {
display: grid;
grid-template-columns: 1fr repeat(3, 3fr);
grid-template-columns: 1fr 1fr repeat(3, 4fr);
//display: flex;
//flex-direction: row;
align-items: center;
gap: 0.5em;
padding: 0.5em;
@ -43,14 +42,34 @@ ul.track-list {
}
&:nth-of-type(odd) {
background-color: lightgray;
background-color: $gray-200;
}
&:has(>input[type=checkbox]:checked) {
background-color: yellow;
background-color: $green-300;
&:nth-of-type(odd) {
background-color: yellowgreen;
background-color: $green-200;
}
}
}
}
.loader {
width: 40px;
height: 40px;
border: 4px solid $gray-700;
border-bottom-color: transparent;
border-radius: 50%;
display: inline-block;
box-sizing: border-box;
animation: rotation 0.8s linear infinite;
}
@keyframes rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

View file

@ -3,6 +3,7 @@ package recentlyListened
import (
"git.akpain.net/codemicro/backseat-music/backseat/httpUtil"
"github.com/zmb3/spotify/v2"
"strconv"
)
func formatArtistNames(artists []spotify.SimpleArtist) string {
@ -30,27 +31,59 @@ func getSmallestImage(images []spotify.Image) spotify.Image {
return *smallest
}
templ indexPage(recentlyListenedTracks []spotify.RecentlyPlayedItem) {
templ indexPage() {
@httpUtil.BasePage("Recently Listened"){
<ul class="track-list">
for _, record := range recentlyListenedTracks {
<li class="track-row">
<input class="display-none" name={ record.Track.ID } type="checkbox" />
{{ smallestImage := getSmallestImage(record.Track.Album.Images) }}
<div><img src={ smallestImage.URL } loading="lazy" alt={ "album art for " + record.Track.Name } /></div>
<div>{ record.Track.Name }</div>
<div>{ formatArtistNames(record.Track.Artists) }</div>
<div>{ record.PlayedAt.String() }</div>
</li>
}
</ul>
<h1>Recently Listened Tracks</h1>
<script>
const onTrackClick = (elem) => {
let checkbox = elem.querySelector("input[type=checkbox]")
checkbox.checked = !checkbox.checked
const collectSelectedTracks = () => {
return Array.from(document.querySelectorAll('#track-list .track-selection-checkbox:checked')).map((elem) => (elem.dataset.trackid)).join(',')
}
document.querySelectorAll("li.track-row").forEach((elem) => { elem.addEventListener("click", () => { onTrackClick(elem) }) });
</script>
<form hx-put="/recentlyListened/createPlaylist" hx-target="main" hx-select="main" hx-disinherit="*" hx-vars="selected:collectSelectedTracks()">
<input type="submit" />
<ul id="track-list" class="track-list" hx-get="/recentlyListened/tracks" hx-trigger="load">
<div style="width: 100%; display: flex; flex-direction: column; gap: 0.5em; align-items: center;">
@httpUtil.LoadingSpinner()
<span>Loading...</span>
</div>
</ul>
</form>
<div id="load-more-button"></div>
}
}
templ trackList(recentlyListenedTracks []spotify.RecentlyPlayedItem) {
for _, record := range recentlyListenedTracks {
<li class="track-row">
<input class="track-selection-checkbox" data-trackid={ record.Track.ID } type="checkbox" style="height: 1.25em;"/>
{{ smallestImage := getSmallestImage(record.Track.Album.Images) }}
<div><img src={ smallestImage.URL } loading="lazy" alt={ "album art for " + record.Track.Name } /></div>
<div>{ record.Track.Name }</div>
<div>{ formatArtistNames(record.Track.Artists) }</div>
<div>{ record.PlayedAt.String() }</div>
</li>
}
<div id="load-more-button" hx-swap-oob="innerHTML">
if len(recentlyListenedTracks) != 0 {
<div id="load-more-spinner" class="htmx-indicator" style="padding-bottom: 0.5em">
@httpUtil.LoadingSpinner()
</div>
<button hx-get={ "/recentlyListened/tracks?before=" + strconv.FormatInt(recentlyListenedTracks[len(recentlyListenedTracks)-1].PlayedAt.UnixMilli(), 10) } hx-target="#track-list" hx-swap="beforeend" hx-indicator="#load-more-spinner" hx-disabled-elt="this">Load more</button>
}
</div>
}
templ createPlaylistPage(trackIDs []string) {
@httpUtil.BasePage("Recently Listened"){
<h1>Create Playlist</h1>
<ul>
for _, id := range trackIDs {
<li>{ id }</li>
}
</ul>
}
}

View file

@ -11,6 +11,7 @@ import templruntime "github.com/a-h/templ/runtime"
import (
"git.akpain.net/codemicro/backseat-music/backseat/httpUtil"
"github.com/zmb3/spotify/v2"
"strconv"
)
func formatArtistNames(artists []spotify.SimpleArtist) string {
@ -38,7 +39,7 @@ func getSmallestImage(images []spotify.Image) spotify.Image {
return *smallest
}
func indexPage(recentlyListenedTracks []spotify.RecentlyPlayedItem) templ.Component {
func indexPage() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@ -71,100 +72,15 @@ func indexPage(recentlyListenedTracks []spotify.RecentlyPlayedItem) templ.Compon
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<ul class=\"track-list\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<h1>Recently Listened Tracks</h1><script>\n const collectSelectedTracks = () => {\n return Array.from(document.querySelectorAll('#track-list .track-selection-checkbox:checked')).map((elem) => (elem.dataset.trackid)).join(',')\n }\n </script> <form hx-put=\"/recentlyListened/createPlaylist\" hx-target=\"main\" hx-select=\"main\" hx-disinherit=\"*\" hx-vars=\"selected:collectSelectedTracks()\"><input type=\"submit\"><ul id=\"track-list\" class=\"track-list\" hx-get=\"/recentlyListened/tracks\" hx-trigger=\"load\"><div style=\"width: 100%; display: flex; flex-direction: column; gap: 0.5em; align-items: center;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, record := range recentlyListenedTracks {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<li class=\"track-row\"><input class=\"display-none\" name=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(record.Track.ID)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `backseat/components/recentlyListened/index.templ`, Line: 38, Col: 70}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\" type=\"checkbox\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
smallestImage := getSmallestImage(record.Track.Album.Images)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div><img src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(smallestImage.URL)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `backseat/components/recentlyListened/index.templ`, Line: 40, Col: 53}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\" loading=\"lazy\" alt=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs("album art for " + record.Track.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `backseat/components/recentlyListened/index.templ`, Line: 40, Col: 113}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\"></div><div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(record.Track.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `backseat/components/recentlyListened/index.templ`, Line: 41, Col: 44}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</div><div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(formatArtistNames(record.Track.Artists))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `backseat/components/recentlyListened/index.templ`, Line: 42, Col: 66}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</div><div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(record.PlayedAt.String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `backseat/components/recentlyListened/index.templ`, Line: 43, Col: 51}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</div></li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = httpUtil.LoadingSpinner().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</ul><script>\n const onTrackClick = (elem) => {\n let checkbox = elem.querySelector(\"input[type=checkbox]\")\n checkbox.checked = !checkbox.checked\n }\n document.querySelectorAll(\"li.track-row\").forEach((elem) => { elem.addEventListener(\"click\", () => { onTrackClick(elem) }) });\n </script>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<span>Loading...</span></div></ul></form><div id=\"load-more-button\"></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -178,4 +94,223 @@ func indexPage(recentlyListenedTracks []spotify.RecentlyPlayedItem) templ.Compon
})
}
func trackList(recentlyListenedTracks []spotify.RecentlyPlayedItem) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
if templ_7745c5c3_Var3 == nil {
templ_7745c5c3_Var3 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
for _, record := range recentlyListenedTracks {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<li class=\"track-row\"><input class=\"track-selection-checkbox\" data-trackid=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(record.Track.ID)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `backseat/components/recentlyListened/index.templ`, Line: 60, Col: 82}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\" type=\"checkbox\" style=\"height: 1.25em;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
smallestImage := getSmallestImage(record.Track.Album.Images)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<div><img src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(smallestImage.URL)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `backseat/components/recentlyListened/index.templ`, Line: 62, Col: 45}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\" loading=\"lazy\" alt=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs("album art for " + record.Track.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `backseat/components/recentlyListened/index.templ`, Line: 62, Col: 105}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\"></div><div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(record.Track.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `backseat/components/recentlyListened/index.templ`, Line: 63, Col: 36}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</div><div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(formatArtistNames(record.Track.Artists))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `backseat/components/recentlyListened/index.templ`, Line: 64, Col: 58}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</div><div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(record.PlayedAt.String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `backseat/components/recentlyListened/index.templ`, Line: 65, Col: 43}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</div></li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<div id=\"load-more-button\" hx-swap-oob=\"innerHTML\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(recentlyListenedTracks) != 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<div id=\"load-more-spinner\" class=\"htmx-indicator\" style=\"padding-bottom: 0.5em\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = httpUtil.LoadingSpinner().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</div><button hx-get=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs("/recentlyListened/tracks?before=" + strconv.FormatInt(recentlyListenedTracks[len(recentlyListenedTracks)-1].PlayedAt.UnixMilli(), 10))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `backseat/components/recentlyListened/index.templ`, Line: 74, Col: 163}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\" hx-target=\"#track-list\" hx-swap=\"beforeend\" hx-indicator=\"#load-more-spinner\" hx-disabled-elt=\"this\">Load more</button>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func createPlaylistPage(trackIDs []string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var11 := templ.GetChildren(ctx)
if templ_7745c5c3_Var11 == nil {
templ_7745c5c3_Var11 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var12 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<h1>Create Playlist</h1><ul>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, id := range trackIDs {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(id)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `backseat/components/recentlyListened/index.templ`, Line: 85, Col: 24}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</ul>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = httpUtil.BasePage("Recently Listened").Render(templ.WithChildren(ctx, templ_7745c5c3_Var12), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View file

@ -7,6 +7,8 @@ import (
"git.akpain.net/codemicro/backseat-music/backseat/spotifyUtil"
"github.com/zmb3/spotify/v2"
"net/http"
"strconv"
"strings"
)
type RecentlyListened struct {
@ -23,6 +25,9 @@ func New(data *data.Store) *RecentlyListened {
}
t.mux.Handle("GET /recentlyListened/{$}", httpUtil.HandleErrors(t.index))
t.mux.Handle("POST /recentlyListened/{$}", httpUtil.HandleErrors(t.index))
t.mux.Handle("GET /recentlyListened/tracks", httpUtil.HandleErrors(t.tracks))
t.mux.Handle("PUT /recentlyListened/createPlaylist", httpUtil.HandleErrors(t.createPlaylist))
return t
}
@ -37,19 +42,64 @@ func (r *RecentlyListened) index(rw http.ResponseWriter, rq *http.Request) error
return nil
}
spotifyClient, err := spotifyUtil.NewSpotityClient(rq.Context(), r.data)
if err != nil {
return fmt.Errorf("get Spotify client: %w", err)
}
recentlyPlayed, err := spotifyClient.PlayerRecentlyPlayedOpt(rq.Context(), &spotify.RecentlyPlayedOptions{Limit: 50})
if err != nil {
return fmt.Errorf("get recently played tracks: %w", err)
}
rw.Header().Set("Content-Type", "text/html")
if err := indexPage(recentlyPlayed).Render(rq.Context(), rw); err != nil {
if err := indexPage().Render(rq.Context(), rw); err != nil {
return fmt.Errorf("render template: %w", err)
}
return nil
}
func (r *RecentlyListened) tracks(rw http.ResponseWriter, rq *http.Request) error {
spotifyClient, err := spotifyUtil.NewSpotityClient(rq.Context(), r.data)
if err != nil {
return fmt.Errorf("get Spotify client: %w", err)
}
recentlyPlayedOpts := &spotify.RecentlyPlayedOptions{Limit: 20}
if beforeStr := rq.URL.Query().Get("before"); beforeStr != "" {
before, err := strconv.Atoi(beforeStr)
if err == nil {
recentlyPlayedOpts.BeforeEpochMs = int64(before)
}
}
recentlyPlayed, err := spotifyClient.PlayerRecentlyPlayedOpt(rq.Context(), recentlyPlayedOpts)
if err != nil {
return fmt.Errorf("get recently played tracks: %w", err)
}
{
n := 0
seen := make(map[spotify.ID]struct{})
for _, track := range recentlyPlayed {
if _, found := seen[track.Track.ID]; !found {
recentlyPlayed[n] = track
n += 1
seen[track.Track.ID] = struct{}{}
}
}
recentlyPlayed = recentlyPlayed[:n]
}
if err := trackList(recentlyPlayed).Render(rq.Context(), rw); err != nil {
return fmt.Errorf("render template: %w", err)
}
return nil
}
func (r *RecentlyListened) createPlaylist(rw http.ResponseWriter, rq *http.Request) error {
if err := rq.ParseForm(); err != nil {
rw.WriteHeader(http.StatusBadRequest)
return nil
}
trackIDs := strings.Split(rq.FormValue("selected"), ",")
if err := createPlaylistPage(trackIDs).Render(rq.Context(), rw); err != nil {
return fmt.Errorf("render template: %w", err)
}

View file

@ -7,6 +7,17 @@ templ BasePage(title string) {
<meta charset="UTF-8" />
<title>{ title } - Backseat Music</title>
<link rel="stylesheet" href="/assets/main.css" />
<meta name="htmx-config" content='{"includeIndicatorStyles":false}'>
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.6/dist/htmx.js"></script>
<style>
.htmx-indicator {
display: none;
}
.htmx-request .htmx-indicator, .htmx-request.htmx-indicator {
display: block;
}
</style>
</head>
<body>
<main>
@ -14,4 +25,8 @@ templ BasePage(title string) {
</main>
</body>
</html>
}
templ LoadingSpinner() {
<span class="loader"></span>
}

View file

@ -42,7 +42,7 @@ func BasePage(title string) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " - Backseat Music</title><link rel=\"stylesheet\" href=\"/assets/main.css\"></head><body><main>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " - Backseat Music</title><link rel=\"stylesheet\" href=\"/assets/main.css\"><meta name=\"htmx-config\" content='{\"includeIndicatorStyles\":false}'><script src=\"https://cdn.jsdelivr.net/npm/htmx.org@2.0.6/dist/htmx.js\"></script><style>\n .htmx-indicator {\n display: none;\n }\n\n .htmx-request .htmx-indicator, .htmx-request.htmx-indicator {\n display: block;\n }\n </style></head><body><main>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -58,4 +58,33 @@ func BasePage(title string) templ.Component {
})
}
func LoadingSpinner() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
if templ_7745c5c3_Var3 == nil {
templ_7745c5c3_Var3 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<span class=\"loader\"></span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate