2020-03-12 05:46:28 +08:00
|
|
|
import * as html from "../html.js";
|
|
|
|
import Component from "../component.js";
|
2020-03-13 06:03:26 +08:00
|
|
|
import Tag from "./tag.js";
|
|
|
|
import Path from "./path.js";
|
|
|
|
import Back from "./back.js";
|
|
|
|
import Song from "./song.js";
|
|
|
|
import { escape, serializeFilter } from "../mpd.js";
|
|
|
|
|
|
|
|
|
|
|
|
const SORT = "-Track";
|
|
|
|
|
|
|
|
function createEnqueueCommand(node) {
|
|
|
|
if (node instanceof Song) {
|
|
|
|
return `add "${escape(node.data["file"])}"`;
|
|
|
|
} else if (node instanceof Tag) {
|
|
|
|
return [
|
|
|
|
"findadd",
|
|
|
|
serializeFilter(node.createChildFilter()),
|
|
|
|
`sort ${SORT}`
|
|
|
|
].join(" ");
|
|
|
|
} else {
|
2020-03-13 17:36:13 +08:00
|
|
|
throw new Error(`Cannot create enqueue command for "${node.nodeName}"`);
|
2020-03-13 06:03:26 +08:00
|
|
|
}
|
|
|
|
}
|
2020-03-12 05:46:28 +08:00
|
|
|
|
|
|
|
class Library extends Component {
|
|
|
|
constructor() {
|
|
|
|
super({selection:"multi"});
|
2020-03-13 06:03:26 +08:00
|
|
|
this._initCommands();
|
2020-03-12 05:46:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
_onAppLoad() {
|
|
|
|
this._showRoot();
|
|
|
|
}
|
|
|
|
|
|
|
|
_onComponentChange(c, isThis) {
|
|
|
|
const wasHidden = this.hidden;
|
|
|
|
this.hidden = !isThis;
|
|
|
|
|
|
|
|
if (!wasHidden) { this._showRoot(); }
|
|
|
|
}
|
|
|
|
|
|
|
|
_showRoot() {
|
|
|
|
html.clear(this);
|
|
|
|
|
|
|
|
html.button({icon:"artist"}, "Artists and albums", this)
|
|
|
|
.addEventListener("click", _ => this._listTags("AlbumArtist"));
|
|
|
|
|
2020-03-13 06:03:26 +08:00
|
|
|
html.button({icon:"folder"}, "Files and directories", this)
|
|
|
|
.addEventListener("click", _ => this._listPath(""));
|
2020-03-12 05:46:28 +08:00
|
|
|
|
2020-03-13 06:03:26 +08:00
|
|
|
html.button({icon:"magnify"}, "Search", this)
|
2020-03-12 05:46:28 +08:00
|
|
|
.addEventListener("click", _ => this._showSearch());
|
|
|
|
}
|
|
|
|
|
|
|
|
async _listTags(tag, filter = {}) {
|
|
|
|
const values = await this._mpd.listTags(tag, filter);
|
|
|
|
html.clear(this);
|
|
|
|
|
2020-03-13 06:03:26 +08:00
|
|
|
if ("AlbumArtist" in filter) { this._buildBack(filter); }
|
|
|
|
values.forEach(value => this._buildTag(tag, value, filter));
|
2020-03-12 05:46:28 +08:00
|
|
|
}
|
|
|
|
|
2020-03-13 06:03:26 +08:00
|
|
|
async _listPath(path) {
|
|
|
|
let paths = await this._mpd.listPath(path);
|
|
|
|
html.clear(this);
|
|
|
|
|
|
|
|
path && this._buildBack(path);
|
|
|
|
paths["directory"].forEach(path => this._buildPath(path));
|
|
|
|
paths["file"].forEach(path => this._buildPath(path));
|
|
|
|
}
|
2020-03-12 05:46:28 +08:00
|
|
|
|
2020-03-13 06:03:26 +08:00
|
|
|
async _listSongs(filter) {
|
|
|
|
const songs = await this._mpd.listSongs(filter);
|
|
|
|
html.clear(this);
|
|
|
|
this._buildBack(filter);
|
|
|
|
songs.forEach(song => this.appendChild(new Song(song)));
|
2020-03-12 05:46:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
_showSearch() {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-03-13 06:03:26 +08:00
|
|
|
_buildTag(tag, value, filter) {
|
2020-03-12 05:46:28 +08:00
|
|
|
let node;
|
|
|
|
switch (tag) {
|
|
|
|
case "AlbumArtist":
|
2020-03-13 06:03:26 +08:00
|
|
|
node = new Tag(tag, value, filter);
|
|
|
|
this.appendChild(node);
|
|
|
|
node.onClick = () => this._listTags("Album", node.createChildFilter());
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "Album":
|
|
|
|
node = new Tag(tag, value, filter);
|
|
|
|
this.appendChild(node);
|
|
|
|
node.addButton("chevron-double-right", _ => this._listSongs(node.createChildFilter()));
|
2020-03-12 05:46:28 +08:00
|
|
|
break;
|
|
|
|
}
|
2020-03-13 06:03:26 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
_buildBack(filterOrPath) {
|
|
|
|
if (typeof(filterOrPath) == "string") {
|
|
|
|
const path = filterOrPath.split("/").slice(0, -1).join("");
|
|
|
|
const node = new Back("..");
|
|
|
|
this.appendChild(node);
|
|
|
|
node.onClick = () => {
|
|
|
|
this.selection.clear();
|
|
|
|
this._listPath(path);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const filter = Object.assign({}, filterOrPath)
|
|
|
|
let tag, title;
|
|
|
|
|
|
|
|
if ("Album" in filter) {
|
|
|
|
tag = "Album";
|
|
|
|
title = filter["AlbumArtist"];
|
|
|
|
} else if ("AlbumArtist" in filter) {
|
|
|
|
tag = "AlbumArtist";
|
|
|
|
title = "Artists";
|
|
|
|
}
|
|
|
|
|
|
|
|
delete filter[tag];
|
|
|
|
const node = new Back(title);
|
|
|
|
this.appendChild(node);
|
|
|
|
node.onClick = () => {
|
|
|
|
this.selection.clear();
|
|
|
|
this._listTags(tag, filter);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_buildPath(data) {
|
|
|
|
let node = new Path(data);
|
2020-03-12 05:46:28 +08:00
|
|
|
this.appendChild(node);
|
2020-03-13 06:03:26 +08:00
|
|
|
|
|
|
|
if ("directory" in data) {
|
|
|
|
const path = data["directory"];
|
|
|
|
node.addButton("chevron-double-right", _ => this._listPath(path));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_initCommands() {
|
|
|
|
const sel = this.selection;
|
|
|
|
|
|
|
|
sel.addCommandAll();
|
|
|
|
|
|
|
|
sel.addCommand(async items => {
|
|
|
|
const commands = [
|
|
|
|
"clear",
|
|
|
|
...items.map(createEnqueueCommand),
|
|
|
|
"play"
|
|
|
|
];
|
|
|
|
await this._mpd.command(commands);
|
|
|
|
this.selection.clear();
|
|
|
|
this._app.dispatchEvent(new CustomEvent("queue-change")); // fixme notification?
|
|
|
|
}, {label:"Play", icon:"play"});
|
|
|
|
|
|
|
|
sel.addCommand(async items => {
|
|
|
|
const commands = items.map(createEnqueueCommand);
|
|
|
|
await this._mpd.command(commands);
|
|
|
|
this.selection.clear();
|
|
|
|
this._app.dispatchEvent(new CustomEvent("queue-change")); // fixme notification?
|
|
|
|
}, {label:"Enqueue", icon:"plus"});
|
|
|
|
|
|
|
|
sel.addCommandCancel();
|
2020-03-12 05:46:28 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
customElements.define("cyp-library", Library);
|