diff --git a/Makefile b/Makefile index 1109956..fb6d038 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ LESS := $(shell npm bin)/lessc APP := app CSS := $(APP)/cyp.css -ICONS := $(APP)/js/lib/icons.js +ICONS := $(APP)/js/icons.js SYSD_USER := ~/.config/systemd/user SERVICE := cyp.service diff --git a/app/css/component.less b/app/css/component.less index fc81fa5..bf9d6ab 100644 --- a/app/css/component.less +++ b/app/css/component.less @@ -38,7 +38,7 @@ margin: 0; } - h2, div { .long-line; } +// h2, div { .long-line; } } &.has-art { diff --git a/app/css/cyp.less b/app/css/cyp.less index 2c2f3d4..32d1d4a 100644 --- a/app/css/cyp.less +++ b/app/css/cyp.less @@ -22,7 +22,7 @@ header, footer { footer { position: relative; height: 56px; - @media (max-width: 480px) { + @media (max-width: @breakpoint-menu) { height: 40px; } } diff --git a/app/css/elements/menu.less b/app/css/elements/menu.less index 147aedf..318df05 100644 --- a/app/css/elements/menu.less +++ b/app/css/elements/menu.less @@ -9,7 +9,7 @@ cyp-menu, cyp-commands { align-items: center; justify-content: center; - @media (max-width: 480px) { + @media (max-width: @breakpoint-menu) { flex-direction: row; span:not([id]) { display: none; } } @@ -47,7 +47,7 @@ cyp-commands { } button { - flex: 0 0 80px; + flex: 0 0 @breakpoint-menu/6; &.last { order: 1; margin-left: auto; diff --git a/app/css/elements/player.less b/app/css/elements/player.less index 8cee6fa..f506b66 100644 --- a/app/css/elements/player.less +++ b/app/css/elements/player.less @@ -34,7 +34,7 @@ cyp-player { margin: 0; } - .title, .subtitle { .long-line; } + .title, .subtitle { .no-wrap; } } .timeline { diff --git a/app/css/elements/playlist.less b/app/css/elements/playlist.less index 6cc20df..bb82ead 100644 --- a/app/css/elements/playlist.less +++ b/app/css/elements/playlist.less @@ -1,7 +1,5 @@ cyp-playlist { - .flex-row; - - padding: 8px; + .item; &:nth-child(odd) { background-color: var(--bg-alt); diff --git a/app/css/elements/playlists.less b/app/css/elements/playlists.less index fe50244..ffb438f 100644 --- a/app/css/elements/playlists.less +++ b/app/css/elements/playlists.less @@ -1,7 +1,2 @@ cyp-playlists { - .component; - - .info { - .multiline; - } -} \ No newline at end of file +} diff --git a/app/css/elements/song.less b/app/css/elements/song.less index a187253..570a86a 100644 --- a/app/css/elements/song.less +++ b/app/css/elements/song.less @@ -1,6 +1,5 @@ cyp-song { - .selectable; - .flex-row; + .item; .info { // FIXME zrevidovat flex-grow: 1; @@ -17,14 +16,6 @@ cyp-song { margin: 0; } - h2, div { .long-line; } - } - - &:nth-child(odd) { - background-color: var(--bg-alt); - } - - &:not(.has-art) { - padding: 8px; +// h2, div { .long-line; } FIXME vyresit zalamovani/vypustku } } diff --git a/app/css/fs.less b/app/css/fs.less index 1584d2a..493ade7 100644 --- a/app/css/fs.less +++ b/app/css/fs.less @@ -15,6 +15,6 @@ } .info { - .multiline; +// .multiline; FIXME } } diff --git a/app/css/mixins.less b/app/css/mixins.less index 676cac4..53ebfbe 100644 --- a/app/css/mixins.less +++ b/app/css/mixins.less @@ -9,17 +9,19 @@ flex-direction: column; } -.long-line { +.no-wrap { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +/* .multiline { .flex-row; h2 { font-weight: normal; } } +*/ .selectable { border-left: 4px solid transparent; @@ -28,3 +30,13 @@ border-left-color: var(--primary); } } + +.item { + .flex-row; + .selectable; + padding: 8px; + + &:nth-child(odd) { + background-color: var(--bg-alt); + } +} \ No newline at end of file diff --git a/app/css/variables.less b/app/css/variables.less index 4f97008..8ad3c9e 100644 --- a/app/css/variables.less +++ b/app/css/variables.less @@ -1,3 +1,5 @@ +@breakpoint-menu: 480px; + cyp-app { --font-size-large: 112.5%; --icon-spacing: 4px; @@ -43,7 +45,7 @@ cyp-app[color=limegreen] { --primary-raw: 50, 205, 50; } -@media (max-width: 480px) { +@media (max-width: @breakpoint-menu) { :root { --spacing: var(--icon-spacing); } diff --git a/app/cyp.css b/app/cyp.css index 7eea054..995133b 100644 --- a/app/cyp.css +++ b/app/cyp.css @@ -91,27 +91,39 @@ select { .flex-column:not([hidden]) { display: flex; } -.long-line { +.no-wrap { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +/* .multiline { - flex-direction: row; - align-items: center; -} -.multiline:not([hidden]) { - display: flex; -} -.multiline h2 { - font-weight: normal; + .flex-row; + + h2 { font-weight: normal; } } +*/ .selectable { border-left: 4px solid transparent; } .selectable.selected { border-left-color: var(--primary); } +.item { + flex-direction: row; + align-items: center; + border-left: 4px solid transparent; + padding: 8px; +} +.item:not([hidden]) { + display: flex; +} +.item.selected { + border-left-color: var(--primary); +} +.item:nth-child(odd) { + background-color: var(--bg-alt); +} .component header { flex-direction: row; align-items: center; @@ -155,12 +167,6 @@ select { font-size: var(--font-size-large); margin: 0; } -.component li .info h2, -.component li .info div { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} .component li:not(.has-art) { padding: 8px; } @@ -213,12 +219,6 @@ select { font-size: var(--font-size-large); margin: 0; } -#library li .info h2, -#library li .info div { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} #library li:not(.has-art) { padding: 8px; } @@ -308,12 +308,6 @@ select { font-size: var(--font-size-large); margin: 0; } -#fs li .info h2, -#fs li .info div { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} #fs li:not(.has-art) { padding: 8px; } @@ -335,16 +329,6 @@ select { #fs .group { cursor: pointer; } -#fs .info { - flex-direction: row; - align-items: center; -} -#fs .info:not([hidden]) { - display: flex; -} -#fs .info h2 { - font-weight: normal; -} .search { flex-direction: row; align-items: center; @@ -509,15 +493,19 @@ cyp-commands button.last { margin-left: auto; } cyp-song { - border-left: 4px solid transparent; flex-direction: row; align-items: center; + border-left: 4px solid transparent; + padding: 8px; +} +cyp-song:not([hidden]) { + display: flex; } cyp-song.selected { border-left-color: var(--primary); } -cyp-song:not([hidden]) { - display: flex; +cyp-song:nth-child(odd) { + background-color: var(--bg-alt); } cyp-song .info { flex-grow: 1; @@ -532,18 +520,6 @@ cyp-song .info h2 { font-size: var(--font-size-large); margin: 0; } -cyp-song .info h2, -cyp-song .info div { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -cyp-song:nth-child(odd) { - background-color: var(--bg-alt); -} -cyp-song:not(.has-art) { - padding: 8px; -} cyp-player { flex-direction: row; align-items: center; @@ -662,74 +638,6 @@ cyp-player .misc .icon { height: 96px; } } -cyp-playlists header { - flex-direction: row; - align-items: center; - padding: var(--spacing); -} -cyp-playlists header:not([hidden]) { - display: flex; -} -cyp-playlists header button { - font-size: var(--font-size-large); - font-weight: bold; - overflow: hidden; -} -cyp-playlists header button .icon { - margin-right: var(--icon-spacing); -} -cyp-playlists ul { - flex-grow: 1; - overflow: auto; - list-style: none; - margin: 0; - padding: 0; -} -cyp-playlists li { - flex-direction: row; - align-items: center; -} -cyp-playlists li:not([hidden]) { - display: flex; -} -cyp-playlists li .info { - flex-grow: 1; - overflow: hidden; -} -cyp-playlists li .info .icon { - color: var(--primary); - margin-right: var(--icon-spacing); - filter: drop-shadow(var(--text-shadow)); -} -cyp-playlists li .info h2 { - font-size: var(--font-size-large); - margin: 0; -} -cyp-playlists li .info h2, -cyp-playlists li .info div { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -cyp-playlists li:not(.has-art) { - padding: 8px; -} -cyp-playlists li button .icon { - width: 32px; -} -cyp-playlists li:nth-child(odd) { - background-color: var(--bg-alt); -} -cyp-playlists .info { - flex-direction: row; - align-items: center; -} -cyp-playlists .info:not([hidden]) { - display: flex; -} -cyp-playlists .info h2 { - font-weight: normal; -} cyp-queue .current { color: var(--primary); } @@ -808,12 +716,6 @@ cyp-yt li .info h2 { font-size: var(--font-size-large); margin: 0; } -cyp-yt li .info h2, -cyp-yt li .info div { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} cyp-yt li:not(.has-art) { padding: 8px; } @@ -917,11 +819,18 @@ x-range:not([disabled]) .-thumb:hover { cyp-playlist { flex-direction: row; align-items: center; + border-left: 4px solid transparent; padding: 8px; } cyp-playlist:not([hidden]) { display: flex; } +cyp-playlist.selected { + border-left-color: var(--primary); +} +cyp-playlist:nth-child(odd) { + background-color: var(--bg-alt); +} cyp-playlist:nth-child(odd) { background-color: var(--bg-alt); } diff --git a/app/icons/cancel.svg b/app/icons/cancel.svg new file mode 100644 index 0000000..f798721 --- /dev/null +++ b/app/icons/cancel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/js/component.js b/app/js/component.js index 27c2471..02b0fbe 100644 --- a/app/js/component.js +++ b/app/js/component.js @@ -13,9 +13,9 @@ export class Item extends HasApp { } export default class Component extends HasApp { - constructor() { + constructor(options) { super(); - this.selection = new Selection(this); + if (options.selection) { this.selection = new Selection(this, options.selection); } } connectedCallback() { diff --git a/app/js/elements/menu.js b/app/js/elements/menu.js index 42340b5..3dc55f9 100644 --- a/app/js/elements/menu.js +++ b/app/js/elements/menu.js @@ -2,7 +2,7 @@ import Component from "../component.js"; class Menu extends Component { constructor() { - super(); + super({selection:null}); this._tabs = Array.from(this.querySelectorAll("[data-for]")); this._tabs.forEach(tab => { @@ -10,19 +10,13 @@ class Menu extends Component { }); } - async _listen() { - const app = await this._app; - let mo = new MutationObserver(_ => this._sync()) - mo.observe(app, {attributes:true}); - } - async _activate(component) { const app = await this._app; app.setAttribute("component", component); } _onComponentChange(component) { - this._tabs.forEach(tab => { + this._tabs.forEach(/** @param {HTMLElement} tab */ tab => { tab.classList.toggle("active", tab.dataset.for == component); }); } diff --git a/app/js/elements/player.js b/app/js/elements/player.js index f0a3fe2..dcb56be 100644 --- a/app/js/elements/player.js +++ b/app/js/elements/player.js @@ -8,7 +8,7 @@ const DELAY = 1000; class Player extends Component { constructor() { - super(); + super({selection:null}); this._current = {}; this._toggledVolume = 0; this._idleTimeout = null; diff --git a/app/js/elements/playlist.js b/app/js/elements/playlist.js index c5ec335..3054ca7 100644 --- a/app/js/elements/playlist.js +++ b/app/js/elements/playlist.js @@ -10,12 +10,6 @@ export default class Playlist extends Item { connectedCallback() { html.icon("playlist-music", this) html.node("h2", {}, this.name, this); - -/* - playButton(TYPE_PLAYLIST, name, node); - addButton(TYPE_PLAYLIST, name, node); - deleteButton(TYPE_PLAYLIST, name, node); -*/ } } diff --git a/app/js/elements/playlists.js b/app/js/elements/playlists.js index 8477db9..b8ceb0b 100644 --- a/app/js/elements/playlists.js +++ b/app/js/elements/playlists.js @@ -1,11 +1,14 @@ import * as html from "../html.js"; -import * as ui from "../ui.js"; - import Component from "../component.js"; import Playlist from "./playlist.js"; class Playlists extends Component { + constructor() { + super({selection:"single"}); + this._initCommands(); + } + handleEvent(e) { switch (e.type) { case "playlists-change": @@ -30,9 +33,25 @@ class Playlists extends Component { _buildLists(lists) { html.clear(this); + this.selection.clear(); lists.forEach(name => this.appendChild(new Playlist(name))); } + + _initCommands() { + const sel = this.selection; + + sel.addCommand(async items => { + }, {label:"Play", icon:"play"}); + + sel.addCommand(async items => { + }, {label:"Enqueue", icon:"plus"}); + + sel.addCommand(async items => { + }, {label:"Delete", icon:"delete"}); + + sel.addCommandCancel(); + } } customElements.define("cyp-playlists", Playlists); diff --git a/app/js/elements/queue.js b/app/js/elements/queue.js index 0822236..5dad233 100644 --- a/app/js/elements/queue.js +++ b/app/js/elements/queue.js @@ -2,9 +2,10 @@ import * as html from "../html.js"; import Component from "../component.js"; import Song from "./song.js"; + class Queue extends Component { constructor() { - super(); + super({selection:"multi"}); this._currentId = null; this._initCommands(); } @@ -35,7 +36,6 @@ class Queue extends Component { } async _sync() { - this.selection.clear(); let songs = await this._mpd.listQueue(); this._buildSongs(songs); @@ -51,6 +51,7 @@ class Queue extends Component { _buildSongs(songs) { html.clear(this); + this.selection.clear(); songs.forEach(song => this.appendChild(new Song(song))); @@ -84,7 +85,7 @@ class Queue extends Component { this._sync(); }, {label:"Remove", icon:"delete"}); - sel.addCommandClear(); + sel.addCommandCancel(); } } diff --git a/app/js/elements/settings.js b/app/js/elements/settings.js index 79d533d..65f9791 100644 --- a/app/js/elements/settings.js +++ b/app/js/elements/settings.js @@ -12,7 +12,7 @@ function saveToStorage(key, value) { class Settings extends Component { constructor() { - super(); + super({selection:null}); this._inputs = { theme: this.querySelector("[name=theme]"), color: Array.from(this.querySelectorAll("[name=color]")) diff --git a/app/js/icons.js b/app/js/icons.js index 34d24eb..811ef25 100644 --- a/app/js/icons.js +++ b/app/js/icons.js @@ -1,15 +1,33 @@ let ICONS={}; -ICONS["library-music"] = ` - -`; -ICONS["plus"] = ` - +ICONS["playlist-music"] = ` + `; ICONS["folder"] = ` `; -ICONS["playlist-music"] = ` - +ICONS["shuffle"] = ` + +`; +ICONS["artist"] = ` + +`; +ICONS["download"] = ` + +`; +ICONS["magnify"] = ` + +`; +ICONS["delete"] = ` + +`; +ICONS["rewind"] = ` + +`; +ICONS["account-multiple"] = ` + +`; +ICONS["cancel"] = ` + `; ICONS["settings"] = ` @@ -17,55 +35,40 @@ ICONS["settings"] = ` ICONS["pause"] = ` `; -ICONS["artist"] = ` - -`; ICONS["volume-off"] = ` `; -ICONS["fast-forward"] = ` - -`; -ICONS["delete"] = ` - -`; -ICONS["volume-high"] = ` - -`; -ICONS["minus"] = ` - -`; -ICONS["play"] = ` - -`; -ICONS["magnify"] = ` - -`; -ICONS["music"] = ` - -`; -ICONS["rewind"] = ` - -`; -ICONS["album"] = ` - -`; -ICONS["download"] = ` - -`; -ICONS["account-multiple"] = ` - -`; ICONS["close"] = ` `; -ICONS["content-save"] = ` - +ICONS["music"] = ` + `; -ICONS["shuffle"] = ` - +ICONS["minus"] = ` + `; ICONS["repeat"] = ` `; +ICONS["play"] = ` + +`; +ICONS["plus"] = ` + +`; +ICONS["content-save"] = ` + +`; +ICONS["library-music"] = ` + +`; +ICONS["fast-forward"] = ` + +`; +ICONS["volume-high"] = ` + +`; +ICONS["album"] = ` + +`; export default ICONS; diff --git a/app/js/selection.js b/app/js/selection.js index 5dd6626..8d8b83d 100644 --- a/app/js/selection.js +++ b/app/js/selection.js @@ -1,8 +1,10 @@ import * as html from "./html.js"; export default class Selection { - constructor(component) { + constructor(component, mode) { this._component = component; + /** @type {"single" | "multi"} */ + this._mode = mode; this._items = []; // FIXME ukladat skutecne HTML? co kdyz nastane refresh? this._node = html.node("cyp-commands", {hidden:true}); } @@ -14,18 +16,21 @@ export default class Selection { addCommand(cb, options) { const button = html.button({icon:options.icon}, "", this._node); html.node("span", {}, options.label, button); - button.addEventListener("click", _ => cb(this._items)); + button.addEventListener("click", _ => { + const arg = (this._mode == "single" ? this._items[0] : this._items); + cb(arg); + }); return button; } - addCommandAll(items) { + addCommandAll() { this.addCommand(_ => { Array.from(this._component.children).forEach(node => this.add(node)); }, {label:"Select all", icon:"plus"}); } - addCommandClear() { - const button = this.addCommand(_ => this.clear(), {icon:"close", label:"Clear"}); + addCommandCancel() { + const button = this.addCommand(_ => this.clear(), {icon:"cancel", label:"Cancel"}); button.classList.add("last"); return button; } @@ -43,6 +48,9 @@ export default class Selection { const length = this._items.length; this._items.push(node); node.classList.add("selected"); + + if (this._mode == "single" && length > 0) { this.remove(this._items[0]); } + if (length == 0) { this._show(); } } @@ -54,7 +62,7 @@ export default class Selection { } _show() { - const parent = this._component.closest("cyp-app").querySelector("footer"); + const parent = this._component.closest("cyp-app").querySelector("footer"); // FIXME jde lepe? parent.appendChild(this._node); this._node.offsetWidth; // FIXME jde lepe? this._node.hidden = false;