yt finished, cleanup
This commit is contained in:
parent
4e10590646
commit
cf46a0ffb1
23 changed files with 301 additions and 199 deletions
|
@ -70,7 +70,6 @@ select {
|
|||
@import "font.less";
|
||||
@import "icons.less";
|
||||
@import "mixins.less";
|
||||
@import "search.less";
|
||||
@import "art.less";
|
||||
@import "variables.less";
|
||||
|
||||
|
@ -84,7 +83,9 @@ select {
|
|||
@import "elements/yt.less";
|
||||
@import "elements/range.less";
|
||||
@import "elements/playlist.less";
|
||||
@import "elements/search.less";
|
||||
@import "elements/library.less";
|
||||
@import "elements/tag.less";
|
||||
@import "elements/back.less";
|
||||
@import "elements/path.less";
|
||||
@import "elements/yt-result.less";
|
||||
|
|
|
@ -15,13 +15,4 @@ cyp-library {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
form {
|
||||
.item;
|
||||
.flex-row;
|
||||
|
||||
button:first-of-type {
|
||||
margin-left: var(--icon-spacing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ cyp-player {
|
|||
|
||||
.timeline {
|
||||
flex: none;
|
||||
height: 24px;
|
||||
height: var(--icon-size);
|
||||
margin-bottom: 4px;
|
||||
.flex-row;
|
||||
|
||||
|
|
23
app/css/elements/search.less
Normal file
23
app/css/elements/search.less
Normal file
|
@ -0,0 +1,23 @@
|
|||
cyp-search {
|
||||
form {
|
||||
.item;
|
||||
align-items: stretch;
|
||||
|
||||
button:first-of-type {
|
||||
margin-left: var(--icon-spacing);
|
||||
}
|
||||
}
|
||||
|
||||
&.pending form {
|
||||
background-image: linear-gradient(var(--primary), var(--primary));
|
||||
background-repeat: no-repeat;
|
||||
background-size: 25% var(--border-width);
|
||||
animation: bar ease-in-out 3s alternate infinite;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@keyframes bar {
|
||||
0% { background-position: 0 100%; }
|
||||
100% { background-position: 100% 100%; }
|
||||
}
|
8
app/css/elements/yt-result.less
Normal file
8
app/css/elements/yt-result.less
Normal file
|
@ -0,0 +1,8 @@
|
|||
cyp-yt-result {
|
||||
.item;
|
||||
cursor: default;
|
||||
|
||||
button .icon {
|
||||
width: var(--icon-size);
|
||||
}
|
||||
}
|
|
@ -1,32 +1,8 @@
|
|||
cyp-yt {
|
||||
header {
|
||||
border-bottom: 1px solid var(--fg);
|
||||
|
||||
button + button {
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.clear {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 0.5em 0.5ch;
|
||||
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 bar {
|
||||
0% { background-position: 0 100%; }
|
||||
100% { background-position: 100% 100%; }
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
.icon {
|
||||
width: 24px;
|
||||
width: var(--icon-size);
|
||||
flex: none;
|
||||
|
||||
path, polygon, circle {
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
.search {
|
||||
.flex-row;
|
||||
margin-left: auto;
|
||||
transition: all 300ms;
|
||||
width: 32px;
|
||||
max-width: 20ch;
|
||||
|
||||
.icon {
|
||||
width: 32px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input {
|
||||
border: none;
|
||||
outline: none;
|
||||
color: inherit;
|
||||
background-color: inherit;
|
||||
border-bottom: 1px solid var(--fg);
|
||||
width: 0;
|
||||
padding: 0;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
&.open {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
@breakpoint-menu: 480px;
|
||||
|
||||
cyp-app {
|
||||
--icon-size: 24px;
|
||||
--icon-spacing: 4px;
|
||||
--primary: rgb(var(--primary-raw));
|
||||
--primary-tint: rgba(var(--primary-raw), 0.1);
|
||||
|
|
File diff suppressed because one or more lines are too long
187
app/cyp.js
187
app/cyp.js
|
@ -654,7 +654,6 @@ async function initMpd() {
|
|||
await init();
|
||||
return mpd;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return mpdMock;
|
||||
}
|
||||
}
|
||||
|
@ -675,6 +674,7 @@ class App extends HTMLElement {
|
|||
const names = children.map(node => node.nodeName.toLowerCase())
|
||||
.filter(name => name.startsWith("cyp-"));
|
||||
const unique = new Set(names);
|
||||
console.log(unique);
|
||||
|
||||
const promises = [...unique].map(name => customElements.whenDefined(name));
|
||||
await Promise.all(promises);
|
||||
|
@ -712,10 +712,12 @@ class Selection {
|
|||
this._component = component;
|
||||
/** @type {"single" | "multi"} */
|
||||
this._mode = mode;
|
||||
this._items = []; // FIXME ukladat skutecne HTML? co kdyz nastane refresh?
|
||||
this._items = [];
|
||||
this._node = node("cyp-commands", {hidden:true});
|
||||
}
|
||||
|
||||
appendTo(parent) { parent.appendChild(this._node); }
|
||||
|
||||
clear() {
|
||||
while (this._items.length) { this.remove(this._items[0]); }
|
||||
}
|
||||
|
@ -770,18 +772,16 @@ class Selection {
|
|||
}
|
||||
|
||||
_show() {
|
||||
const parent = this._component.closest("cyp-app").querySelector("footer"); // FIXME jde lepe?
|
||||
parent.appendChild(this._node);
|
||||
this._node.offsetWidth; // FIXME jde lepe?
|
||||
this._node.hidden = false;
|
||||
}
|
||||
|
||||
_hide() {
|
||||
this._node.hidden = true;
|
||||
this._node.remove();
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("cyp-commands", class extends HTMLElement {});
|
||||
|
||||
class Component extends HTMLElement {
|
||||
constructor(options = {}) {
|
||||
super();
|
||||
|
@ -789,6 +789,10 @@ class Component extends HTMLElement {
|
|||
}
|
||||
|
||||
connectedCallback() {
|
||||
if (this.selection) {
|
||||
const parent = this._app.querySelector("footer");
|
||||
this.selection.appendTo(parent);
|
||||
}
|
||||
this._app.addEventListener("load", _ => this._onAppLoad());
|
||||
this._app.addEventListener("component-change", _ => {
|
||||
const component = this._app.component;
|
||||
|
@ -814,6 +818,13 @@ class Menu extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
_onAppLoad() {
|
||||
this._app.addEventListener("queue-length-change", e => {
|
||||
this.querySelector(".queue-length").textContent = `(${e.detail})`;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
async _activate(component) {
|
||||
const app = await this._app;
|
||||
app.setAttribute("component", component);
|
||||
|
@ -1157,8 +1168,8 @@ class Queue extends Component {
|
|||
let songs = await this._mpd.listQueue();
|
||||
this._buildSongs(songs);
|
||||
|
||||
// FIXME pubsub?
|
||||
document.querySelector("#queue-length").textContent = `(${songs.length})`;
|
||||
let e = new CustomEvent("queue-length-change", {detail:songs.length});
|
||||
this._app.dispatchEvent(e);
|
||||
}
|
||||
|
||||
_updateCurrent() {
|
||||
|
@ -1366,6 +1377,54 @@ class Settings extends Component {
|
|||
|
||||
customElements.define("cyp-settings", Settings);
|
||||
|
||||
class Search extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this._built = false;
|
||||
}
|
||||
|
||||
get value() { return this._input.value.trim(); }
|
||||
set value(value) { this._input.value = value; }
|
||||
get _input() { return this.querySelector("input"); }
|
||||
|
||||
onSubmit() {}
|
||||
focus() { this._input.focus(); }
|
||||
pending(pending) { this.classList.toggle("pending", pending); }
|
||||
|
||||
connectedCallback() {
|
||||
if (this._built) { return; }
|
||||
|
||||
const form = node("form", {}, "", this);
|
||||
node("input", {type:"text"}, "", form);
|
||||
button({icon:"magnify"}, "", form);
|
||||
|
||||
form.addEventListener("submit", e => {
|
||||
e.preventDefault();
|
||||
this.onSubmit();
|
||||
});
|
||||
|
||||
this._built = true;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("cyp-search", Search);
|
||||
|
||||
class YtResult extends Item {
|
||||
constructor(title) {
|
||||
super();
|
||||
this._title = title;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.appendChild(icon("magnify"));
|
||||
this._buildTitle(this._title);
|
||||
}
|
||||
|
||||
onClick() {}
|
||||
}
|
||||
|
||||
customElements.define("cyp-yt-result", YtResult);
|
||||
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
|
||||
function decodeChunk(byteArray) {
|
||||
|
@ -1374,57 +1433,52 @@ function decodeChunk(byteArray) {
|
|||
}
|
||||
|
||||
class YT extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this._search = new Search();
|
||||
|
||||
this._search.onSubmit = _ => {
|
||||
let query = this._search.value;
|
||||
query && this._doSearch(query);
|
||||
};
|
||||
}
|
||||
|
||||
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; }
|
||||
|
||||
this._post(url);
|
||||
}
|
||||
|
||||
_search() {
|
||||
let q = prompt("Please enter a search string:");
|
||||
if (!q) { return; }
|
||||
|
||||
this._post(`ytsearch:${q}`);
|
||||
this._clear();
|
||||
}
|
||||
|
||||
_clear() {
|
||||
clear(this.querySelector("pre"));
|
||||
clear(this);
|
||||
this.appendChild(this._search);
|
||||
}
|
||||
|
||||
async _post(q) {
|
||||
let pre = this.querySelector("pre");
|
||||
clear(pre);
|
||||
async _doSearch(query) {
|
||||
this._clear();
|
||||
this._search.pending(true);
|
||||
|
||||
this.classList.add("pending");
|
||||
let response = await fetch(`/youtube?q=${encodeURIComponent(query)}`);
|
||||
let results = await response.json();
|
||||
|
||||
this._search.pending(false);
|
||||
|
||||
results.forEach(result => {
|
||||
let node = new YtResult(result.title);
|
||||
this.appendChild(node);
|
||||
node.addButton("download", () => this._download(result.id));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async _download(id) {
|
||||
this._clear();
|
||||
|
||||
let pre = node("pre", {}, "", this);
|
||||
this._search.pending(true);
|
||||
|
||||
let body = new URLSearchParams();
|
||||
body.set("q", q);
|
||||
body.set("id", id);
|
||||
let response = await fetch("/youtube", {method:"POST", body});
|
||||
|
||||
let reader = response.body.getReader();
|
||||
|
@ -1436,7 +1490,7 @@ class YT extends Component {
|
|||
}
|
||||
reader.releaseLock();
|
||||
|
||||
this.classList.remove("pending");
|
||||
this._search.pending(false);
|
||||
|
||||
if (response.status == 200) {
|
||||
this._mpd.command(`update ${escape(ytPath)}`);
|
||||
|
@ -1444,7 +1498,10 @@ class YT extends Component {
|
|||
}
|
||||
|
||||
_onComponentChange(c, isThis) {
|
||||
const wasHidden = this.hidden;
|
||||
this.hidden = !isThis;
|
||||
|
||||
if (!wasHidden && isThis) { this._showRoot(); }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1560,6 +1617,13 @@ class Library extends Component {
|
|||
super({selection:"multi"});
|
||||
this._stateStack = [];
|
||||
this._initCommands();
|
||||
|
||||
this._search = new Search();
|
||||
this._search.onSubmit = _ => {
|
||||
let query = this._search.value;
|
||||
if (query.length < 3) { return; }
|
||||
this._doSearch(query);
|
||||
};
|
||||
}
|
||||
|
||||
_popState() {
|
||||
|
@ -1642,29 +1706,26 @@ class Library extends Component {
|
|||
_showSearch(query = "") {
|
||||
clear(this);
|
||||
|
||||
const form = node("form", {}, "", this);
|
||||
const input = node("input", {type:"text", value:query}, "", form);
|
||||
button({icon:"magnify"}, "", form);
|
||||
form.addEventListener("submit", e => {
|
||||
e.preventDefault();
|
||||
const query = input.value.trim();
|
||||
if (query.length < 3) { return; }
|
||||
this._doSearch(query, form);
|
||||
});
|
||||
this.appendChild(this._search);
|
||||
this._search.value = query;
|
||||
this._search.focus();
|
||||
|
||||
input.focus();
|
||||
if (query) { this._doSearch(query, form); }
|
||||
query && this._search.onSubmit();
|
||||
}
|
||||
|
||||
async _doSearch(query, form) {
|
||||
async _doSearch(query) {
|
||||
let state = this._stateStack[this._stateStack.length-1];
|
||||
state.query = query;
|
||||
|
||||
clear(this);
|
||||
this.appendChild(this._search);
|
||||
this._search.pending(true);
|
||||
|
||||
const songs1 = await this._mpd.searchSongs({"AlbumArtist": query});
|
||||
const songs2 = await this._mpd.searchSongs({"Album": query});
|
||||
const songs3 = await this._mpd.searchSongs({"Title": query});
|
||||
clear(this);
|
||||
this.appendChild(form);
|
||||
|
||||
this._search.pending(false);
|
||||
|
||||
this._aggregateSearch(songs1, "AlbumArtist");
|
||||
this._aggregateSearch(songs2, "Album");
|
||||
|
|
|
@ -76,7 +76,7 @@
|
|||
<button data-for="queue" data-icon="music">
|
||||
<div>
|
||||
<span>Queue</span>
|
||||
<span id="queue-length"></span>
|
||||
<span class="queue-length"></span>
|
||||
</div>
|
||||
</button>
|
||||
<button data-for="playlists" data-icon="playlist-music"><span>Playlists</span></button>
|
||||
|
|
|
@ -8,6 +8,10 @@ export default class Component extends HTMLElement {
|
|||
}
|
||||
|
||||
connectedCallback() {
|
||||
if (this.selection) {
|
||||
const parent = this._app.querySelector("footer");
|
||||
this.selection.appendTo(parent);
|
||||
}
|
||||
this._app.addEventListener("load", _ => this._onAppLoad());
|
||||
this._app.addEventListener("component-change", _ => {
|
||||
const component = this._app.component;
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
export const artSize = 96;
|
||||
export const ytPath = "_youtube";
|
||||
export const locale = "cs";
|
||||
|
|
|
@ -14,7 +14,6 @@ async function initMpd() {
|
|||
await mpd.init();
|
||||
return mpd;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return mpdMock;
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +34,7 @@ class App extends HTMLElement {
|
|||
const names = children.map(node => node.nodeName.toLowerCase())
|
||||
.filter(name => name.startsWith("cyp-"));
|
||||
const unique = new Set(names);
|
||||
console.log(unique);
|
||||
|
||||
const promises = [...unique].map(name => customElements.whenDefined(name));
|
||||
await Promise.all(promises);
|
||||
|
|
|
@ -4,6 +4,7 @@ import Tag from "./tag.js";
|
|||
import Path from "./path.js";
|
||||
import Back from "./back.js";
|
||||
import Song from "./song.js";
|
||||
import Search from "./search.js";
|
||||
import { escape, serializeFilter } from "../mpd.js";
|
||||
|
||||
|
||||
|
@ -36,6 +37,13 @@ class Library extends Component {
|
|||
super({selection:"multi"});
|
||||
this._stateStack = [];
|
||||
this._initCommands();
|
||||
|
||||
this._search = new Search();
|
||||
this._search.onSubmit = _ => {
|
||||
let query = this._search.value;
|
||||
if (query.length < 3) { return; }
|
||||
this._doSearch(query);
|
||||
}
|
||||
}
|
||||
|
||||
_popState() {
|
||||
|
@ -118,29 +126,26 @@ class Library extends Component {
|
|||
_showSearch(query = "") {
|
||||
html.clear(this);
|
||||
|
||||
const form = html.node("form", {}, "", this);
|
||||
const input = html.node("input", {type:"text", value:query}, "", form);
|
||||
html.button({icon:"magnify"}, "", form);
|
||||
form.addEventListener("submit", e => {
|
||||
e.preventDefault();
|
||||
const query = input.value.trim();
|
||||
if (query.length < 3) { return; }
|
||||
this._doSearch(query, form);
|
||||
});
|
||||
this.appendChild(this._search);
|
||||
this._search.value = query;
|
||||
this._search.focus();
|
||||
|
||||
input.focus();
|
||||
if (query) { this._doSearch(query, form); }
|
||||
query && this._search.onSubmit();
|
||||
}
|
||||
|
||||
async _doSearch(query, form) {
|
||||
async _doSearch(query) {
|
||||
let state = this._stateStack[this._stateStack.length-1];
|
||||
state.query = query;
|
||||
|
||||
html.clear(this);
|
||||
this.appendChild(this._search);
|
||||
this._search.pending(true);
|
||||
|
||||
const songs1 = await this._mpd.searchSongs({"AlbumArtist": query});
|
||||
const songs2 = await this._mpd.searchSongs({"Album": query});
|
||||
const songs3 = await this._mpd.searchSongs({"Title": query});
|
||||
html.clear(this);
|
||||
this.appendChild(form);
|
||||
|
||||
this._search.pending(false);
|
||||
|
||||
this._aggregateSearch(songs1, "AlbumArtist");
|
||||
this._aggregateSearch(songs2, "Album");
|
||||
|
|
|
@ -10,6 +10,13 @@ class Menu extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
_onAppLoad() {
|
||||
this._app.addEventListener("queue-length-change", e => {
|
||||
this.querySelector(".queue-length").textContent = `(${e.detail})`;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
async _activate(component) {
|
||||
const app = await this._app;
|
||||
app.setAttribute("component", component);
|
||||
|
|
|
@ -52,8 +52,8 @@ class Queue extends Component {
|
|||
let songs = await this._mpd.listQueue();
|
||||
this._buildSongs(songs);
|
||||
|
||||
// FIXME pubsub?
|
||||
document.querySelector("#queue-length").textContent = `(${songs.length})`;
|
||||
let e = new CustomEvent("queue-length-change", {detail:songs.length});
|
||||
this._app.dispatchEvent(e);
|
||||
}
|
||||
|
||||
_updateCurrent() {
|
||||
|
|
33
app/js/elements/search.js
Normal file
33
app/js/elements/search.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
import * as html from "../html.js";
|
||||
|
||||
export default class Search extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this._built = false;
|
||||
}
|
||||
|
||||
get value() { return this._input.value.trim(); }
|
||||
set value(value) { this._input.value = value; }
|
||||
get _input() { return this.querySelector("input"); }
|
||||
|
||||
onSubmit() {}
|
||||
focus() { this._input.focus(); }
|
||||
pending(pending) { this.classList.toggle("pending", pending); }
|
||||
|
||||
connectedCallback() {
|
||||
if (this._built) { return; }
|
||||
|
||||
const form = html.node("form", {}, "", this);
|
||||
html.node("input", {type:"text"}, "", form);
|
||||
html.button({icon:"magnify"}, "", form);
|
||||
|
||||
form.addEventListener("submit", e => {
|
||||
e.preventDefault();
|
||||
this.onSubmit();
|
||||
});
|
||||
|
||||
this._built = true;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("cyp-search", Search);
|
19
app/js/elements/yt-result.js
Normal file
19
app/js/elements/yt-result.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
import Item from "../item.js";
|
||||
import * as html from "../html.js";
|
||||
|
||||
|
||||
export default class YtResult extends Item {
|
||||
constructor(title) {
|
||||
super();
|
||||
this._title = title;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.appendChild(html.icon("magnify"));
|
||||
this._buildTitle(this._title);
|
||||
}
|
||||
|
||||
onClick() {}
|
||||
}
|
||||
|
||||
customElements.define("cyp-yt-result", YtResult);
|
|
@ -2,6 +2,8 @@ import * as html from "../html.js";
|
|||
import * as conf from "../conf.js";
|
||||
import { escape } from "../mpd.js";
|
||||
import Component from "../component.js";
|
||||
import Search from "./search.js";
|
||||
import Result from "./yt-result.js";
|
||||
|
||||
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
|
@ -12,57 +14,52 @@ function decodeChunk(byteArray) {
|
|||
}
|
||||
|
||||
class YT extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this._search = new Search();
|
||||
|
||||
this._search.onSubmit = _ => {
|
||||
let query = this._search.value;
|
||||
query && this._doSearch(query);
|
||||
}
|
||||
}
|
||||
|
||||
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; }
|
||||
|
||||
this._post(url);
|
||||
}
|
||||
|
||||
_search() {
|
||||
let q = prompt("Please enter a search string:");
|
||||
if (!q) { return; }
|
||||
|
||||
this._post(`ytsearch:${q}`);
|
||||
this._clear();
|
||||
}
|
||||
|
||||
_clear() {
|
||||
html.clear(this.querySelector("pre"));
|
||||
html.clear(this);
|
||||
this.appendChild(this._search);
|
||||
}
|
||||
|
||||
async _post(q) {
|
||||
let pre = this.querySelector("pre");
|
||||
html.clear(pre);
|
||||
async _doSearch(query) {
|
||||
this._clear();
|
||||
this._search.pending(true);
|
||||
|
||||
this.classList.add("pending");
|
||||
let response = await fetch(`/youtube?q=${encodeURIComponent(query)}`);
|
||||
let results = await response.json();
|
||||
|
||||
this._search.pending(false);
|
||||
|
||||
results.forEach(result => {
|
||||
let node = new Result(result.title);
|
||||
this.appendChild(node);
|
||||
node.addButton("download", () => this._download(result.id));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async _download(id) {
|
||||
this._clear();
|
||||
|
||||
let pre = html.node("pre", {}, "", this);
|
||||
this._search.pending(true);
|
||||
|
||||
let body = new URLSearchParams();
|
||||
body.set("q", q);
|
||||
body.set("id", id);
|
||||
let response = await fetch("/youtube", {method:"POST", body});
|
||||
|
||||
let reader = response.body.getReader();
|
||||
|
@ -74,7 +71,7 @@ class YT extends Component {
|
|||
}
|
||||
reader.releaseLock();
|
||||
|
||||
this.classList.remove("pending");
|
||||
this._search.pending(false);
|
||||
|
||||
if (response.status == 200) {
|
||||
this._mpd.command(`update ${escape(conf.ytPath)}`);
|
||||
|
@ -82,7 +79,10 @@ class YT extends Component {
|
|||
}
|
||||
|
||||
_onComponentChange(c, isThis) {
|
||||
const wasHidden = this.hidden;
|
||||
this.hidden = !isThis;
|
||||
|
||||
if (!wasHidden && isThis) { this._showRoot(); }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,10 +8,12 @@ export default class Selection {
|
|||
this._component = component;
|
||||
/** @type {"single" | "multi"} */
|
||||
this._mode = mode;
|
||||
this._items = []; // FIXME ukladat skutecne HTML? co kdyz nastane refresh?
|
||||
this._items = [];
|
||||
this._node = html.node("cyp-commands", {hidden:true});
|
||||
}
|
||||
|
||||
appendTo(parent) { parent.appendChild(this._node); }
|
||||
|
||||
clear() {
|
||||
while (this._items.length) { this.remove(this._items[0]); }
|
||||
}
|
||||
|
@ -66,14 +68,12 @@ export default class Selection {
|
|||
}
|
||||
|
||||
_show() {
|
||||
const parent = this._component.closest("cyp-app").querySelector("footer"); // FIXME jde lepe?
|
||||
parent.appendChild(this._node);
|
||||
this._node.offsetWidth; // FIXME jde lepe?
|
||||
this._node.hidden = false;
|
||||
}
|
||||
|
||||
_hide() {
|
||||
this._node.hidden = true;
|
||||
this._node.remove();
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("cyp-commands", class extends HTMLElement {});
|
15
index.js
15
index.js
|
@ -14,8 +14,9 @@ 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}`);
|
||||
q = escape(`ytsearch3:${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);
|
||||
|
@ -28,14 +29,14 @@ function searchYoutube(q, response) {
|
|||
}
|
||||
|
||||
|
||||
function downloadYoutube(q, response) {
|
||||
function downloadYoutube(id, response) {
|
||||
response.setHeader("Content-Type", "text/plain"); // necessary for firefox to read by chunks
|
||||
|
||||
console.log("YouTube downloading", q);
|
||||
console.log("YouTube downloading", id);
|
||||
let args = [
|
||||
"-f", "bestaudio",
|
||||
"-o", `${__dirname}/_youtube/%(title)s-%(id)s.%(ext)s`,
|
||||
q
|
||||
id
|
||||
]
|
||||
let child = require("child_process").spawn(cmd, args);
|
||||
|
||||
|
@ -70,9 +71,9 @@ function handleYoutubeDownload(request, response) {
|
|||
request.setEncoding("utf8");
|
||||
request.on("data", chunk => str += chunk);
|
||||
request.on("end", () => {
|
||||
let q = require("querystring").parse(str)["id"];
|
||||
if (q) {
|
||||
downloadYoutube(q, response);
|
||||
let id = require("querystring").parse(str)["id"];
|
||||
if (id) {
|
||||
downloadYoutube(id, response);
|
||||
} else {
|
||||
response.writeHead(404);
|
||||
response.end();
|
||||
|
|
Loading…
Reference in a new issue