more refactor
This commit is contained in:
parent
14569a9415
commit
bb5e2d1fb6
11 changed files with 131 additions and 305 deletions
36
app/app.css
36
app/app.css
|
@ -625,72 +625,72 @@ cyp-queue .current {
|
||||||
#fs .info h2 {
|
#fs .info h2 {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
#playlists header {
|
cyp-playlists header {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: var(--spacing);
|
padding: var(--spacing);
|
||||||
}
|
}
|
||||||
#playlists header:not([hidden]) {
|
cyp-playlists header:not([hidden]) {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
#playlists header button {
|
cyp-playlists header button {
|
||||||
font-size: var(--font-size-large);
|
font-size: var(--font-size-large);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
#playlists header button .icon {
|
cyp-playlists header button .icon {
|
||||||
margin-right: var(--icon-spacing);
|
margin-right: var(--icon-spacing);
|
||||||
}
|
}
|
||||||
#playlists ul {
|
cyp-playlists ul {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
#playlists li {
|
cyp-playlists li {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
#playlists li:not([hidden]) {
|
cyp-playlists li:not([hidden]) {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
#playlists li .info {
|
cyp-playlists li .info {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
#playlists li .info .icon {
|
cyp-playlists li .info .icon {
|
||||||
color: var(--primary);
|
color: var(--primary);
|
||||||
margin-right: var(--icon-spacing);
|
margin-right: var(--icon-spacing);
|
||||||
filter: drop-shadow(var(--text-shadow));
|
filter: drop-shadow(var(--text-shadow));
|
||||||
}
|
}
|
||||||
#playlists li .info h2 {
|
cyp-playlists li .info h2 {
|
||||||
font-size: var(--font-size-large);
|
font-size: var(--font-size-large);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
#playlists li .info h2,
|
cyp-playlists li .info h2,
|
||||||
#playlists li .info div {
|
cyp-playlists li .info div {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
#playlists li:not(.has-art) {
|
cyp-playlists li:not(.has-art) {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
#playlists li button .icon {
|
cyp-playlists li button .icon {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
}
|
}
|
||||||
#playlists li:nth-child(odd) {
|
cyp-playlists li:nth-child(odd) {
|
||||||
background-color: var(--bg-alt);
|
background-color: var(--bg-alt);
|
||||||
}
|
}
|
||||||
#playlists .info {
|
cyp-playlists .info {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
#playlists .info:not([hidden]) {
|
cyp-playlists .info:not([hidden]) {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
#playlists .info h2 {
|
cyp-playlists .info h2 {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
#yt header {
|
#yt header {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#playlists {
|
cyp-playlists {
|
||||||
.component;
|
.component;
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<link rel="stylesheet" href="app.css" />
|
<link rel="stylesheet" href="app.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<cyp-app component="queue" theme="dark" color="dodgerblue">
|
<cyp-app theme="dark" color="dodgerblue">
|
||||||
<header>
|
<header>
|
||||||
<cyp-player>
|
<cyp-player>
|
||||||
<span class="art"></span>
|
<span class="art"></span>
|
||||||
|
@ -46,9 +46,9 @@
|
||||||
</header>
|
</header>
|
||||||
<ul></ul>
|
<ul></ul>
|
||||||
</cyp-queue>
|
</cyp-queue>
|
||||||
<section id="playlists">
|
<cyp-playlists>
|
||||||
<ul></ul>
|
<ul></ul>
|
||||||
</section>
|
</cyp-playlists>
|
||||||
<section id="library">
|
<section id="library">
|
||||||
<header></header>
|
<header></header>
|
||||||
<ul></ul>
|
<ul></ul>
|
||||||
|
|
|
@ -14,44 +14,50 @@ import * as yt from "./yt.js";
|
||||||
import * as settings from "./settings.js";
|
import * as settings from "./settings.js";
|
||||||
|
|
||||||
function initIcons() {
|
function initIcons() {
|
||||||
Array.from(document.querySelectorAll("[data-icon]")).forEach(node => {
|
Array.from(document.querySelectorAll("[data-icon]")).forEach(/** @param {HTMLElement} node */ node => {
|
||||||
let icon = html.icon(node.dataset.icon);
|
let icon = html.icon(node.dataset.icon);
|
||||||
node.insertBefore(icon, node.firstChild);
|
node.insertBefore(icon, node.firstChild);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function mpdExecutor(resolve, reject) {
|
|
||||||
try {
|
|
||||||
await mpd.init();
|
|
||||||
resolve(mpd);
|
|
||||||
} catch (e) {
|
|
||||||
resolve(mpdMock);
|
|
||||||
console.error(e);
|
|
||||||
reject(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class App extends HTMLElement {
|
class App extends HTMLElement {
|
||||||
|
static get observedAttributes() { return ["component"]; }
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
initIcons();
|
|
||||||
|
|
||||||
this._mpd = new Promise(mpdExecutor);
|
initIcons();
|
||||||
|
|
||||||
this._load();
|
this._load();
|
||||||
}
|
}
|
||||||
|
|
||||||
get mpd() { return this._mpd; }
|
attributeChangedCallback(name, oldValue, newValue) {
|
||||||
|
switch (name) {
|
||||||
|
case "component":
|
||||||
|
const e = new CustomEvent("component-change");
|
||||||
|
this.dispatchEvent(e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async _load() {
|
async _load() {
|
||||||
|
try {
|
||||||
|
await mpd.init();
|
||||||
|
this.mpd = mpd;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
this.mpd = mpdMock;
|
||||||
|
}
|
||||||
|
|
||||||
const promises = ["cyp-player"].map(name => customElements.whenDefined(name));
|
const promises = ["cyp-player"].map(name => customElements.whenDefined(name));
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
|
|
||||||
|
this.dispatchEvent(new CustomEvent("load"));
|
||||||
|
|
||||||
const onHashChange = () => {
|
const onHashChange = () => {
|
||||||
const hash = location.hash.substring(1);
|
const hash = location.hash.substring(1);
|
||||||
this._activate(hash || "queue");
|
this._activate(hash || "queue");
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("hashchange", onHashChange);
|
window.addEventListener("hashchange", onHashChange);
|
||||||
onHashChange();
|
onHashChange();
|
||||||
}
|
}
|
||||||
|
@ -59,9 +65,6 @@ class App extends HTMLElement {
|
||||||
_activate(what) {
|
_activate(what) {
|
||||||
location.hash = what;
|
location.hash = what;
|
||||||
this.setAttribute("component", what);
|
this.setAttribute("component", what);
|
||||||
|
|
||||||
const component = this.querySelector(`cyp-${what}`);
|
|
||||||
// component.activate();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,32 +1,22 @@
|
||||||
const APP = "cyp-app";
|
const APP = "cyp-app";
|
||||||
|
|
||||||
export default class Component extends HTMLElement {
|
export class HasApp extends HTMLElement {
|
||||||
|
get _app() { return this.closest("cyp-app"); }
|
||||||
|
get _mpd() { return this._app.mpd; }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Component extends HasApp {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this._app.then(app => {
|
this._app.addEventListener("load", _ => this._onAppLoad());
|
||||||
let mo = new MutationObserver(mrs => {
|
this._app.addEventListener("component-change", _ => {
|
||||||
mrs.forEach(mr => this._onAppAttributeChange(mr));
|
const component = this._app.getAttribute("component");
|
||||||
});
|
|
||||||
mo.observe(app, {attributes:true});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_onAppAttributeChange(mr) {
|
|
||||||
if (mr.attributeName != "component") { return; }
|
|
||||||
const component = mr.target.getAttribute(mr.attributeName);
|
|
||||||
const isThis = (this.nodeName.toLowerCase() == `cyp-${component}`);
|
const isThis = (this.nodeName.toLowerCase() == `cyp-${component}`);
|
||||||
this._onComponentChange(component, isThis);
|
this._onComponentChange(component, isThis);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get _app() {
|
_onComponentChange(_component, _isThis) {}
|
||||||
return customElements.whenDefined(APP)
|
_onAppLoad() {}
|
||||||
.then(() => this.closest(APP));
|
|
||||||
}
|
|
||||||
|
|
||||||
get _mpd() {
|
|
||||||
return this._app.then(app => app.mpd);
|
|
||||||
}
|
|
||||||
|
|
||||||
_onComponentChange(component) {}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,170 +0,0 @@
|
||||||
class Range extends HTMLElement {
|
|
||||||
static get observedAttributes() { return ["min", "max", "value", "step", "disabled"]; }
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this._dom = {};
|
|
||||||
|
|
||||||
this.addEventListener("mousedown", this);
|
|
||||||
this.addEventListener("keydown", this);
|
|
||||||
}
|
|
||||||
|
|
||||||
get _valueAsNumber() {
|
|
||||||
let raw = (this.hasAttribute("value") ? Number(this.getAttribute("value")) : 50);
|
|
||||||
return this._constrain(raw);
|
|
||||||
}
|
|
||||||
get _minAsNumber() {
|
|
||||||
return (this.hasAttribute("min") ? Number(this.getAttribute("min")) : 0);
|
|
||||||
}
|
|
||||||
get _maxAsNumber() {
|
|
||||||
return (this.hasAttribute("max") ? Number(this.getAttribute("max")) : 100);
|
|
||||||
}
|
|
||||||
get _stepAsNumber() {
|
|
||||||
return (this.hasAttribute("step") ? Number(this.getAttribute("step")) : 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
get value() { return String(this._valueAsNumber); }
|
|
||||||
get valueAsNumber() { return this._valueAsNumber; }
|
|
||||||
get min() { return this.hasAttribute("min") ? this.getAttribute("min") : ""; }
|
|
||||||
get max() { return this.hasAttribute("max") ? this.getAttribute("max") : ""; }
|
|
||||||
get step() { return this.hasAttribute("step") ? this.getAttribute("step") : ""; }
|
|
||||||
get disabled() { return this.hasAttribute("disabled"); }
|
|
||||||
|
|
||||||
set _valueAsNumber(value) { this.value = String(value); }
|
|
||||||
set min(min) { this.setAttribute("min", min); }
|
|
||||||
set max(max) { this.setAttribute("max", max); }
|
|
||||||
set value(value) { this.setAttribute("value", value); }
|
|
||||||
set step(step) { this.setAttribute("step", step); }
|
|
||||||
set disabled(disabled) {
|
|
||||||
disabled ? this.setAttribute("disabled", "") : this.removeAttribute("disabled");
|
|
||||||
}
|
|
||||||
|
|
||||||
connectedCallback() {
|
|
||||||
if (this.firstChild) { return; }
|
|
||||||
|
|
||||||
this.innerHTML = `
|
|
||||||
<span class="-track"></span>
|
|
||||||
<span class="-elapsed"></span>
|
|
||||||
<span class="-remaining"></span>
|
|
||||||
<div class="-inner">
|
|
||||||
<button class="-thumb"></button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
Array.from(this.querySelectorAll("[class^='-']")).forEach(node => {
|
|
||||||
let name = node.className.substring(1);
|
|
||||||
this._dom[name] = node;
|
|
||||||
});
|
|
||||||
|
|
||||||
this._update();
|
|
||||||
}
|
|
||||||
|
|
||||||
attributeChangedCallback(name, oldValue, newValue) {
|
|
||||||
switch (name) {
|
|
||||||
case "min":
|
|
||||||
case "max":
|
|
||||||
case "value":
|
|
||||||
case "step":
|
|
||||||
this._update();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleEvent(e) {
|
|
||||||
switch (e.type) {
|
|
||||||
case "mousedown":
|
|
||||||
if (this.disabled) { return; }
|
|
||||||
document.addEventListener("mousemove", this);
|
|
||||||
document.addEventListener("mouseup", this);
|
|
||||||
this._setToMouse(e);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "mousemove":
|
|
||||||
this._setToMouse(e);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "mouseup":
|
|
||||||
document.removeEventListener("mousemove", this);
|
|
||||||
document.removeEventListener("mouseup", this);
|
|
||||||
this.dispatchEvent(new CustomEvent("change"));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "keydown":
|
|
||||||
if (this.disabled) { return; }
|
|
||||||
this._handleKey(e.code);
|
|
||||||
this.dispatchEvent(new CustomEvent("input"));
|
|
||||||
this.dispatchEvent(new CustomEvent("change"));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleKey(code) {
|
|
||||||
let min = this._minAsNumber;
|
|
||||||
let max = this._maxAsNumber;
|
|
||||||
let range = max - min;
|
|
||||||
let step = this._stepAsNumber;
|
|
||||||
|
|
||||||
switch (code) {
|
|
||||||
case "ArrowLeft":
|
|
||||||
case "ArrowDown":
|
|
||||||
this._valueAsNumber = this._constrain(this._valueAsNumber - step);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "ArrowRight":
|
|
||||||
case "ArrowUp":
|
|
||||||
this._valueAsNumber = this._constrain(this._valueAsNumber + step);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "Home": this._valueAsNumber = this._constrain(min); break;
|
|
||||||
case "End": this._valueAsNumber = this._constrain(max); break;
|
|
||||||
|
|
||||||
case "PageUp": this._valueAsNumber = this._constrain(this._valueAsNumber + range/10); break;
|
|
||||||
case "PageDown": this._valueAsNumber = this._constrain(this._valueAsNumber - range/10); break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_constrain(value) {
|
|
||||||
const min = this._minAsNumber;
|
|
||||||
const max = this._maxAsNumber;
|
|
||||||
const step = this._stepAsNumber;
|
|
||||||
|
|
||||||
value = Math.max(value, min);
|
|
||||||
value = Math.min(value, max);
|
|
||||||
|
|
||||||
value -= min;
|
|
||||||
value = Math.round(value / step) * step;
|
|
||||||
value += min;
|
|
||||||
if (value > max) { value -= step; }
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
_update() {
|
|
||||||
let min = this._minAsNumber;
|
|
||||||
let max = this._maxAsNumber;
|
|
||||||
let frac = (this._valueAsNumber-min) / (max-min);
|
|
||||||
this._dom.thumb.style.left = `${frac * 100}%`;
|
|
||||||
this._dom.remaining.style.left = `${frac * 100}%`;
|
|
||||||
this._dom.elapsed.style.width = `${frac * 100}%`;
|
|
||||||
}
|
|
||||||
|
|
||||||
_setToMouse(e) {
|
|
||||||
let rect = this._dom.inner.getBoundingClientRect();
|
|
||||||
let x = e.clientX;
|
|
||||||
x = Math.max(x, rect.left);
|
|
||||||
x = Math.min(x, rect.right);
|
|
||||||
|
|
||||||
let min = this._minAsNumber;
|
|
||||||
let max = this._maxAsNumber;
|
|
||||||
|
|
||||||
let frac = (x-rect.left) / (rect.right-rect.left);
|
|
||||||
let value = this._constrain(min + frac * (max-min));
|
|
||||||
if (value == this._valueAsNumber) { return; }
|
|
||||||
|
|
||||||
this._valueAsNumber = value;
|
|
||||||
this.dispatchEvent(new CustomEvent("input"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define('x-range', Range);
|
|
1
app/js/lib/range.js
Symbolic link
1
app/js/lib/range.js
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../../node_modules/custom-range/range.js
|
|
@ -12,6 +12,9 @@ class Player extends Component {
|
||||||
this._toggledVolume = 0;
|
this._toggledVolume = 0;
|
||||||
this._idleTimeout = null;
|
this._idleTimeout = null;
|
||||||
this._dom = this._initDOM();
|
this._dom = this._initDOM();
|
||||||
|
}
|
||||||
|
|
||||||
|
_onAppLoad() {
|
||||||
this._update();
|
this._update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,9 +43,8 @@ class Player extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
async _command(cmd) {
|
async _command(cmd) {
|
||||||
const mpd = await this._mpd;
|
|
||||||
this._clearIdle();
|
this._clearIdle();
|
||||||
const data = await mpd.commandAndStatus(cmd);
|
const data = await this._mpd.commandAndStatus(cmd);
|
||||||
this._sync(data);
|
this._sync(data);
|
||||||
this._idle();
|
this._idle();
|
||||||
}
|
}
|
||||||
|
@ -57,9 +59,8 @@ class Player extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
async _update() {
|
async _update() {
|
||||||
const mpd = await this._mpd;
|
|
||||||
this._clearIdle();
|
this._clearIdle();
|
||||||
const data = await mpd.status();
|
const data = await this._mpd.status();
|
||||||
this._sync(data);
|
this._sync(data);
|
||||||
this._idle();
|
this._idle();
|
||||||
}
|
}
|
||||||
|
@ -136,10 +137,9 @@ class Player extends Component {
|
||||||
this._current = data;
|
this._current = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _dispatchSongChange(detail) {
|
_dispatchSongChange(detail) {
|
||||||
const app = await this._app;
|
|
||||||
const e = new CustomEvent("song-change", {detail});
|
const e = new CustomEvent("song-change", {detail});
|
||||||
app.dispatchEvent(e);
|
this._app.dispatchEvent(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,31 +1,38 @@
|
||||||
import * as mpd from "./lib/mpd.js";
|
|
||||||
import * as html from "./lib/html.js";
|
import * as html from "./lib/html.js";
|
||||||
import * as pubsub from "./lib/pubsub.js";
|
|
||||||
import * as ui from "./lib/ui.js";
|
import * as ui from "./lib/ui.js";
|
||||||
|
|
||||||
let node;
|
import Component from "./component.js";
|
||||||
|
|
||||||
function buildLists(lists) {
|
|
||||||
let ul = node.querySelector("ul");
|
class Playlists extends Component {
|
||||||
|
handleEvent(e) {
|
||||||
|
switch (e.type) {
|
||||||
|
case "playlists-change":
|
||||||
|
this._sync();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_onAppLoad() {
|
||||||
|
this._app.addEventListener("playlists-change", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onComponentChange(c, isThis) {
|
||||||
|
this.hidden = !isThis;
|
||||||
|
if (isThis) { this._sync(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
async _sync() {
|
||||||
|
let lists = await this._mpd.listPlaylists();
|
||||||
|
this._buildLists(lists);
|
||||||
|
}
|
||||||
|
|
||||||
|
_buildLists(lists) {
|
||||||
|
let ul = this.querySelector("ul");
|
||||||
html.clear(ul);
|
html.clear(ul);
|
||||||
|
|
||||||
lists.map(list => ui.playlist(list, ul));
|
lists.map(list => ui.playlist(list, ul));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function syncLists() {
|
customElements.define("cyp-playlists", Playlists);
|
||||||
let lists = await mpd.listPlaylists();
|
|
||||||
buildLists(lists);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPlaylistsChange(message, publisher, data) {
|
|
||||||
syncLists();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function activate() {
|
|
||||||
syncLists();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function init(n) {
|
|
||||||
node = n;
|
|
||||||
pubsub.subscribe("playlists-change", onPlaylistsChange);
|
|
||||||
}
|
|
||||||
|
|
|
@ -9,23 +9,15 @@ class Queue extends Component {
|
||||||
this._currentId = null;
|
this._currentId = null;
|
||||||
|
|
||||||
this.querySelector(".clear").addEventListener("click", async _ => {
|
this.querySelector(".clear").addEventListener("click", async _ => {
|
||||||
const mpd = await this._mpd;
|
await this._mpd.command("clear");
|
||||||
await mpd.command("clear");
|
|
||||||
this._sync();
|
this._sync();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.querySelector(".save").addEventListener("click", async _ => {
|
this.querySelector(".save").addEventListener("click", _ => {
|
||||||
let name = prompt("Save current queue as a playlist?", "name");
|
let name = prompt("Save current queue as a playlist?", "name");
|
||||||
if (name === null) { return; }
|
if (name === null) { return; }
|
||||||
const mpd = await this._mpd;
|
this._mpd.command(`save "${this._mpd.escape(name)}"`);
|
||||||
mpd.command(`save "${mpd.escape(name)}"`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this._app.then(app => {
|
|
||||||
app.addEventListener("song-change", this);
|
|
||||||
app.addEventListener("queue-change", this);
|
|
||||||
})
|
|
||||||
this._sync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleEvent(e) {
|
handleEvent(e) {
|
||||||
|
@ -41,6 +33,12 @@ class Queue extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onAppLoad() {
|
||||||
|
this._app.addEventListener("song-change", this);
|
||||||
|
this._app.addEventListener("queue-change", this);
|
||||||
|
this._sync();
|
||||||
|
}
|
||||||
|
|
||||||
_onComponentChange(c, isThis) {
|
_onComponentChange(c, isThis) {
|
||||||
this.hidden = !isThis;
|
this.hidden = !isThis;
|
||||||
|
|
||||||
|
@ -48,8 +46,7 @@ class Queue extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
async _sync() {
|
async _sync() {
|
||||||
const mpd = await this._mpd;
|
let songs = await this._mpd.listQueue();
|
||||||
let songs = await mpd.listQueue();
|
|
||||||
this._buildSongs(songs);
|
this._buildSongs(songs);
|
||||||
|
|
||||||
// FIXME pubsub?
|
// FIXME pubsub?
|
||||||
|
@ -57,8 +54,7 @@ class Queue extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateCurrent() {
|
_updateCurrent() {
|
||||||
let all = Array.from(this.querySelectorAll("[data-song-id]"));
|
Array.from(this.querySelectorAll("[data-song-id]")).forEach(/** @param {HTMLElement} node */ node => {
|
||||||
all.forEach(node => {
|
|
||||||
node.classList.toggle("current", node.dataset.songId == this._currentId);
|
node.classList.toggle("current", node.dataset.songId == this._currentId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,13 +17,24 @@ class Settings extends Component {
|
||||||
theme: this.querySelector("[name=theme]"),
|
theme: this.querySelector("[name=theme]"),
|
||||||
color: Array.from(this.querySelectorAll("[name=color]"))
|
color: Array.from(this.querySelectorAll("[name=color]"))
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
this._load();
|
_onAppLoad() {
|
||||||
|
let mo = new MutationObserver(mrs => {
|
||||||
|
mrs.forEach(mr => this._onAppAttributeChange(mr));
|
||||||
|
});
|
||||||
|
mo.observe(this._app, {attributes:true});
|
||||||
|
|
||||||
this._inputs.theme.addEventListener("change", e => this._setTheme(e.target.value));
|
this._inputs.theme.addEventListener("change", e => this._setTheme(e.target.value));
|
||||||
this._inputs.color.forEach(input => {
|
this._inputs.color.forEach(input => {
|
||||||
input.addEventListener("click", e => this._setColor(e.target.value));
|
input.addEventListener("click", e => this._setColor(e.target.value));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const theme = loadFromStorage("theme");
|
||||||
|
(theme ? this._app.setAttribute("theme", theme) : this._syncTheme());
|
||||||
|
|
||||||
|
const color = loadFromStorage("color");
|
||||||
|
(color ? this._app.setAttribute("color", color) : this._syncColor());
|
||||||
}
|
}
|
||||||
|
|
||||||
_onAppAttributeChange(mr) {
|
_onAppAttributeChange(mr) {
|
||||||
|
@ -31,39 +42,29 @@ class Settings extends Component {
|
||||||
if (mr.attributeName == "color") { this._syncColor(); }
|
if (mr.attributeName == "color") { this._syncColor(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
async _syncTheme() {
|
_syncTheme() {
|
||||||
const app = await this._app;
|
this._inputs.theme.value = this._app.getAttribute("theme");
|
||||||
this._inputs.theme.value = app.getAttribute("theme");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _syncColor() {
|
_syncColor() {
|
||||||
const app = await this._app;
|
|
||||||
this._inputs.color.forEach(input => {
|
this._inputs.color.forEach(input => {
|
||||||
input.checked = (input.value == app.getAttribute("color"));
|
input.checked = (input.value == this._app.getAttribute("color"));
|
||||||
input.parentNode.style.color = input.value;
|
input.parentNode.style.color = input.value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async _load() {
|
_setTheme(theme) {
|
||||||
const app = await this._app;
|
|
||||||
|
|
||||||
const theme = loadFromStorage("theme");
|
|
||||||
(theme ? app.setAttribute("theme", theme) : this._syncTheme());
|
|
||||||
|
|
||||||
const color = loadFromStorage("color");
|
|
||||||
(color ? app.setAttribute("color", color) : this._syncColor());
|
|
||||||
}
|
|
||||||
|
|
||||||
async _setTheme(theme) {
|
|
||||||
const app = await this._app;
|
|
||||||
saveToStorage("theme", theme);
|
saveToStorage("theme", theme);
|
||||||
app.setAttribute("theme", theme);
|
this._app.setAttribute("theme", theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _setColor(color) {
|
_setColor(color) {
|
||||||
const app = await this._app;
|
|
||||||
saveToStorage("color", color);
|
saveToStorage("color", color);
|
||||||
app.setAttribute("color", color);
|
this._app.setAttribute("color", color);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onComponentChange(c, isThis) {
|
||||||
|
this.hidden = !isThis;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
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 pubsub from "./lib/pubsub.js";
|
|
||||||
import * as ui from "./lib/ui.js";
|
|
||||||
import * as conf from "./conf.js";
|
import * as conf from "./conf.js";
|
||||||
|
|
||||||
let node;
|
let node;
|
||||||
|
|
Loading…
Reference in a new issue