diff --git a/app/cyp.js b/app/cyp.js index 047155a..136d2a3 100644 --- a/app/cyp.js +++ b/app/cyp.js @@ -782,12 +782,7 @@ class Selection { } } -class HasApp extends HTMLElement { - get _app() { return this.closest("cyp-app"); } - get _mpd() { return this._app.mpd; } -} - -class Component extends HasApp { +class Component extends HTMLElement { constructor(options = {}) { super(); if (options.selection) { this.selection = new Selection(this, options.selection); } @@ -802,6 +797,9 @@ class Component extends HasApp { }); } + get _app() { return this.closest("cyp-app"); } + get _mpd() { return this._app.mpd; } + _onAppLoad() {} _onComponentChange(_component, _isThis) {} } @@ -1056,7 +1054,7 @@ class Player extends Component { customElements.define("cyp-player", Player); -class Item extends HasApp { +class Item extends HTMLElement { constructor() { super(); this.addEventListener("click", _ => this.onClick()); @@ -1376,12 +1374,31 @@ function decodeChunk(byteArray) { } class YT extends Component { - _onAppLoad() { - this.querySelector(".download").addEventListener("click", _ => this._download()); - this.querySelector(".search-download").addEventListener("click", _ => this._search()); - this.querySelector(".clear").addEventListener("click", _ => this._clear()); + connectedCallback() { + super.connectedCallback(); + + const form = node("form", {}, "", this); + const input = node("input", {type:"text"}, "", form); + button({icon:"magnify"}, "", form); + form.addEventListener("submit", e => { + e.preventDefault(); + const query = input.value.trim(); + if (!query.length) { return; } + this._doSearch(query, form); + }); } + async _doSearch(query, form) { + let response = await fetch(`/youtube?q=${encodeURIComponent(query)}`); + let data = await response.json(); + + clear(this); + this.appendChild(form); + + console.log(data); + } + + _download() { let url = prompt("Please enter a YouTube URL:"); if (!url) { return; } diff --git a/app/index.html b/app/index.html index 35e981f..0998b41 100644 --- a/app/index.html +++ b/app/index.html @@ -42,14 +42,7 @@ - -
- - - -
-

-			
+
Theme
diff --git a/app/js/component.js b/app/js/component.js index e830cd7..6d6a37b 100644 --- a/app/js/component.js +++ b/app/js/component.js @@ -1,11 +1,7 @@ import Selection from "./selection.js"; -export class HasApp extends HTMLElement { - get _app() { return this.closest("cyp-app"); } - get _mpd() { return this._app.mpd; } -} -export default class Component extends HasApp { +export default class Component extends HTMLElement { constructor(options = {}) { super(); if (options.selection) { this.selection = new Selection(this, options.selection); } @@ -20,6 +16,9 @@ export default class Component extends HasApp { }); } + get _app() { return this.closest("cyp-app"); } + get _mpd() { return this._app.mpd; } + _onAppLoad() {} _onComponentChange(_component, _isThis) {} } diff --git a/app/js/elements/yt.js b/app/js/elements/yt.js index 8ae8833..142c9b2 100644 --- a/app/js/elements/yt.js +++ b/app/js/elements/yt.js @@ -12,12 +12,31 @@ function decodeChunk(byteArray) { } class YT extends Component { - _onAppLoad() { - this.querySelector(".download").addEventListener("click", _ => this._download()); - this.querySelector(".search-download").addEventListener("click", _ => this._search()); - this.querySelector(".clear").addEventListener("click", _ => this._clear()); + connectedCallback() { + super.connectedCallback(); + + const form = html.node("form", {}, "", this); + const input = html.node("input", {type:"text"}, "", form); + html.button({icon:"magnify"}, "", form); + form.addEventListener("submit", e => { + e.preventDefault(); + const query = input.value.trim(); + if (!query.length) { return; } + this._doSearch(query, form); + }); } + async _doSearch(query, form) { + let response = await fetch(`/youtube?q=${encodeURIComponent(query)}`); + let data = await response.json(); + + html.clear(this); + this.appendChild(form); + + console.log(data); + } + + _download() { let url = prompt("Please enter a YouTube URL:"); if (!url) { return; } diff --git a/app/js/item.js b/app/js/item.js index 2fde134..59b1768 100644 --- a/app/js/item.js +++ b/app/js/item.js @@ -1,7 +1,7 @@ import * as html from "./html.js"; -import { HasApp } from "./component.js"; -export default class Item extends HasApp { + +export default class Item extends HTMLElement { constructor() { super(); this.addEventListener("click", _ => this.onClick()); diff --git a/app/js/search.js b/app/js/search.js deleted file mode 100644 index b3e5d6c..0000000 --- a/app/js/search.js +++ /dev/null @@ -1,43 +0,0 @@ -import * as html from "./html.js"; -import * as conf from "./conf.js"; - -const OPEN = "open"; -const collator = new Intl.Collator(conf.locale, {usage:"search", sensitivity:"base"}); - -export default class Search extends EventTarget { - constructor(parent) { - super(); - this._node = html.node("label", {className:"search"}); - - this._input = html.node("input", {type:"text"}, "", this._node); - html.icon("magnify", this._node); - - this._node.addEventListener("click", e => { - if (e.target == this._input) { return; } - if (this._node.classList.contains(OPEN)) { - this.reset(); - this.dispatchEvent(new Event("input")); - } else { - this._node.classList.add(OPEN); - } - }); - - this._input.addEventListener("input", e => { - this.dispatchEvent(new Event("input")); - }); - } - - getNode() { return this._node; } - - match(str) { - let q = this._input.value.trim(); - if (!q) { return true; } - let len = q.length; - return str.split(" ").some(str => collator.compare(q, str.substring(0, len)) == 0); - } - - reset() { - this._input.value = ""; - this._node.classList.remove(OPEN); - } -} diff --git a/index.js b/index.js index cb628b3..b054e86 100644 --- a/index.js +++ b/index.js @@ -6,6 +6,27 @@ let tickets = []; const cmd = "youtube-dl"; +function escape(arg) { + return `'${arg.replace(/'/g, `'\\''`)}'`; +} + +function searchYoutube(q, response) { + response.setHeader("Content-Type", "text/plain"); // necessary for firefox to read by chunks + + console.log("YouTube searching", q); + q = escape(`ytsearch10:${q}`); + const command = `${cmd} -j ${q} | jq "{id,title}" | jq -s .`; + require("child_process").exec(command, {}, (error, stdout, stderr) => { + if (error) { + console.log("error", error); + response.writeHead(500); + response.end(error.message); + } else { + response.end(stdout); + } + }); +} + function downloadYoutube(q, response) { response.setHeader("Content-Type", "text/plain"); // necessary for firefox to read by chunks @@ -34,12 +55,22 @@ function downloadYoutube(q, response) { }); } -function handleYoutube(request, response) { +function handleYoutubeSearch(url, response) { + let q = url.searchParams.get("q"); + if (q) { + searchYoutube(q, response); + } else { + response.writeHead(404); + response.end(); + } +} + +function handleYoutubeDownload(request, response) { let str = ""; request.setEncoding("utf8"); request.on("data", chunk => str += chunk); request.on("end", () => { - let q = require("querystring").parse(str)["q"]; + let q = require("querystring").parse(str)["id"]; if (q) { downloadYoutube(q, response); } else { @@ -62,11 +93,16 @@ function handleTicket(request, response) { } function onRequest(request, response) { - switch (true) { - case request.method == "POST" && request.url == "/youtube": - return handleYoutube(request, response); + const url = new URL(request.url, "http://localhost"); - case request.method == "POST" && request.url == "/ticket": + switch (true) { + case request.method == "GET" && url.pathname == "/youtube": + return handleYoutubeSearch(url, response); + + case request.method == "POST" && url.pathname == "/youtube": + return handleYoutubeDownload(request, response); + + case request.method == "POST" && url.pathname == "/ticket": return handleTicket(request, response); default: