diff --git a/app/app.css b/app/app.css index cc85feb..1b63dcc 100644 --- a/app/app.css +++ b/app/app.css @@ -38,6 +38,7 @@ button { flex-direction: row; align-items: center; display: inline-flex; + white-space: nowrap; background-color: transparent; padding: 0; border: none; @@ -576,8 +577,6 @@ nav ul li.active { height: 100%; display: flex; flex-direction: column; - align-items: center; - justify-content: center; } #yt header { display: flex; @@ -632,36 +631,32 @@ nav ul li.active { #yt li:nth-child(odd) { background-color: var(--bg-alt); } -#yt .go { - width: 96px; - height: 96px; - justify-content: center; +#yt header { + border-bottom: 1px solid var(--fg); } -#yt .go:disabled { - position: relative; +#yt header button + button { + margin-left: 16px; } -#yt .go:disabled::before { - content: ""; - display: block; - position: absolute; - left: 0; - right: 0; - top: 0; - bottom: 0; - border-top: 3px solid var(--primary); - border-radius: 50%; - animation: rotate linear 3s infinite; +#yt .clear { + margin-left: auto; } -#yt p { - margin: 16px 8px; +#yt pre { + flex-grow: 1; + overflow: auto; white-space: pre-wrap; } -@keyframes rotate { +#yt.pending header { + background-image: linear-gradient(var(--primary), var(--primary)); + background-repeat: no-repeat; + background-size: 25% 4px; + animation: bar ease-in-out 3s alternate infinite; +} +@keyframes bar { 0% { - transform: rotate(0deg); + background-position: 0 100%; } 100% { - transform: rotate(360deg); + background-position: 100% 100%; } } #settings { diff --git a/app/css/app.less b/app/css/app.less index 36ec2e6..8a8dd48 100644 --- a/app/css/app.less +++ b/app/css/app.less @@ -35,6 +35,7 @@ button { .flex-row; display: inline-flex; + white-space: nowrap; background-color: transparent; padding: 0; diff --git a/app/css/yt.less b/app/css/yt.less index 4a5b4c6..929acb5 100644 --- a/app/css/yt.less +++ b/app/css/yt.less @@ -1,39 +1,33 @@ #yt { - .component; + .component; - align-items: center; - justify-content: center; + header { + border-bottom: 1px solid var(--fg); - .go { - width: 96px; - height: 96px; - justify-content: center; - - &:disabled { - position: relative; - - &::before { - content: ""; - display: block; - position: absolute; - left: 0; - right: 0; - top: 0; - bottom: 0; - border-top: 3px solid var(--primary); - border-radius: 50%; - animation: rotate linear 3s infinite; - } + button + button { + margin-left: 16px; } } - p { - margin: 16px 8px; + .clear { + margin-left: auto; + } + + pre { + flex-grow: 1; + overflow: auto; white-space: pre-wrap; } + + &.pending header { + background-image: linear-gradient(var(--primary), var(--primary)); + background-repeat: no-repeat; + background-size: 25% 4px; + animation: bar ease-in-out 3s alternate infinite; + } } -@keyframes rotate { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } +@keyframes bar { + 0% { background-position: 0 100%; } + 100% { background-position: 100% 100%; } } diff --git a/app/index.html b/app/index.html index 901f1ca..1d3d79b 100644 --- a/app/index.html +++ b/app/index.html @@ -57,8 +57,12 @@
- -

+
+ + + +
+

 			
diff --git a/app/js/yt.js b/app/js/yt.js index a15eb04..ec598c3 100644 --- a/app/js/yt.js +++ b/app/js/yt.js @@ -8,41 +8,58 @@ let node; const decoder = new TextDecoder("utf-8"); function decodeChunk(byteArray) { - return decoder.decode(byteArray); + // \r => \n + return decoder.decode(byteArray).replace(/\u000d/g, "\n"); } -async function onClick(e) { - let url = prompt("Please enter a YouTube URL:"); - if (!url) { return; } +async function post(q) { + let pre = node.querySelector("pre"); + html.clear(pre); - let button = e.target; - button.disabled = true; - - let p = node.querySelector("p"); - p.textContent = ""; + node.classList.add("pending"); let body = new URLSearchParams(); - body.set("url", url); + body.set("q", q); let response = await fetch("/youtube", {method:"POST", body}); let reader = response.body.getReader(); while (true) { let { done, value } = await reader.read(); if (done) { break; } - p.textContent += decodeChunk(value); + pre.textContent += decodeChunk(value); + pre.scrollTop = pre.scrollHeight; } reader.releaseLock(); - button.disabled = false; + node.classList.remove("pending"); + if (response.status == 200) { mpd.command(`update ${mpd.escape(conf.ytPath)}`); } } +function download() { + let url = prompt("Please enter a YouTube URL:"); + if (!url) { return; } + + post(url); +} + +function search() { + let q = prompt("Please enter a search string:"); + if (!q) { return; } + post(`ytsearch:${q}`); +} + +function clear() { + html.clear(node.querySelector("pre")); +} + export async function activate() {} export function init(n) { node = n; - - let button = node.querySelector(".go").addEventListener("click", onClick); + node.querySelector(".download").addEventListener("click", e => download()); + node.querySelector(".search-download").addEventListener("click", e => search()); + node.querySelector(".clear").addEventListener("click", e => clear()); } diff --git a/index.js b/index.js index 7db577c..4e67a0f 100644 --- a/index.js +++ b/index.js @@ -5,16 +5,16 @@ const port = Number(process.argv[2]) || 8080; const cmd = "youtube-dl"; //const cmd = "./test.sh"; -function downloadYoutube(url, response) { +function downloadYoutube(q, response) { response.setHeader("Content-Type", "text/plain"); // necessary for firefox to read by chunks // response.setHeader("Content-Type", "text/plain; charset=utf-8"); // FIXME create directory - console.log("YouTube downloading", url); + console.log("YouTube downloading", q); let args = [ "-f", "bestaudio", "-o", `${__dirname}/_youtube/%(title)s-%(id)s.%(ext)s`, - url + q ] let child = require("child_process").spawn(cmd, args); @@ -39,9 +39,9 @@ function handleYoutube(request, response) { request.setEncoding("utf8"); request.on("data", chunk => str += chunk); request.on("end", () => { - let url = require("querystring").parse(str)["url"]; - if (url) { - downloadYoutube(url, response); + let q = require("querystring").parse(str)["q"]; + if (q) { + downloadYoutube(q, response); } else { response.writeHead(404); response.end();