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

View file

@ -5,6 +5,8 @@ html {
} }
body { body {
.flex-column;
box-sizing: border-box; box-sizing: border-box;
font-family: lato, sans-serif; font-family: lato, sans-serif;
line-height: 1.25; line-height: 1.25;
@ -14,8 +16,6 @@ body {
max-width: 800px; max-width: 800px;
margin: 0 auto; margin: 0 auto;
overflow: hidden; overflow: hidden;
display: flex;
flex-direction: column;
height: 100vh; height: 100vh;
> header, > footer { > header, > footer {
@ -33,32 +33,20 @@ button {
-moz-appearance: none; -moz-appearance: none;
appearance: none; appearance: none;
.flex-row;
display: inline-flex;
background-color: transparent; background-color: transparent;
padding: 0; padding: 0;
border: none; border: none;
line-height: 1; line-height: 1;
cursor: pointer; cursor: pointer;
flex-shrink: 0; 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 "font.less";
@import "icons.less"; @import "icons.less";
@import "mixins.less";
@import "main.less"; @import "main.less";
@import "nav.less"; @import "nav.less";
@import "player.less"; @import "player.less";

View file

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

View file

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

View file

@ -1,6 +1,15 @@
#library { #library {
.component; .component;
header {
white-space: pre; // separator
}
.search {
order: 1;
&.open ~ * { display: none; }
}
.art img, .art .icon { .art img, .art .icon {
width: 64px; 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; margin: 0;
padding: 0; padding: 0;
list-style: none; list-style: none;
display: flex; .flex-row;
flex-direction: row;
.icon { .icon {
display: block; display: block;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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