cyp/app/js/elements/library.js
2020-03-13 16:52:24 +01:00

174 lines
4.2 KiB
JavaScript

import * as html from "../html.js";
import Component from "../component.js";
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 nonempty(str) { return (str.length > 0); }
function createEnqueueCommand(node) {
if (node instanceof Song) {
return `add "${escape(node.data["file"])}"`;
} else if (node instanceof Path) {
return `add "${escape(node.file)}"`;
} else if (node instanceof Tag) {
return [
"findadd",
serializeFilter(node.createChildFilter()),
// `sort ${SORT}` // MPD >= 0.22, not yet released
].join(" ");
} else {
throw new Error(`Cannot create enqueue command for "${node.nodeName}"`);
}
}
class Library extends Component {
constructor() {
super({selection:"multi"});
this._initCommands();
}
_onAppLoad() {
this._showRoot();
}
_onComponentChange(c, isThis) {
const wasHidden = this.hidden;
this.hidden = !isThis;
if (!wasHidden && isThis) { this._showRoot(); }
}
_showRoot() {
html.clear(this);
html.button({icon:"artist"}, "Artists and albums", this)
.addEventListener("click", _ => this._listTags("AlbumArtist"));
html.button({icon:"folder"}, "Files and directories", this)
.addEventListener("click", _ => this._listPath(""));
html.button({icon:"magnify"}, "Search", this)
.addEventListener("click", _ => this._showSearch());
}
async _listTags(tag, filter = {}) {
const values = await this._mpd.listTags(tag, filter);
html.clear(this);
if ("AlbumArtist" in filter) { this._buildBack(filter); }
values.filter(nonempty).forEach(value => this._buildTag(tag, value, filter));
}
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));
}
async _listSongs(filter) {
const songs = await this._mpd.listSongs(filter);
html.clear(this);
this._buildBack(filter);
songs.forEach(song => this.appendChild(new Song(song)));
}
_showSearch() {
}
_buildTag(tag, value, filter) {
let node;
switch (tag) {
case "AlbumArtist":
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()));
break;
}
}
_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);
this.appendChild(node);
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();
}
}
customElements.define("cyp-library", Library);