refactor
This commit is contained in:
parent
5c9be9ceac
commit
5417037db6
13 changed files with 129 additions and 83 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +1,2 @@
|
|||
node_modules
|
||||
_youtube
|
||||
|
|
20
app/app.css
20
app/app.css
|
@ -9,7 +9,7 @@ html {
|
|||
body {
|
||||
box-sizing: border-box;
|
||||
font-family: lato, sans-serif;
|
||||
line-height: 1.3;
|
||||
line-height: 1;
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
text-shadow: 0 1px 1px #000;
|
||||
|
@ -75,12 +75,12 @@ nav ul li {
|
|||
text-align: center;
|
||||
flex: 1 0 0;
|
||||
line-height: 40px;
|
||||
}
|
||||
nav ul li:hover {
|
||||
background-color: red;
|
||||
cursor: pointer;
|
||||
border-top: 4px solid transparent;
|
||||
border-bottom: 4px solid transparent;
|
||||
}
|
||||
nav ul li.active {
|
||||
background-color: green;
|
||||
border-top-color: dodgerblue;
|
||||
}
|
||||
#player {
|
||||
display: flex;
|
||||
|
@ -133,13 +133,13 @@ nav ul li.active {
|
|||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.component .grid li h2 {
|
||||
flex-grow: 1;
|
||||
font-size: 100%;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
@ -171,13 +171,13 @@ nav ul li.active {
|
|||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
#queue .grid li h2 {
|
||||
flex-grow: 1;
|
||||
font-size: 100%;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
@ -212,13 +212,13 @@ nav ul li.active {
|
|||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
#library .grid li h2 {
|
||||
flex-grow: 1;
|
||||
font-size: 100%;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
@ -250,13 +250,13 @@ nav ul li.active {
|
|||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
#fs .grid li h2 {
|
||||
flex-grow: 1;
|
||||
font-size: 100%;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
@ -288,13 +288,13 @@ nav ul li.active {
|
|||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
#playlists .grid li h2 {
|
||||
flex-grow: 1;
|
||||
font-size: 100%;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ html {
|
|||
body {
|
||||
box-sizing: border-box;
|
||||
font-family: lato, sans-serif;
|
||||
line-height: 1.3;
|
||||
line-height: 1;
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
text-shadow: 0 1px 1px #000;
|
||||
|
|
|
@ -17,13 +17,13 @@
|
|||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 4px;
|
||||
white-space: nowrap;
|
||||
|
||||
h2 {
|
||||
flex-grow: 1;
|
||||
font-size: 100%;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
|
|
@ -9,11 +9,13 @@ nav ul {
|
|||
text-align: center;
|
||||
flex: 1 0 0;
|
||||
line-height: 40px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover { background-color:red;}
|
||||
border-top: 4px solid transparent;
|
||||
border-bottom: 4px solid transparent;
|
||||
|
||||
&.active {
|
||||
background-color: green;
|
||||
border-top-color: dodgerblue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,8 +28,8 @@
|
|||
<main>
|
||||
<section id="queue">
|
||||
<header>
|
||||
<button class="clear"></button>
|
||||
<button class="save"></button>
|
||||
<button class="clear" title="Clear the queue"></button>
|
||||
<button class="save" title="Save the queue"></button>
|
||||
</header>
|
||||
<ul class="grid"></ul>
|
||||
</section>
|
||||
|
|
|
@ -24,13 +24,13 @@ function buildHeader(path) {
|
|||
function buildDirectory(data, parent) {
|
||||
let path = data["directory"];
|
||||
let name = path.split("/").pop();
|
||||
let node = ui.group(ui.GROUP_DIRECTORY, name, path, parent);
|
||||
let node = ui.group(ui.CTX_FS, name, path, parent);
|
||||
node.addEventListener("click", e => list(path));
|
||||
return node;
|
||||
}
|
||||
|
||||
function buildFile(data, parent) {
|
||||
return ui.song(ui.SONG_FILE, data, parent);
|
||||
return ui.song(ui.CTX_FS, data, parent);
|
||||
}
|
||||
|
||||
function buildResults(results) {
|
||||
|
|
|
@ -46,7 +46,7 @@ function serializeFilter(filter) {
|
|||
return `"${escape(filterStr)}"`;
|
||||
}
|
||||
|
||||
function escape(str) {
|
||||
export function escape(str) {
|
||||
return str.replace(/(['"\\])/g, "\\$1");
|
||||
}
|
||||
|
||||
|
@ -86,13 +86,9 @@ export async function listPlaylists() {
|
|||
return (list instanceof Array ? list : [list]);
|
||||
}
|
||||
|
||||
export async function enqueue(urlOrFilter, sort = null) {
|
||||
if (typeof(urlOrFilter) == "string") {
|
||||
return command(`add "${escape(urlOrFilter)}"`);
|
||||
}
|
||||
|
||||
export async function enqueueByFilter(filter, sort = null) {
|
||||
let tokens = ["findadd"];
|
||||
tokens.push(serializeFilter(urlOrFilter));
|
||||
tokens.push(serializeFilter(filter));
|
||||
// sort && tokens.push("sort", sort); FIXME not implemented in MPD
|
||||
return command(tokens.join(" "));
|
||||
}
|
||||
|
@ -133,12 +129,9 @@ export async function albumArt(songUrl) {
|
|||
if (data.length >= Number(metadata["size"])) { return data; }
|
||||
offset += Number(metadata["binary"]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function save(name) {
|
||||
return command(`save "${escape(name)}"`);
|
||||
}
|
||||
|
||||
export async function init() {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
|
|
129
app/js/lib/ui.js
129
app/js/lib/ui.js
|
@ -4,29 +4,40 @@ import * as pubsub from "./pubsub.js";
|
|||
import * as format from "./format.js";
|
||||
import * as player from "../player.js";
|
||||
|
||||
export const SONG_FILE = 1;
|
||||
export const SONG_LIBRARY = 2;
|
||||
export const SONG_QUEUE = 3;
|
||||
export const GROUP_DIRECTORY = 4;
|
||||
export const GROUP_LIBRARY = 5;
|
||||
export const CTX_FS = 1;
|
||||
export const CTX_QUEUE = 2;
|
||||
export const CTX_LIBRARY = 3;
|
||||
|
||||
const TYPE_ID = 1;
|
||||
const TYPE_URL = 2;
|
||||
const TYPE_FILTER = 3;
|
||||
const TYPE_PLAYLIST = 4;
|
||||
|
||||
const SORT = "-Track";
|
||||
|
||||
function enqueue(type, what) {
|
||||
switch (type) {
|
||||
case TYPE_URL: return mpd.command(`add "${mpd.escape(what)}"`); break;
|
||||
case TYPE_FILTER: return mpd.enqueueByFilter(what, SORT); break;
|
||||
case TYPE_PLAYLIST: return mpd.command(`load "${mpd.escape(what)}"`); break;
|
||||
}
|
||||
}
|
||||
|
||||
function fileName(data) {
|
||||
return data["file"].split("/").pop();
|
||||
}
|
||||
|
||||
function formatTitle(type, data) {
|
||||
switch (type) {
|
||||
case SONG_FILE:
|
||||
function formatTitle(ctx, data) {
|
||||
switch (ctx) {
|
||||
case CTX_FS:
|
||||
return `🎵 ${fileName(data)}`;
|
||||
break;
|
||||
|
||||
case SONG_LIBRARY:
|
||||
case CTX_LIBRARY:
|
||||
return data["Artist"] || fileName(data);
|
||||
break;
|
||||
|
||||
case SONG_QUEUE:
|
||||
case CTX_QUEUE:
|
||||
let tokens = [];
|
||||
data["Artist"] && tokens.push(data["Artist"]);
|
||||
data["Title"] && tokens.push(data["Title"]);
|
||||
|
@ -36,77 +47,97 @@ function formatTitle(type, data) {
|
|||
}
|
||||
}
|
||||
|
||||
function playButton(id, parent) {
|
||||
function playButton(type, what, parent) {
|
||||
let button = html.button({icon:"play", title:"Play"}, "", parent);
|
||||
button.addEventListener("click", async e => {
|
||||
await mpd.command(`playid ${id}`);
|
||||
if (type == TYPE_ID) {
|
||||
await mpd.command(`playid ${what}`);
|
||||
} else {
|
||||
await mpd.command("clear");
|
||||
await enqueue(type, what);
|
||||
await mpd.command("play");
|
||||
pubsub.publish("queue-change");
|
||||
}
|
||||
player.update();
|
||||
});
|
||||
|
||||
return button;
|
||||
|
||||
}
|
||||
|
||||
function deleteButton(id, parent) {
|
||||
let button = html.button({icon:"close", title:"Delete from queue"}, "", parent);
|
||||
function deleteButton(type, id, parent) {
|
||||
let title;
|
||||
|
||||
switch (type) {
|
||||
case TYPE_ID: title = "Delete from queue"; break;
|
||||
case TYPE_PLAYLIST: title = "Delete playlist"; break;
|
||||
}
|
||||
|
||||
let button = html.button({icon:"close", title}, "", parent);
|
||||
button.addEventListener("click", async e => {
|
||||
await mpd.command(`deleteid ${id}`);
|
||||
pubsub.publish("queue-change");
|
||||
switch (type) {
|
||||
case TYPE_ID:
|
||||
await mpd.command(`deleteid ${id}`);
|
||||
pubsub.publish("queue-change");
|
||||
return;
|
||||
case TYPE_PLAYLIST:
|
||||
let ok = confirm(`Really delete playlist '${id}'?`);
|
||||
if (!ok) { return; }
|
||||
await mpd.command(`rm "${mpd.escape(id)}"`);
|
||||
pubsub.publish("playlists-change");
|
||||
return;
|
||||
}
|
||||
});
|
||||
return button;
|
||||
}
|
||||
|
||||
function addAndPlayButton(urlOrFilter, parent) {
|
||||
let button = html.button({icon:"play", title:"Play"}, "", parent);
|
||||
button.addEventListener("click", async e => {
|
||||
e.stopPropagation();
|
||||
await mpd.command("clear");
|
||||
await mpd.enqueue(urlOrFilter, SORT);
|
||||
await mpd.command("play");
|
||||
pubsub.publish("queue-change");
|
||||
player.update();
|
||||
});
|
||||
return button;
|
||||
}
|
||||
|
||||
function addButton(urlOrFilter, parent) {
|
||||
function addButton(type, what, parent) {
|
||||
let button = html.button({icon:"plus", title:"Add to queue"}, "", parent);
|
||||
button.addEventListener("click", async e => {
|
||||
e.stopPropagation();
|
||||
await mpd.enqueue(urlOrFilter, SORT);
|
||||
await enqueue(type, what);
|
||||
pubsub.publish("queue-change");
|
||||
// fixme notification?
|
||||
});
|
||||
return button;
|
||||
}
|
||||
|
||||
export function song(type, data, parent) {
|
||||
export function song(ctx, data, parent) {
|
||||
let node = html.node("li", {}, "", parent);
|
||||
|
||||
let title = formatTitle(type, data);
|
||||
let title = formatTitle(ctx, data);
|
||||
html.node("h2", {}, title, node);
|
||||
|
||||
html.node("span", {className:"duration"}, format.time(Number(data["duration"])), node);
|
||||
|
||||
if (type == SONG_QUEUE) {
|
||||
let id = data["Id"];
|
||||
node.dataset.songId = id;
|
||||
playButton(id, node);
|
||||
deleteButton(id, node);
|
||||
} else {
|
||||
let url = data["file"];
|
||||
addAndPlayButton(url, node);
|
||||
addButton(url, node);
|
||||
switch (ctx) {
|
||||
case CTX_QUEUE:
|
||||
let id = data["Id"];
|
||||
node.dataset.songId = id;
|
||||
playButton(TYPE_ID, id, node);
|
||||
deleteButton(TYPE_ID, id, node);
|
||||
break;
|
||||
|
||||
case CTX_FS:
|
||||
let url = data["file"];
|
||||
playButton(TYPE_URL, url, node);
|
||||
addButton(TYPE_URL, url, node);
|
||||
break;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
export function group(type, label, urlOrFilter, parent) {
|
||||
export function group(ctx, label, urlOrFilter, parent) {
|
||||
let node = html.node("li", {}, "", parent);
|
||||
|
||||
if (type == GROUP_DIRECTORY) { label = `📁 ${label}`; }
|
||||
if (ctx == CTX_FS) { label = `📁 ${label}`; }
|
||||
html.node("h2", {}, label, node);
|
||||
|
||||
addAndPlayButton(urlOrFilter, node);
|
||||
addButton(urlOrFilter, node);
|
||||
let type = (ctx == CTX_FS ? TYPE_URL : TYPE_FILTER);
|
||||
|
||||
playButton(type, urlOrFilter, node);
|
||||
addButton(type, urlOrFilter, node);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
@ -117,9 +148,9 @@ export function playlist(name, parent) {
|
|||
html.icon("playlist-music", node)
|
||||
html.node("h2", {}, name, node);
|
||||
|
||||
// addAndPlayButton(url, node);
|
||||
// addButton(url, node);
|
||||
// deleteButton(id, node);
|
||||
playButton(TYPE_PLAYLIST, name, node);
|
||||
addButton(TYPE_PLAYLIST, name, node);
|
||||
deleteButton(TYPE_PLAYLIST, name, node);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
|
|
@ -29,14 +29,14 @@ function buildHeader(filter) {
|
|||
|
||||
function buildAlbum(album, filter, parent) {
|
||||
let childFilter = Object.assign({}, filter, {"Album": album});
|
||||
let node = ui.group(ui.GROUP_LIBRARY, album, childFilter, parent);
|
||||
let node = ui.group(ui.CTX_LIBRARY, album, childFilter, parent);
|
||||
node.addEventListener("click", e => listSongs(childFilter));
|
||||
return node;
|
||||
}
|
||||
|
||||
function buildArtist(artist, filter, parent) {
|
||||
let childFilter = Object.assign({}, filter, {"Artist": artist});
|
||||
let node = ui.group(ui.GROUP_LIBRARY, artist, childFilter, parent);
|
||||
let node = ui.group(ui.CTX_LIBRARY, artist, childFilter, parent);
|
||||
node.addEventListener("click", e => listAlbums(childFilter));
|
||||
return node;
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ function buildSongs(songs, filter) {
|
|||
ul.classList.remove("tiles");
|
||||
ul.classList.add("grid");
|
||||
|
||||
songs.map(song => ui.song(ui.SONG_LIBRARY, song, ul));
|
||||
songs.map(song => ui.song(ui.CTX_LIBRARY, song, ul));
|
||||
}
|
||||
|
||||
function buildAlbums(albums, filter) {
|
||||
|
|
|
@ -17,10 +17,15 @@ async function syncLists() {
|
|||
buildLists(lists);
|
||||
}
|
||||
|
||||
function onPlaylistsChange(message, publisher, data) {
|
||||
syncLists();
|
||||
}
|
||||
|
||||
export async function activate() {
|
||||
syncLists();
|
||||
}
|
||||
|
||||
export function init(n) {
|
||||
node = n;
|
||||
pubsub.subscribe("playlists-change", onPlaylistsChange);
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ function buildSongs(songs) {
|
|||
let ul = node.querySelector("ul");
|
||||
html.clear(ul);
|
||||
|
||||
songs.map(song => ui.song(ui.SONG_QUEUE, song, ul));
|
||||
songs.map(song => ui.song(ui.CTX_QUEUE, song, ul));
|
||||
|
||||
updateCurrent();
|
||||
}
|
||||
|
@ -59,6 +59,6 @@ export function init(n) {
|
|||
save.addEventListener("click", e => {
|
||||
let name = prompt("Save current queue as a playlist?", "name");
|
||||
if (name === null) { return; }
|
||||
mpd.save(name);
|
||||
mpd.command(`save "${mpd.escape(name)}"`);
|
||||
});
|
||||
}
|
||||
|
|
14
app/tsconfig.json
Normal file
14
app/tsconfig.json
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"noEmit": true,
|
||||
"lib": ["es2017", "dom"],
|
||||
"target": "es6",
|
||||
"baseUrl": "js/app",
|
||||
|
||||
"noImplicitReturns": true,
|
||||
"strictFunctionTypes": true
|
||||
},
|
||||
"files": ["js/app.js"]
|
||||
}
|
Loading…
Reference in a new issue