This commit is contained in:
Ondrej Zara 2019-03-28 22:52:57 +01:00
parent e291f0feca
commit f8442348ad
17 changed files with 151 additions and 114 deletions

View file

@ -38,6 +38,7 @@ button {
border: none;
line-height: 1;
cursor: pointer;
flex-shrink: 0;
}
@font-face {
font-family: 'Lato';
@ -71,6 +72,9 @@ nav ul {
display: flex;
flex-direction: row;
}
nav ul .icon {
margin-right: 4px;
}
nav ul li {
text-align: center;
flex: 1 0 0;
@ -85,15 +89,7 @@ nav ul li.active {
#player {
display: flex;
flex-direction: row;
}
#player .art img {
vertical-align: top;
}
#player .info {
flex-grow: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
align-items: center;
}
#player:not([data-state=play]) .pause {
display: none;
@ -105,7 +101,20 @@ nav ul li.active {
#player:not([data-flags~=repeat]) .repeat {
opacity: 0.5;
}
#player .icon {
#player .art img {
vertical-align: top;
}
#player .info {
flex-grow: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
#player .controls {
white-space: nowrap;
text-align: center;
}
#player .controls .icon {
width: 64px;
}
#player .misc {
@ -135,7 +144,7 @@ nav ul li.active {
padding: 0 4px;
white-space: nowrap;
}
.component .grid li h2 {
.component .grid h2 {
flex-grow: 1;
font-size: 100%;
font-weight: normal;
@ -143,17 +152,17 @@ nav ul li.active {
overflow: hidden;
text-overflow: ellipsis;
}
.component .grid li button {
flex-shrink: 0;
}
@media (pointer: coarse) {
.component .grid li button .icon {
width: 32px;
}
.component .grid h2 .icon {
margin-right: 4px;
}
.component .grid li:nth-child(odd) {
background-color: #555;
}
@media (pointer: coarse) {
.component .grid .icon {
width: 32px;
}
}
#queue {
height: 100%;
display: flex;
@ -173,7 +182,7 @@ nav ul li.active {
padding: 0 4px;
white-space: nowrap;
}
#queue .grid li h2 {
#queue .grid h2 {
flex-grow: 1;
font-size: 100%;
font-weight: normal;
@ -181,19 +190,19 @@ nav ul li.active {
overflow: hidden;
text-overflow: ellipsis;
}
#queue .grid li button {
flex-shrink: 0;
}
@media (pointer: coarse) {
#queue .grid li button .icon {
width: 32px;
}
#queue .grid h2 .icon {
margin-right: 4px;
}
#queue .grid li:nth-child(odd) {
background-color: #555;
}
#queue .current {
font-weight: bold;
@media (pointer: coarse) {
#queue .grid .icon {
width: 32px;
}
}
#queue .current * {
font-weight: bold !important;
}
#library {
height: 100%;
@ -214,7 +223,7 @@ nav ul li.active {
padding: 0 4px;
white-space: nowrap;
}
#library .grid li h2 {
#library .grid h2 {
flex-grow: 1;
font-size: 100%;
font-weight: normal;
@ -222,17 +231,17 @@ nav ul li.active {
overflow: hidden;
text-overflow: ellipsis;
}
#library .grid li button {
flex-shrink: 0;
}
@media (pointer: coarse) {
#library .grid li button .icon {
width: 32px;
}
#library .grid h2 .icon {
margin-right: 4px;
}
#library .grid li:nth-child(odd) {
background-color: #555;
}
@media (pointer: coarse) {
#library .grid .icon {
width: 32px;
}
}
#fs {
height: 100%;
display: flex;
@ -252,7 +261,7 @@ nav ul li.active {
padding: 0 4px;
white-space: nowrap;
}
#fs .grid li h2 {
#fs .grid h2 {
flex-grow: 1;
font-size: 100%;
font-weight: normal;
@ -260,17 +269,17 @@ nav ul li.active {
overflow: hidden;
text-overflow: ellipsis;
}
#fs .grid li button {
flex-shrink: 0;
}
@media (pointer: coarse) {
#fs .grid li button .icon {
width: 32px;
}
#fs .grid h2 .icon {
margin-right: 4px;
}
#fs .grid li:nth-child(odd) {
background-color: #555;
}
@media (pointer: coarse) {
#fs .grid .icon {
width: 32px;
}
}
#playlists {
height: 100%;
display: flex;
@ -290,7 +299,7 @@ nav ul li.active {
padding: 0 4px;
white-space: nowrap;
}
#playlists .grid li h2 {
#playlists .grid h2 {
flex-grow: 1;
font-size: 100%;
font-weight: normal;
@ -298,14 +307,14 @@ nav ul li.active {
overflow: hidden;
text-overflow: ellipsis;
}
#playlists .grid li button {
flex-shrink: 0;
}
@media (pointer: coarse) {
#playlists .grid li button .icon {
width: 32px;
}
#playlists .grid h2 .icon {
margin-right: 4px;
}
#playlists .grid li:nth-child(odd) {
background-color: #555;
}
@media (pointer: coarse) {
#playlists .grid .icon {
width: 32px;
}
}

