This commit is contained in:
Ondrej Zara 2019-04-02 19:19:46 +02:00
parent a55201f928
commit 0873dcfe11
14 changed files with 213 additions and 90 deletions

View file

@ -7,6 +7,8 @@ html {
background-color: var(--fg);
}
body {
display: flex;
flex-direction: column;
box-sizing: border-box;
font-family: lato, sans-serif;
line-height: 1.25;
@ -16,8 +18,6 @@ body {
max-width: 800px;
margin: 0 auto;
overflow: hidden;
display: flex;
flex-direction: column;
height: 100vh;
}
body > header,
@ -34,6 +34,10 @@ button {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
display: flex;
flex-direction: row;
align-items: center;
display: inline-flex;
background-color: transparent;
padding: 0;
border: none;
@ -41,22 +45,6 @@ button {
cursor: pointer;
flex-shrink: 0;
}
button .icon {
vertical-align: middle;
}
.long-line {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.multiline {
display: flex;
flex-direction: row;
align-items: center;
}
.multiline h2 {
font-weight: normal;
}
@font-face {
font-family: 'Lato';
src: url('font/LatoLatin-Regular.woff2') format('woff2');
@ -77,6 +65,28 @@ button .icon {
.icon circle:not([fill]) {
fill: currentColor;
}
.flex-row {
display: flex;
flex-direction: row;
align-items: center;
}
.flex-column {
display: flex;
flex-direction: column;
}
.long-line {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.multiline {
display: flex;
flex-direction: row;
align-items: center;
}
.multiline h2 {
font-weight: normal;
}
main {
flex-grow: 1;
overflow: hidden;
@ -87,6 +97,7 @@ nav ul {
list-style: none;
display: flex;
flex-direction: row;
align-items: center;
}
nav ul .icon {
display: block;
@ -158,6 +169,16 @@ nav ul li.active {
}
.component header {
display: flex;
flex-direction: row;
align-items: center;
padding: 8px;
}
.component header button {
font-size: var(--font-size-large);
font-weight: bold;
}
.component header button .icon {
margin-right: var(--icon-spacing);
}
.component ul {
flex-grow: 1;
@ -193,12 +214,12 @@ nav ul li.active {
.component li:not(.has-art) {
padding: 8px;
}
.component li button .icon {
width: 32px;
}
.component li:nth-child(odd) {
background-color: #555;
}
.component button .icon {
width: 32px;
}
#queue {
height: 100%;
display: flex;
@ -206,6 +227,16 @@ nav ul li.active {
}
#queue header {
display: flex;
flex-direction: row;
align-items: center;
padding: 8px;
}
#queue header button {
font-size: var(--font-size-large);
font-weight: bold;
}
#queue header button .icon {
margin-right: var(--icon-spacing);
}
#queue ul {
flex-grow: 1;
@ -241,12 +272,12 @@ nav ul li.active {
#queue li:not(.has-art) {
padding: 8px;
}
#queue li button .icon {
width: 32px;
}
#queue li:nth-child(odd) {
background-color: #555;
}
#queue button .icon {
width: 32px;
}
#queue .current {
color: var(--primary);
}
@ -257,6 +288,16 @@ nav ul li.active {
}
#library header {
display: flex;
flex-direction: row;
align-items: center;
padding: 8px;
}
#library header button {
font-size: var(--font-size-large);
font-weight: bold;
}
#library header button .icon {
margin-right: var(--icon-spacing);
}
#library ul {
flex-grow: 1;
@ -292,11 +333,20 @@ nav ul li.active {
#library li:not(.has-art) {
padding: 8px;
}
#library li button .icon {
width: 32px;
}
#library li:nth-child(odd) {
background-color: #555;
}
#library button .icon {
width: 32px;
#library header {
white-space: pre;
}
#library .search {
order: 1;
}
#library .search.open ~ * {
display: none;
}
#library .art img,
#library .art .icon {
@ -333,6 +383,16 @@ nav ul li.active {
}
#fs header {
display: flex;
flex-direction: row;
align-items: center;
padding: 8px;
}
#fs header button {
font-size: var(--font-size-large);
font-weight: bold;
}
#fs header button .icon {
margin-right: var(--icon-spacing);
}
#fs ul {
flex-grow: 1;
@ -368,11 +428,20 @@ nav ul li.active {
#fs li:not(.has-art) {
padding: 8px;
}
#fs li button .icon {
width: 32px;
}
#fs li:nth-child(odd) {
background-color: #555;
}
#fs button .icon {
width: 32px;
#fs header {
white-space: pre;
}
#fs .search {
order: 1;
}
#fs .search.open ~ * {
display: none;
}
#fs .group {
cursor: pointer;
@ -392,6 +461,16 @@ nav ul li.active {
}
#playlists header {
display: flex;
flex-direction: row;
align-items: center;
padding: 8px;
}
#playlists header button {
font-size: var(--font-size-large);
font-weight: bold;
}
#playlists header button .icon {
margin-right: var(--icon-spacing);
}
#playlists ul {
flex-grow: 1;
@ -427,12 +506,12 @@ nav ul li.active {
#playlists li:not(.has-art) {
padding: 8px;
}
#playlists li button .icon {
width: 32px;
}
#playlists li:nth-child(odd) {
background-color: #555;
}
#playlists button .icon {
width: 32px;
}
#playlists .info {
display: flex;
flex-direction: row;
@ -450,6 +529,16 @@ nav ul li.active {
}
#yt header {
display: flex;
flex-direction: row;
align-items: center;
padding: 8px;
}
#yt header button {
font-size: var(--font-size-large);
font-weight: bold;
}
#yt header button .icon {
margin-right: var(--icon-spacing);
}
#yt ul {
flex-grow: 1;
@ -485,12 +574,12 @@ nav ul li.active {
#yt li:not(.has-art) {
padding: 8px;
}
#yt li button .icon {
width: 32px;
}
#yt li:nth-child(odd) {
background-color: #555;
}
#yt button .icon {
width: 32px;
}
#yt .go {
width: 96px;
height: 96px;
@ -524,8 +613,12 @@ nav ul li.active {
}
.search {
display: flex;
flex-direction: row;
align-items: center;
margin-left: auto;
transition: all 300ms;
width: 32px;
max-width: 20ch;
}
.search .icon {
width: 32px;
@ -533,15 +626,16 @@ nav ul li.active {
}
.search input {
border: none;
outline: none;
color: inherit;
background-color: inherit;
border-bottom: 1px solid var(--fg);
transition: all 300ms;
width: 0;
padding: 0;
flex-grow: 1;
}
.search.open input {
width: 10vw;
.search.open {
flex: 1;
}
.art {
margin-right: var(--icon-spacing);

View file

@ -5,6 +5,8 @@ html {
}
body {
.flex-column;
box-sizing: border-box;
font-family: lato, sans-serif;
line-height: 1.25;
@ -14,8 +16,6 @@ body {
max-width: 800px;
margin: 0 auto;
overflow: hidden;
display: flex;
flex-direction: column;
height: 100vh;
> header, > footer {
@ -33,32 +33,20 @@ button {
-moz-appearance: none;
appearance: none;
.flex-row;
display: inline-flex;
background-color: transparent;
padding: 0;
border: none;
line-height: 1;
cursor: pointer;
flex-shrink: 0;
.icon { vertical-align: middle; }
}
.long-line {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.multiline {
display: flex;
flex-direction: row;
align-items: center;
h2 { font-weight: normal; }
}
@import "font.less";
@import "icons.less";
@import "mixins.less";
@import "main.less";
@import "nav.less";
@import "player.less";

View file

@ -1,10 +1,17 @@
.component {
height: 100%;
display: flex;
flex-direction: column;
.flex-column;
header {
display: flex;
.flex-row;
padding: 8px;
button {
font-size: var(--font-size-large);
font-weight: bold;
.icon { margin-right: var(--icon-spacing); }
}
}
ul {
@ -16,9 +23,7 @@
}
li {
display: flex;
flex-direction: row;
align-items: center;
.flex-row;
.info {
flex-grow: 1;
@ -45,12 +50,11 @@
&:not(.has-art) {
padding: 8px;
}
button .icon { width: 32px; }
}
li:nth-child(odd) {
background-color: #555;
}
button .icon { width: 32px; }
}

View file

@ -1,6 +1,15 @@
#fs {
.component;
header {
white-space: pre; // separator
}
.search {
order: 1;
&.open ~ * { display: none; }
}
.group {
cursor: pointer;
}

View file

@ -1,6 +1,15 @@
#library {
.component;
header {
white-space: pre; // separator
}
.search {
order: 1;
&.open ~ * { display: none; }
}
.art img, .art .icon {
width: 64px;
}

23
app/css/mixins.less Normal file
View file

@ -0,0 +1,23 @@
.flex-row {
display: flex;
flex-direction: row;
align-items: center;
}
.flex-column {
display: flex;
flex-direction: column;
}
.long-line {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.multiline {
.flex-row;
h2 { font-weight: normal; }
}

View file

@ -2,8 +2,7 @@ nav ul {
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: row;
.flex-row;
.icon {
display: block;

View file

@ -1,7 +1,5 @@
#player {
display: flex;
flex-direction: row;
align-items: center;
.flex-row;
&:not([data-state=play]) .pause { display: none; }
&[data-state=play] .play { display: none; }

View file

@ -1,7 +1,9 @@
.search {
display: flex;
align-items: center;
.flex-row;
margin-left: auto;
transition: all 300ms;
width: 32px;
max-width: 20ch;
.icon {
width: 32px;
@ -10,17 +12,16 @@
input {
border: none;
outline: none;
color: inherit;
background-color: inherit;
border-bottom: 1px solid var(--fg);
transition: all 300ms;
width: 0;
padding: 0;
flex-grow: 1;
}
&.open {
input {
width: 10vw;
}
flex: 1;
}
}

View file

@ -13,17 +13,16 @@ function buildHeader(path) {
let header = node.querySelector("header");
html.clear(header);
let button = html.button({}, "/", header);
button.addEventListener("click", e => list(""));
search.reset();
header.appendChild(search.getNode());
path.split("/").filter(x => x).forEach((name, index, all) => {
let button = html.button({}, name, header);
index && html.node("span", {}, " / ", header);
let button = html.button({icon:"folder"}, name, header);
let path = all.slice(0, index+1).join("/");
button.addEventListener("click", e => list(path));
});
search.reset();
header.appendChild(search.getNode());
}
function buildDirectory(data, parent) {

View file

@ -1,4 +1,4 @@
const SEPARATOR = " · ";
export const SEPARATOR = " · ";
export function time(sec) {
sec = Math.round(sec);

View file

@ -137,7 +137,7 @@ export async function albumArt(songUrl) {
export async function init() {
return new Promise((resolve, reject) => {
try {
ws = new WebSocket("ws://localhost:8080");
ws = new WebSocket(`ws://${location.host}`);
} catch (e) { reject(e); }
current = {resolve, reject};

View file

@ -1,6 +1,7 @@
import * as mpd from "./lib/mpd.js";
import * as html from "./lib/html.js";
import * as ui from "./lib/ui.js";
import * as format from "./lib/format.js";
import Search from "./lib/search.js";
@ -13,25 +14,24 @@ function buildHeader(filter) {
let header = node.querySelector("header");
html.clear(header);
let button = html.button({}, "Music Library", header);
button.addEventListener("click", e => listArtists());
search.reset();
header.appendChild(search.getNode());
let artist = filter["Artist"];
if (artist) {
let artistFilter = {"Artist":artist};
let button = html.button({}, artist, header);
let button = html.button({icon:"artist"}, artist, header);
button.addEventListener("click", e => listAlbums(artistFilter));
let album = filter["Album"];
if (album) {
html.node("span", {}, format.SEPARATOR, header);
let albumFilter = Object.assign({}, artistFilter, {"Album":album});
let button = html.button({}, album, header);
let button = html.button({icon:"album"}, album, header);
button.addEventListener("click", e => listSongs(albumFilter));
}
}
search.reset();
header.appendChild(search.getNode());
}
function buildAlbum(album, filter, parent) {

View file

@ -1,6 +1,6 @@
const static = require("node-static");
const app = new static.Server("./app");
const port = 8080;
const port = Number(process.argv[2]) || 8080;
function downloadYoutube(url, response) {
// FIXME create directory
@ -41,7 +41,6 @@ function handleYoutube(request, response) {
request.on("data", chunk => str += chunk);
request.on("end", () => {
let url = require("querystring").parse(str)["url"];
console.log(url);
if (url) {
downloadYoutube(url, response);
} else {