Add buttons to play/queue an entire folder
continuous-integration/drone/push Build is passing Details

Fixes #2
This commit is contained in:
Sijmen 2023-05-07 01:50:06 +02:00
parent 7a1adb37b2
commit c4936dc4ff
Signed by: vijfhoek
GPG Key ID: DAF7821E067D9C48
5 changed files with 136 additions and 45 deletions

View File

@ -92,12 +92,33 @@ async fn get_browser(req: tide::Request<()>) -> tide::Result {
#[derive(Deserialize)]
struct PostQueueQuery {
path: String,
#[serde(default)]
replace: bool,
#[serde(default)]
next: bool,
#[serde(default)]
play: bool,
}
async fn post_queue(req: tide::Request<()>) -> tide::Result {
let query: PostQueueQuery = req.query()?;
let path = percent_decode_str(&query.path).decode_utf8_lossy();
mpd::connect()?.add(&path)?;
let mut mpd = mpd::Mpd::connect().await?;
if query.replace {
mpd.clear().await?;
}
if query.next {
mpd.add_pos(&path, "+0").await?;
} else {
mpd.add(&path).await?;
}
if query.play {
mpd.play().await?;
}
Ok("".into())
}
@ -178,7 +199,7 @@ async fn sse(_req: tide::Request<()>, sender: tide::sse::Sender) -> tide::Result
#[async_std::main]
async fn main() -> tide::Result<()> {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.with_max_level(tracing::Level::WARN)
.init();
let mut app = tide::new();

View File

@ -104,8 +104,8 @@ pub(crate) struct Mpd {
}
impl Mpd {
fn escape_str(s: &str) -> String {
s.replace("\"", "\\\"").replace("'", "\\'")
pub fn escape_str(s: &str) -> String {
s.replace('\"', "\\\"").replace('\'', "\\'")
}
pub async fn connect() -> anyhow::Result<Self> {
@ -130,6 +130,45 @@ impl Mpd {
Ok(Self { stream, reader })
}
pub async fn command(&mut self, command: &str) -> anyhow::Result<()> {
self.stream
.write_all(format!("{command}\n").as_bytes())
.await?;
let mut buffer = String::new();
loop {
buffer.clear();
self.reader.read_line(&mut buffer).await?;
let split: Vec<_> = buffer.trim_end().split_ascii_whitespace().collect();
if split[0] == "OK" {
break Ok(());
} else if split[0] == "ACK" {
break Err(anyhow!(buffer));
}
}
}
pub async fn clear(&mut self) -> anyhow::Result<()> {
self.command("clear").await
}
pub async fn add(&mut self, path: &str) -> anyhow::Result<()> {
let path = Self::escape_str(path);
self.command(&format!("add \"{path}\"")).await
}
pub async fn add_pos(&mut self, path: &str, pos: &str) -> anyhow::Result<()> {
let path = Self::escape_str(path);
let pos = Self::escape_str(pos);
self.command(&format!("add \"{path}\" \"{pos}\"")).await
}
pub async fn play(&mut self) -> anyhow::Result<()> {
self.command("play").await
}
pub(crate) async fn idle(&mut self, systems: &[&str]) -> anyhow::Result<Vec<String>> {
let mut buffer = String::new();

View File

@ -32,6 +32,14 @@ button {
background-color: transparent;
border: none;
color: inherit;
padding: 0;
font-weight: bold;
display: flex;
line-height: 24px;
cursor: pointer;
}
button .material-symbols-outlined {
margin-right: 0.25rem;
}
.browser {
@ -71,14 +79,6 @@ ul {
font-weight: bold;
flex: 1;
}
.queue-clear {
font-weight: bold;
display: flex;
line-height: 24px;
}
.queue-clear .material-symbols-outlined {
margin-right: 0.25rem;
}
.queue {
margin-top: 0.5rem;
@ -112,15 +112,28 @@ ul {
-webkit-box-orient: vertical;
}
.browser .header {
display: flex;
flex-flow: column;
background-color: #334;
border-radius: 0.25rem;
padding: 0.75rem 1rem;
margin: 16px 16px 0;
}
.browser .buttons {
display: flex;
flex-flow: row;
margin-top: 0.75rem;
}
.browser .buttons button {
margin-right: 1.0rem;
}
ul.breadcrumb {
display: flex;
margin-bottom: 1rem;
flex-wrap: wrap;
list-style: none;
background-color: #334;
border-radius: .25rem;
padding: .75rem 1rem;
margin: 16px 16px 0;
}
@media (prefers-contrast: more) {
ul.breadcrumb {

View File

@ -1,31 +1,49 @@
{# #}
<ul class="breadcrumb">
<li>
<a
href="/"
hx-replace-url="/"
hx-get="/browser"
hx-vals='{"path": ""}'
hx-target=".browser"
>Root</a>
</li>
{% for (i, component) in path.iter().enumerate() %}
<li>
{% if i == path.len() - 1 %}
{{ component }}
{% else %}
<a
{% let encoded = path[..i + 1].join("/")|urlencode %}
href="/?path={{ encoded }}"
hx-replace-url="/?path={{ encoded }}"
hx-get="/browser"
hx-vals='{"path": "{{ encoded }}"}'
hx-target=".browser"
>{{ component }}</a>
{% endif %}
</li>
{% endfor %}
</ul>
<div class="header">
<ul class="breadcrumb">
<li>
<a
href="/"
hx-replace-url="/"
hx-get="/browser"
hx-vals='{"path": ""}'
hx-target=".browser"
>Root</a>
</li>
{% for (i, component) in path.iter().enumerate() %}
<li>
{% if i == path.len() - 1 %}
{{ component }}
{% else %}
<a
{% let encoded = path[..i + 1].join("/")|urlencode %}
href="/?path={{ encoded }}"
hx-replace-url="/?path={{ encoded }}"
hx-get="/browser"
hx-vals='{"path": "{{ encoded }}"}'
hx-target=".browser"
>{{ component }}</a>
{% endif %}
</li>
{% endfor %}
</ul>
<div class="buttons">
{% let encoded = path.join("/")|urlencode %}
<button hx-delete="/queue" hx-swap="none" hx-post="/queue?path={{ encoded }}">
<span class="material-symbols-outlined">playlist_add</span>
Queue all
</button>
<button hx-delete="/queue" hx-swap="none" hx-post="/queue?path={{ encoded }}&replace=true&play=true">
<span class="material-symbols-outlined">playlist_play</span>
Play all
</button>
<button hx-delete="/queue" hx-swap="none" hx-post="/queue?path={{ encoded }}&next=true">
<span class="material-symbols-outlined">playlist_add</span>
Play next
</button>
</div>
</div>
<ul class="dir" hx-boost="true" tabindex="-1">
{% for entry in entries %}

View File

@ -3,7 +3,7 @@
<div class="queue-header">
<div class="queue-next">Next in queue</div>
<button class="queue-clear" role="button" hx-delete="/queue" hx-swap="none">
<button class="queue-clear" hx-delete="/queue" hx-swap="none">
<span class="material-symbols-outlined">playlist_remove</span>
Clear
</div>