View file

@ -37,9 +37,9 @@ button {
border: none;
line-height: 1;
cursor: pointer;
flex-shrink: 0;
}
@import "font.less";
@import "icons.less";
@import "main.less";

View file

@ -18,6 +18,7 @@
align-items: center;
padding: 0 4px;
white-space: nowrap;
}
h2 {
flex-grow: 1;
@ -26,19 +27,16 @@
margin: 0;
overflow: hidden;
text-overflow: ellipsis;
}
button {
flex-shrink: 0;
@media (pointer: coarse) {
.icon { width: 32px; }
}
}
.icon { margin-right: 4px; }
}
li:nth-child(odd) {
background-color: #555;
}
@media (pointer: coarse) {
.icon { width: 32px; }
}
}
}

View file

@ -5,6 +5,10 @@ nav ul {
display: flex;
flex-direction: row;
.icon {
margin-right: 4px;
}
li {
text-align: center;
flex: 1 0 0;

View file

@ -1,8 +1,14 @@
#player {
display: flex;
flex-direction: row;
align-items: center;
&:not([data-state=play]) .pause { display: none; }
&[data-state=play] .play { display: none; }
&:not([data-flags~=random]) .random, &:not([data-flags~=repeat]) .repeat { opacity: 0.5; }
.art img { vertical-align: top; }
.info {
flex-grow: 1;
white-space: nowrap;
@ -10,12 +16,10 @@
text-overflow: ellipsis;
}
&:not([data-state=play]) .pause { display: none; }
&[data-state=play] .play { display: none; }
&:not([data-flags~=random]) .random, &:not([data-flags~=repeat]) .repeat { opacity: 0.5; }
.icon {
width: 64px;
.controls {
white-space: nowrap;
text-align: center;
.icon { width: 64px; }
}
.misc {

View file

@ -1,5 +1,4 @@
#queue {
.component;
.current { font-weight: bold; }
.current * { font-weight: bold !important; }
}

1
app/icons/download.svg Normal file
View file

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M5,20H19V18H5M19,9H15V3H9V9H5L12,16L19,9Z" /></svg>

After

Width:  |  Height:  |  Size: 336 B

1
app/icons/folder.svg Normal file
View file

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6H12L10,4Z" /></svg>

After

Width:  |  Height:  |  Size: 388 B

View file

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M4,6H2V20A2,2 0 0,0 4,22H18V20H4M18,7H15V12.5A2.5,2.5 0 0,1 12.5,15A2.5,2.5 0 0,1 10,12.5A2.5,2.5 0 0,1 12.5,10C13.07,10 13.58,10.19 14,10.5V5H18M20,2H8A2,2 0 0,0 6,4V16A2,2 0 0,0 8,18H20A2,2 0 0,0 22,16V4A2,2 0 0,0 20,2Z" /></svg>

After

Width:  |  Height:  |  Size: 516 B

1
app/icons/music.svg Normal file
View file

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M21,3V15.5A3.5,3.5 0 0,1 17.5,19A3.5,3.5 0 0,1 14,15.5A3.5,3.5 0 0,1 17.5,12C18.04,12 18.55,12.12 19,12.34V6.47L9,8.6V17.5A3.5,3.5 0 0,1 5.5,21A3.5,3.5 0 0,1 2,17.5A3.5,3.5 0 0,1 5.5,14C6.04,14 6.55,14.12 7,14.34V6L21,3Z" /></svg>

After

Width:  |  Height:  |  Size: 515 B

View file

@ -14,22 +14,26 @@
<h2 class="title"></h2>
<span class="artist-album"></span>
</div>
<div class="controls">
<button class="prev" data-icon="rewind"></button>
<button class="play" data-icon="play"></button>
<button class="pause" data-icon="pause"></button>
<button class="next" data-icon="fast-forward"></button>
<div>
<span class="elapsed"></span>/<span class="duration"></span>
<button class="prev"></button>
<button class="play"></button>
<button class="pause"></button>
<button class="next"></button>
</div>
</div>
<div class="misc">
<button class="repeat"></button>
<button class="random"></button>
<button class="repeat" data-icon="repeat"></button>
<button class="random" data-icon="shuffle"></button>
</div>
</section>
</header>
<main>
<section id="queue">
<header>
<button class="clear" title="Clear the queue"></button>
<button class="save" title="Save the queue"></button>
<button class="clear" data-icon="close" title="Clear the queue"></button>
<button class="save" data-icon="content-save" title="Save the queue"></button>
</header>
<ul class="grid"></ul>
</section>
@ -45,20 +49,20 @@
<ul class="grid"></ul>
</section>
<section id="yt">
<button class="go">Go!</button>
<button class="go" data-icon="download">Go!</button>
</section>
</main>
<footer>
<nav>
<ul>
<li data-for="queue">
<li data-for="queue" data-icon="music">
Q
<span id="queue-length"></span>
</li>
<li data-for="playlists">Playlists</li>
<li data-for="library">Library</li>
<li data-for="fs">FS</li>
<li data-for="yt">YouTube</li>
<li data-for="playlists" data-icon="playlist-music">Playlists</li>
<li data-for="library" data-icon="library-music">Library</li>
<li data-for="fs" data-icon="folder">FS</li>
<li data-for="yt" data-icon="download">YouTube</li>
</ul>
</nav>
</footer>

View file

@ -1,6 +1,7 @@
import * as nav from "./nav.js";
import * as mpd from "./lib/mpd.js";
import * as player from "./player.js";
import * as html from "./lib/html.js";
import * as queue from "./queue.js";
import * as library from "./library.js";
@ -11,6 +12,8 @@ import * as yt from "./yt.js";
const components = { queue, library, fs, playlists, yt };
export function activate(what) {
location.hash = what;
for (let id in components) {
let node = document.querySelector(`#${id}`);
if (what == id) {
@ -23,7 +26,15 @@ export function activate(what) {
nav.active(what);
}
function initIcons() {
Array.from(document.querySelectorAll("[data-icon]")).forEach(node => {
let icon = html.icon(node.dataset.icon);
node.insertBefore(icon, node.firstChild);
});
}
async function init() {
initIcons();
await mpd.init();
nav.init(document.querySelector("nav"));

View file

@ -1,4 +1,7 @@
let ICONS={};
ICONS["library-music"] = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 24 24">
<path d="M4,6H2V20A2,2 0 0,0 4,22H18V20H4M18,7H15V12.5A2.5,2.5 0 0,1 12.5,15A2.5,2.5 0 0,1 10,12.5A2.5,2.5 0 0,1 12.5,10C13.07,10 13.58,10.19 14,10.5V5H18M20,2H8A2,2 0 0,0 6,4V16A2,2 0 0,0 8,18H20A2,2 0 0,0 22,16V4A2,2 0 0,0 20,2Z"/>
</svg>`;
ICONS["pause"] = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 24 24">
<path d="M14,19H18V5H14M6,19H10V5H6V19Z"/>
</svg>`;
@ -47,9 +50,15 @@ ICONS["minus-circle-outline"] = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:x
ICONS["playlist-music"] = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 24 24">
<path d="M15,6H3V8H15V6M15,10H3V12H15V10M3,16H11V14H3V16M17,6V14.18C16.69,14.07 16.35,14 16,14A3,3 0 0,0 13,17A3,3 0 0,0 16,20A3,3 0 0,0 19,17V8H22V6H17Z"/>
</svg>`;
ICONS["music"] = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 24 24">
<path d="M21,3V15.5A3.5,3.5 0 0,1 17.5,19A3.5,3.5 0 0,1 14,15.5A3.5,3.5 0 0,1 17.5,12C18.04,12 18.55,12.12 19,12.34V6.47L9,8.6V17.5A3.5,3.5 0 0,1 5.5,21A3.5,3.5 0 0,1 2,17.5A3.5,3.5 0 0,1 5.5,14C6.04,14 6.55,14.12 7,14.34V6L21,3Z"/>
</svg>`;
ICONS["repeat"] = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 24 24">
<path d="M17,17H7V14L3,18L7,22V19H19V13H17M7,7H17V10L21,6L17,2V5H5V11H7V7Z"/>
</svg>`;
ICONS["folder"] = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 24 24">
<path d="M10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6H12L10,4Z"/>
</svg>`;
ICONS["close-circle-outline"] = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 24 24">
<path d="M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2C6.47,2 2,6.47 2,12C2,17.53 6.47,22 12,22C17.53,22 22,17.53 22,12C22,6.47 17.53,2 12,2M14.59,8L12,10.59L9.41,8L8,9.41L10.59,12L8,14.59L9.41,16L12,13.41L14.59,16L16,14.59L13.41,12L16,9.41L14.59,8Z"/>
</svg>`;
@ -59,6 +68,9 @@ ICONS["pause-circle-outline"] = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:x
ICONS["plus-circle-outline"] = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 24 24">
<path d="M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M13,7H11V11H7V13H11V17H13V13H17V11H13V7Z"/>
</svg>`;
ICONS["download"] = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 24 24">
<path d="M5,20H19V18H5M19,9H15V3H9V9H5L12,16L19,9Z"/>
</svg>`;
ICONS["play-circle"] = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 24 24">
<path d="M10,16.5V7.5L16,12M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z"/>
</svg>`;

View file

@ -30,9 +30,7 @@ function fileName(data) {
function formatTitle(ctx, data) {
let tokens = [];
switch (ctx) {
case CTX_FS:
return `🎵 ${fileName(data)}`;
break;
case CTX_FS: return fileName(data); break;
case CTX_LIBRARY:
data["Track"] && tokens.push(data["Track"].padStart(2, "0"));
@ -108,8 +106,11 @@ function addButton(type, what, parent) {
export function song(ctx, data, parent) {
let node = html.node("li", {}, "", parent);
let title = formatTitle(ctx, data);
html.node("h2", {}, title, node);
let h2 = html.node("h2", {}, "", node);
if (ctx == CTX_FS || ctx == CTX_LIBRARY) { html.icon("music", h2); }
html.text(title, h2);
html.node("span", {className:"duration"}, format.time(Number(data["duration"])), node);
@ -135,8 +136,9 @@ export function song(ctx, data, parent) {
export function group(ctx, label, urlOrFilter, parent) {
let node = html.node("li", {}, "", parent);
if (ctx == CTX_FS) { label = `📁 ${label}`; }
html.node("h2", {}, label, node);
let h2 = html.node("h2", {}, "", node);
if (ctx == CTX_FS) { html.icon("folder", h2); }
html.text(label, h2);
let type = (ctx == CTX_FS ? TYPE_URL : TYPE_FILTER);
@ -149,8 +151,9 @@ export function group(ctx, label, urlOrFilter, parent) {
export function playlist(name, parent) {
let node = html.node("li", {}, "", parent);
html.icon("playlist-music", node)
html.node("h2", {}, name, node);
let h2 = html.node("h2", {}, "", node);
html.icon("playlist-music", h2)
html.text(name, h2);
playButton(TYPE_PLAYLIST, name, node);
addButton(TYPE_PLAYLIST, name, node);

View file

@ -16,7 +16,7 @@ function sync(data) {
if (data["file"] != current["file"]) { // changed song
DOM.duration.textContent = format.time(Number(data["duration"] || 0));
DOM.title.textContent = data["Title"] || "";
DOM.title.textContent = data["Title"] || data["file"].split("/").pop();
DOM["artist-album"].textContent = format.artistAlbum(data["Artist"], data["Album"]);
pubsub.publish("song-change", null, data);
}
@ -76,12 +76,5 @@ export function init(n) {
DOM.random.addEventListener("click", e => command(`random ${current["random"] == "1" ? "0" : "1"}`));
DOM.repeat.addEventListener("click", e => command(`repeat ${current["repeat"] == "1" ? "0" : "1"}`));
DOM.play.appendChild(html.icon("play"));
DOM.pause.appendChild(html.icon("pause"));
DOM.prev.appendChild(html.icon("rewind"));
DOM.next.appendChild(html.icon("fast-forward"));
DOM.random.appendChild(html.icon("shuffle"));
DOM.repeat.appendChild(html.icon("repeat"));
update();
}

View file

@ -47,16 +47,12 @@ export function init(n) {
pubsub.subscribe("song-change", onSongChange);
pubsub.subscribe("queue-change", onQueueChange);
let clear = node.querySelector(".clear");
clear.appendChild(html.icon("close"));
clear.addEventListener("click", async e => {
node.querySelector(".clear").addEventListener("click", async e => {
await mpd.command("clear");
syncQueue();
});
let save = node.querySelector(".save");
save.appendChild(html.icon("content-save"));
save.addEventListener("click", e => {
node.querySelector(".save").addEventListener("click", e => {
let name = prompt("Save current queue as a playlist?", "name");
if (name === null) { return; }
mpd.command(`save "${mpd.escape(name)}"`);

View file

@ -22,5 +22,5 @@ export async function activate() {}
export function init(n) {
node = n;
node.querySelector(".go").addEventListener("click", onClick);
let button = node.querySelector(".go").addEventListener("click", onClick);
}