Implement "now playing" card
This commit is contained in:
parent
96a514d3c6
commit
adfddfc9cf
3 changed files with 182 additions and 63 deletions
36
src/main.rs
36
src/main.rs
|
@ -49,6 +49,28 @@ async fn get_queue(_req: tide::Request<()>) -> tide::Result {
|
||||||
Ok(template.into())
|
Ok(template.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "current.html")]
|
||||||
|
struct CurrentTemplate {
|
||||||
|
song: Option<mpdrs::Song>,
|
||||||
|
name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_current(_req: tide::Request<()>) -> tide::Result {
|
||||||
|
let mut mpd = mpd::connect()?;
|
||||||
|
let song = mpd.currentsong()?;
|
||||||
|
|
||||||
|
let mut template = CurrentTemplate { song: song.clone(), name: None };
|
||||||
|
|
||||||
|
if let Some(song) = song {
|
||||||
|
let name = song.title.unwrap_or(song.file.clone()).to_string();
|
||||||
|
template.name = Some(name);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(template.into())
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct PostQueueQuery {
|
struct PostQueueQuery {
|
||||||
path: String,
|
path: String,
|
||||||
|
@ -80,6 +102,18 @@ async fn post_next(_req: tide::Request<()>) -> tide::Result {
|
||||||
Ok("".into())
|
Ok("".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_art(req: tide::Request<()>) -> tide::Result {
|
||||||
|
let query: IndexQuery = req.query()?;
|
||||||
|
let resp = if let Ok(art) = mpd::connect()?.albumart(&query.path) {
|
||||||
|
tide::Response::builder(tide::StatusCode::Ok)
|
||||||
|
.body(art)
|
||||||
|
.header("cache-control", "max-age=3600")
|
||||||
|
} else {
|
||||||
|
tide::Response::builder(tide::StatusCode::NotFound)
|
||||||
|
};
|
||||||
|
Ok(resp.into())
|
||||||
|
}
|
||||||
|
|
||||||
async fn sse(_req: tide::Request<()>, sender: tide::sse::Sender) -> tide::Result<()> {
|
async fn sse(_req: tide::Request<()>, sender: tide::sse::Sender) -> tide::Result<()> {
|
||||||
// Needs to be async and all async mpd libraries suck
|
// Needs to be async and all async mpd libraries suck
|
||||||
let mut stream = TcpStream::connect(mpd::HOST).await?;
|
let mut stream = TcpStream::connect(mpd::HOST).await?;
|
||||||
|
@ -120,6 +154,8 @@ async fn main() -> tide::Result<()> {
|
||||||
|
|
||||||
app.at("/").get(index);
|
app.at("/").get(index);
|
||||||
app.at("/queue").get(get_queue);
|
app.at("/queue").get(get_queue);
|
||||||
|
app.at("/current").get(get_current);
|
||||||
|
app.at("/art").get(get_art);
|
||||||
|
|
||||||
app.at("/sse").get(tide::sse::endpoint(sse));
|
app.at("/sse").get(tide::sse::endpoint(sse));
|
||||||
|
|
||||||
|
|
37
templates/current.html
Normal file
37
templates/current.html
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
{# #}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<div class="nowplaying">
|
||||||
|
<div class="current">
|
||||||
|
{% if let Some(song) = song %}
|
||||||
|
<img class="albumart" src="/art?path={{ song.file }}">
|
||||||
|
|
||||||
|
<div class="metadata">
|
||||||
|
{% if let Some(name) = name %}
|
||||||
|
<div class="song__name">{{ name }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if let Some(artist) = song.artist %}
|
||||||
|
<div>{{ artist }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<span
|
||||||
|
hx-post="/previous" hx-swap="none"
|
||||||
|
class="control material-symbols-outlined" role="button"
|
||||||
|
>skip_previous</span>
|
||||||
|
<span
|
||||||
|
hx-post="/play" hx-swap="none"
|
||||||
|
class="control material-symbols-outlined" role="button"
|
||||||
|
>play_arrow</span>
|
||||||
|
<span
|
||||||
|
hx-post="/pause" hx-swap="none"
|
||||||
|
class="control material-symbols-outlined" role="button"
|
||||||
|
>pause</span>
|
||||||
|
<span
|
||||||
|
hx-post="/next" hx-swap="none"
|
||||||
|
class="control material-symbols-outlined" role="button"
|
||||||
|
>skip_next</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -13,6 +13,16 @@
|
||||||
font-family: sans;
|
font-family: sans;
|
||||||
background-color: #112;
|
background-color: #112;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
display: flex;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body > div {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.browser {
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
@ -24,12 +34,20 @@
|
||||||
ul {
|
ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.queue {
|
||||||
|
margin-top: 1.0rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.queue li {
|
ul.queue li {
|
||||||
padding: .5rem 0;
|
padding: 1.0rem 0.75rem;
|
||||||
|
border-radius: .25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.queue li.playing {
|
ul.queue li.playing {
|
||||||
|
background-color: #334;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,6 +78,12 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ul.dir li img {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
ul.dir li:hover {
|
ul.dir li:hover {
|
||||||
background-color: #334;
|
background-color: #334;
|
||||||
}
|
}
|
||||||
|
@ -69,39 +93,55 @@
|
||||||
width: 24px;
|
width: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.song .song__name {
|
.song__name {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
span.control {
|
.player {
|
||||||
|
width: 25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player .nowplaying {
|
||||||
|
flex-flow: column;
|
||||||
|
background-color: #334;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player .controls {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player .control {
|
||||||
font-size: 40px;
|
font-size: 40px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.player .current {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player .albumart {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
object-fit: contain;
|
||||||
|
display: block;
|
||||||
|
margin-right: 1.0rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player .metadata {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body hx-sse="connect:/sse">
|
<body hx-sse="connect:/sse">
|
||||||
<span
|
<div class="browser">
|
||||||
hx-post="/previous" hx-swap="none"
|
|
||||||
class="control material-symbols-outlined" role="button"
|
|
||||||
>skip_previous</span>
|
|
||||||
<span
|
|
||||||
hx-post="/play" hx-swap="none"
|
|
||||||
class="control material-symbols-outlined" role="button"
|
|
||||||
>play_arrow</span>
|
|
||||||
<span
|
|
||||||
hx-post="/pause" hx-swap="none"
|
|
||||||
class="control material-symbols-outlined" role="button"
|
|
||||||
>pause</span>
|
|
||||||
<span
|
|
||||||
hx-post="/next" hx-swap="none"
|
|
||||||
class="control material-symbols-outlined" role="button"
|
|
||||||
>skip_next</span>
|
|
||||||
|
|
||||||
<div hx-trigger="load,sse:queue,sse:player" hx-get="/queue"></div>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<ul class="breadcrumb">
|
<ul class="breadcrumb">
|
||||||
<li><a href="?path=">Root</a></li>
|
<li><a href="?path=">Root</a></li>
|
||||||
{% for (i, component) in path.iter().enumerate() %}
|
{% for (i, component) in path.iter().enumerate() %}
|
||||||
|
@ -123,6 +163,7 @@
|
||||||
{% when mpd::Entry::Song with { name, path, artist } %}
|
{% when mpd::Entry::Song with { name, path, artist } %}
|
||||||
<li hx-post="/queue?path={{path}}" hx-swap="none" role="button" >
|
<li hx-post="/queue?path={{path}}" hx-swap="none" role="button" >
|
||||||
<span class="material-symbols-outlined">music_note</span>
|
<span class="material-symbols-outlined">music_note</span>
|
||||||
|
<!-- img src="/art?path={{path}}" -->
|
||||||
<div class="song">
|
<div class="song">
|
||||||
<div class="song__name">{{ name }}</div>
|
<div class="song__name">{{ name }}</div>
|
||||||
<div class="song__artist">{{ artist }}</div>
|
<div class="song__artist">{{ artist }}</div>
|
||||||
|
@ -143,7 +184,12 @@
|
||||||
{% endmatch %}
|
{% endmatch %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="player">
|
||||||
|
<div hx-trigger="load,sse:queue,sse:player" hx-get="/current"></div>
|
||||||
|
<div hx-trigger="load,sse:queue,sse:player" hx-get="/queue"></div>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue