massive reorg

This commit is contained in:
Ondrej Zara 2020-03-09 22:24:31 +01:00
parent 65570a6ac5
commit eb195a171e
No known key found for this signature in database
GPG Key ID: B0A5751E616840C5
40 changed files with 680 additions and 692 deletions

View File

@ -1,6 +1,6 @@
LESS := $(shell npm bin)/lessc
APP := app
CSS := $(APP)/app.css
CSS := $(APP)/cyp.css
ICONS := $(APP)/js/lib/icons.js
SYSD_USER := ~/.config/systemd/user
SERVICE := cyp.service
@ -12,8 +12,8 @@ icons: $(ICONS)
$(ICONS): $(APP)/icons/*
$(APP)/svg2js.sh $(APP)/icons > $@
$(CSS): $(APP)/css/*
$(LESS) $(APP)/css/app.less > $@
$(CSS): $(APP)/css/* $(APP)/css/elements/*
$(LESS) $(APP)/css/cyp.less > $@
service: $(SERVICE)
systemctl --user enable $(PWD)/$(SERVICE)

View File

View File

@ -13,21 +13,6 @@ main {
overflow: auto;
}
cyp-app {
.flex-column;
box-sizing: border-box;
font-family: lato, sans-serif;
line-height: 1.25;
background-color: var(--bg);
color: var(--fg);
text-shadow: var(--text-shadow);
max-width: 800px;
margin: 0 auto;
overflow: hidden;
height: 100vh;
}
header, footer {
flex-shrink: 0;
z-index: 1;
@ -73,18 +58,20 @@ select {
@import "font.less";
@import "icons.less";
@import "mixins.less";
@import "range.less";
@import "player.less";
@import "component.less";
@import "queue.less";
@import "library.less";
@import "fs.less";
@import "playlists.less";
@import "yt.less";
@import "settings.less";
@import "search.less";
@import "art.less";
@import "variables.less";
@import "song.less";
@import "menu.less";
@import "elements/app.less";
@import "elements/menu.less";
@import "elements/song.less";
@import "elements/player.less";
@import "elements/playlists.less";
@import "elements/queue.less";
@import "elements/settings.less";
@import "elements/yt.less";
@import "elements/range.less";
@import "elements/playlist.less";

14
app/css/elements/app.less Normal file
View File

@ -0,0 +1,14 @@
cyp-app {
.flex-column;
box-sizing: border-box;
font-family: lato, sans-serif;
line-height: 1.25;
background-color: var(--bg);
color: var(--fg);
text-shadow: var(--text-shadow);
max-width: 800px;
margin: 0 auto;
overflow: hidden;
height: 100vh;
}

View File

@ -0,0 +1,9 @@
cyp-playlist {
.flex-row;
padding: 8px;
&:nth-child(odd) {
background-color: var(--bg-alt);
}
}

View File

@ -1,5 +1,3 @@
cyp-queue {
.component;
.current { color: var(--primary); }
}

1
app/css/elements/range.less Symbolic link
View File

@ -0,0 +1 @@
../../../node_modules/custom-range/range.less

View File

@ -2,7 +2,7 @@ cyp-song {
.selectable;
.flex-row;
.info {
.info { // FIXME zrevidovat
flex-grow: 1;
overflow: hidden;

View File

View File

@ -1 +0,0 @@
../../node_modules/custom-range/range.less

View File

@ -13,22 +13,6 @@ main {
flex-grow: 1;
overflow: auto;
}
cyp-app {
flex-direction: column;
box-sizing: border-box;
font-family: lato, sans-serif;
line-height: 1.25;
background-color: var(--bg);
color: var(--fg);
text-shadow: var(--text-shadow);
max-width: 800px;
margin: 0 auto;
overflow: hidden;
height: 100vh;
}
cyp-app:not([hidden]) {
display: flex;
}
header,
footer {
flex-shrink: 0;
@ -128,186 +112,6 @@ select {
.selectable.selected {
border-left-color: var(--primary);
}
x-range {
--thumb-size: 8px;
--thumb-color: #fff;
--thumb-shadow: #000;
--thumb-hover-color: #ddd;
--track-size: 4px;
--track-color: #888;
--track-shadow: #000;
--elapsed-color: #ddd;
--remaining-color: transparent;
--radius: calc(var(--track-size)/2);
display: inline-block;
position: relative;
width: 192px;
height: 16px;
}
x-range .-track,
x-range .-elapsed,
x-range .-remaining {
position: absolute;
top: calc(50% - var(--track-size)/2);
height: var(--track-size);
border-radius: var(--radius);
}
x-range .-track {
width: 100%;
left: 0;
background-color: var(--track-color);
box-shadow: 0 0 1px var(--thumb-shadow);
}
x-range .-elapsed {
left: 0;
background-color: var(--elapsed-color);
}
x-range .-remaining {
right: 0;
background-color: var(--remaining-color);
}
x-range .-inner {
position: absolute;
left: var(--thumb-size);
right: var(--thumb-size);
top: 0;
bottom: 0;
}
x-range .-thumb {
all: unset;
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
border-radius: 50%;
width: calc(2*var(--thumb-size));
height: calc(2*var(--thumb-size));
background-color: var(--thumb-color);
box-shadow: 0 0 2px var(--thumb-shadow);
}
x-range[disabled] {
opacity: 0.5;
}
x-range:not([disabled]) .-thumb:hover {
background-color: var(--thumb-hover-color);
}
cyp-player {
flex-direction: row;
align-items: center;
align-items: stretch;
}
cyp-player:not([hidden]) {
display: flex;
}
cyp-player:not([data-state=play]) .pause {
display: none;
}
cyp-player[data-state=play] .play {
display: none;
}
cyp-player:not([data-flags~=random]) .random,
cyp-player:not([data-flags~=repeat]) .repeat {
opacity: 0.5;
}
cyp-player x-range {
flex-grow: 1;
--elapsed-color: var(--primary);
}
cyp-player .art {
margin-right: 0;
height: 96px;
}
cyp-player .art img,
cyp-player .art .icon {
width: 96px;
}
cyp-player .info {
flex-grow: 2;
flex-basis: 0;
padding: 0 var(--icon-spacing);
overflow: hidden;
flex-direction: column;
justify-content: space-around;
}
cyp-player .info:not([hidden]) {
display: flex;
}
cyp-player .info h2 {
font-size: 125%;
margin: 0;
}
cyp-player .info .title,
cyp-player .info .subtitle {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
cyp-player .timeline {
flex-direction: row;
align-items: center;
}
cyp-player .timeline:not([hidden]) {
display: flex;
}
cyp-player .timeline .duration,
cyp-player .timeline .elapsed {
flex-basis: 5ch;
text-align: center;
}
cyp-player .controls {
flex-grow: 1;
flex-basis: 0;
max-width: 220px;
flex-direction: column;
justify-content: space-around;
}
cyp-player .controls:not([hidden]) {
display: flex;
}
cyp-player .controls .playback {
flex-direction: row;
align-items: center;
justify-content: space-around;
}
cyp-player .controls .playback:not([hidden]) {
display: flex;
}
cyp-player .controls .playback .icon {
width: 40px;
}
cyp-player .controls .playback .icon-play,
cyp-player .controls .playback .icon-pause {
width: 64px;
}
cyp-player .controls .volume {
flex-direction: row;
align-items: center;
}
cyp-player .controls .volume:not([hidden]) {
display: flex;
}
cyp-player .controls .volume .mute {
margin-right: 4px;
}
cyp-player .misc {
display: flex;
flex-direction: column;
align-self: stretch;
justify-content: space-around;
}
cyp-player .misc .icon {
width: 32px;
}
@media (max-width: 519px) {
cyp-player {
flex-wrap: wrap;
justify-content: space-between;
}
cyp-player .info {
order: 1;
flex-basis: 100%;
height: 96px;
}
}
.component header {
flex-direction: row;
align-items: center;
@ -366,67 +170,6 @@ cyp-player .misc .icon {
.component li:nth-child(odd) {
background-color: var(--bg-alt);
}
cyp-queue header {
flex-direction: row;
align-items: center;
padding: var(--spacing);
}
cyp-queue header:not([hidden]) {
display: flex;
}
cyp-queue header button {
font-size: var(--font-size-large);
font-weight: bold;
overflow: hidden;
}
cyp-queue header button .icon {
margin-right: var(--icon-spacing);
}
cyp-queue ul {
flex-grow: 1;
overflow: auto;
list-style: none;
margin: 0;
padding: 0;
}
cyp-queue li {
flex-direction: row;
align-items: center;
}
cyp-queue li:not([hidden]) {
display: flex;
}
cyp-queue li .info {
flex-grow: 1;
overflow: hidden;
}
cyp-queue li .info .icon {
color: var(--primary);
margin-right: var(--icon-spacing);
filter: drop-shadow(var(--text-shadow));
}
cyp-queue li .info h2 {
font-size: var(--font-size-large);
margin: 0;
}
cyp-queue li .info h2,
cyp-queue li .info div {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
cyp-queue li:not(.has-art) {
padding: 8px;
}
cyp-queue li button .icon {
width: 32px;
}
cyp-queue li:nth-child(odd) {
background-color: var(--bg-alt);
}
cyp-queue .current {
color: var(--primary);
}
#library header {
flex-direction: row;
align-items: center;
@ -602,6 +345,323 @@ cyp-queue .current {
#fs .info h2 {
font-weight: normal;
}
.search {
flex-direction: row;
align-items: center;
margin-left: auto;
transition: all 300ms;
width: 32px;
max-width: 20ch;
}
.search:not([hidden]) {
display: flex;
}
.search .icon {
width: 32px;
cursor: pointer;
}
.search input {
border: none;
outline: none;
color: inherit;
background-color: inherit;
border-bottom: 1px solid var(--fg);
width: 0;
padding: 0;
flex-grow: 1;
}
.search.open {
flex: 1;
}
.art {
margin-right: var(--icon-spacing);
}
.art .icon,
.art img {
vertical-align: top;
}
cyp-app {
--font-size-large: 112.5%;
--icon-spacing: 4px;
--primary: rgb(var(--primary-raw));
--spacing: 8px;
--box-shadow: 0 0 3px #000;
}
cyp-app[theme=light] {
--fg: #333;
--bg: #f0f0f0;
--bg-alt: #e0e0e0;
--text-shadow: none;
}
cyp-app[theme=dark] {
--fg: #f0f0f0;
--bg: #333;
--bg-alt: #555;
--text-shadow: 0 1px 1px rgba(0, 0, 0, 0.8);
}
@media (prefers-color-scheme: dark) {
cyp-app[theme=auto] {
--fg: #f0f0f0;
--bg: #333;
--bg-alt: #555;
--text-shadow: 0 1px 1px rgba(0, 0, 0, 0.8);
}
}
@media (prefers-color-scheme: light) {
cyp-app[theme=auto] {
--fg: #333;
--bg: #f0f0f0;
--bg-alt: #e0e0e0;
--text-shadow: none;
}
}
cyp-app[color=dodgerblue] {
--primary-raw: 30, 144, 255;
}
cyp-app[color=darkorange] {
--primary-raw: 255, 140, 0;
}
cyp-app[color=limegreen] {
--primary-raw: 50, 205, 50;
}
@media (max-width: 480px) {
:root {
--spacing: var(--icon-spacing);
}
}
cyp-app {
flex-direction: column;
box-sizing: border-box;
font-family: lato, sans-serif;
line-height: 1.25;
background-color: var(--bg);
color: var(--fg);
text-shadow: var(--text-shadow);
max-width: 800px;
margin: 0 auto;
overflow: hidden;
height: 100vh;
}
cyp-app:not([hidden]) {
display: flex;
}
cyp-menu,
cyp-commands {
flex-direction: row;
align-items: center;
height: 100%;
}
cyp-menu:not([hidden]),
cyp-commands:not([hidden]) {
display: flex;
}
cyp-menu button,
cyp-commands button {
height: 100%;
flex-direction: column;
align-items: center;
justify-content: center;
}
cyp-menu button:not([hidden]),
cyp-commands button:not([hidden]) {
display: flex;
}
@media (max-width: 480px) {
cyp-menu button,
cyp-commands button {
flex-direction: row;
}
cyp-menu button span:not([id]),
cyp-commands button span:not([id]) {
display: none;
}
}
cyp-menu button {
flex: 1 0 0;
border-top: 4px solid transparent;
border-bottom: 4px solid transparent;
}
cyp-menu button .icon {
margin-right: var(--icon-spacing);
}
cyp-menu button.active {
border-top-color: var(--primary);
color: var(--primary);
background-color: rgb(var(--primary-raw), 0.1);
}
cyp-commands {
position: absolute;
left: 0;
top: 0;
width: 100%;
transition: top 300ms;
background-color: var(--bg);
}
cyp-commands[hidden] {
display: flex;
top: 100%;
}
cyp-commands button {
flex: 0 0 80px;
}
cyp-commands button.last {
order: 1;
margin-left: auto;
}
cyp-song {
border-left: 4px solid transparent;
flex-direction: row;
align-items: center;
}
cyp-song.selected {
border-left-color: var(--primary);
}
cyp-song:not([hidden]) {
display: flex;
}
cyp-song .info {
flex-grow: 1;
overflow: hidden;
}
cyp-song .info .icon {
color: var(--primary);
margin-right: var(--icon-spacing);
filter: drop-shadow(var(--text-shadow));
}
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;
align-items: stretch;
}
cyp-player:not([hidden]) {
display: flex;
}
cyp-player:not([data-state=play]) .pause {
display: none;
}
cyp-player[data-state=play] .play {
display: none;
}
cyp-player:not([data-flags~=random]) .random,
cyp-player:not([data-flags~=repeat]) .repeat {
opacity: 0.5;
}
cyp-player x-range {
flex-grow: 1;
--elapsed-color: var(--primary);
}
cyp-player .art {
margin-right: 0;
height: 96px;
}
cyp-player .art img,
cyp-player .art .icon {
width: 96px;
}
cyp-player .info {
flex-grow: 2;
flex-basis: 0;
padding: 0 var(--icon-spacing);
overflow: hidden;
flex-direction: column;
justify-content: space-around;
}
cyp-player .info:not([hidden]) {
display: flex;
}
cyp-player .info h2 {
font-size: 125%;
margin: 0;
}
cyp-player .info .title,
cyp-player .info .subtitle {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
cyp-player .timeline {
flex-direction: row;
align-items: center;
}
cyp-player .timeline:not([hidden]) {
display: flex;
}
cyp-player .timeline .duration,
cyp-player .timeline .elapsed {
flex-basis: 5ch;
text-align: center;
}
cyp-player .controls {
flex-grow: 1;
flex-basis: 0;
max-width: 220px;
flex-direction: column;
justify-content: space-around;
}
cyp-player .controls:not([hidden]) {
display: flex;
}
cyp-player .controls .playback {
flex-direction: row;
align-items: center;
justify-content: space-around;
}
cyp-player .controls .playback:not([hidden]) {
display: flex;
}
cyp-player .controls .playback .icon {
width: 40px;
}
cyp-player .controls .playback .icon-play,
cyp-player .controls .playback .icon-pause {
width: 64px;
}
cyp-player .controls .volume {
flex-direction: row;
align-items: center;
}
cyp-player .controls .volume:not([hidden]) {
display: flex;
}
cyp-player .controls .volume .mute {
margin-right: 4px;
}
cyp-player .misc {
display: flex;
flex-direction: column;
align-self: stretch;
justify-content: space-around;
}
cyp-player .misc .icon {
width: 32px;
}
@media (max-width: 519px) {
cyp-player {
flex-wrap: wrap;
justify-content: space-between;
}
cyp-player .info {
order: 1;
flex-basis: 100%;
height: 96px;
}
}
cyp-playlists header {
flex-direction: row;
align-items: center;
@ -670,6 +730,41 @@ cyp-playlists .info:not([hidden]) {
cyp-playlists .info h2 {
font-weight: normal;
}
cyp-queue .current {
color: var(--primary);
}
cyp-settings {
font-size: var(--font-size-large);
}
cyp-settings dl {
margin: var(--spacing);
display: grid;
grid-template-columns: max-content 1fr;
align-items: center;
grid-gap: var(--spacing);
}
cyp-settings dt {
font-weight: bold;
}
cyp-settings dd {
margin: 0;
flex-direction: column;
align-items: start;
}
cyp-settings dd:not([hidden]) {
display: flex;
}
cyp-settings label {
flex-direction: row;
align-items: center;
}
cyp-settings label:not([hidden]) {
display: flex;
}
cyp-settings label [type=radio],
cyp-settings label [type=checkbox] {
margin: 0 4px 0 0;
}
cyp-yt header {
flex-direction: row;
align-items: center;
@ -757,218 +852,76 @@ cyp-yt.pending header {
background-position: 100% 100%;
}
}
cyp-settings {
font-size: var(--font-size-large);
x-range {
--thumb-size: 8px;
--thumb-color: #fff;
--thumb-shadow: #000;
--thumb-hover-color: #ddd;
--track-size: 4px;
--track-color: #888;
--track-shadow: #000;
--elapsed-color: #ddd;
--remaining-color: transparent;
--radius: calc(var(--track-size)/2);
display: inline-block;
position: relative;
width: 192px;
height: 16px;
}
cyp-settings dl {
margin: var(--spacing);
display: grid;
grid-template-columns: max-content 1fr;
align-items: center;
grid-gap: var(--spacing);
x-range .-track,
x-range .-elapsed,
x-range .-remaining {
position: absolute;
top: calc(50% - var(--track-size)/2);
height: var(--track-size);
border-radius: var(--radius);
}
cyp-settings dt {
font-weight: bold;
x-range .-track {
width: 100%;
left: 0;
background-color: var(--track-color);
box-shadow: 0 0 1px var(--thumb-shadow);
}
cyp-settings dd {
margin: 0;
flex-direction: column;
align-items: start;
x-range .-elapsed {
left: 0;
background-color: var(--elapsed-color);
}
cyp-settings dd:not([hidden]) {
display: flex;
x-range .-remaining {
right: 0;
background-color: var(--remaining-color);
}
cyp-settings label {
x-range .-inner {
position: absolute;
left: var(--thumb-size);
right: var(--thumb-size);
top: 0;
bottom: 0;
}
x-range .-thumb {
all: unset;
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
border-radius: 50%;
width: calc(2*var(--thumb-size));
height: calc(2*var(--thumb-size));
background-color: var(--thumb-color);
box-shadow: 0 0 2px var(--thumb-shadow);
}
x-range[disabled] {
opacity: 0.5;
}
x-range:not([disabled]) .-thumb:hover {
background-color: var(--thumb-hover-color);
}
cyp-playlist {
flex-direction: row;
align-items: center;
}
cyp-settings label:not([hidden]) {
display: flex;
}
cyp-settings label [type=radio],
cyp-settings label [type=checkbox] {
margin: 0 4px 0 0;
}
.search {
flex-direction: row;
align-items: center;
margin-left: auto;
transition: all 300ms;
width: 32px;
max-width: 20ch;
}
.search:not([hidden]) {
display: flex;
}
.search .icon {
width: 32px;
cursor: pointer;
}
.search input {
border: none;
outline: none;
color: inherit;
background-color: inherit;
border-bottom: 1px solid var(--fg);
width: 0;
padding: 0;
flex-grow: 1;
}
.search.open {
flex: 1;
}
.art {
margin-right: var(--icon-spacing);
}
.art .icon,
.art img {
vertical-align: top;
}
cyp-app {
--font-size-large: 112.5%;
--icon-spacing: 4px;
--primary: rgb(var(--primary-raw));
--spacing: 8px;
--box-shadow: 0 0 3px #000;
}
cyp-app[theme=light] {
--fg: #333;
--bg: #f0f0f0;
--bg-alt: #e0e0e0;
--text-shadow: none;
}
cyp-app[theme=dark] {
--fg: #f0f0f0;
--bg: #333;
--bg-alt: #555;
--text-shadow: 0 1px 1px rgba(0, 0, 0, 0.8);
}
@media (prefers-color-scheme: dark) {
cyp-app[theme=auto] {
--fg: #f0f0f0;
--bg: #333;
--bg-alt: #555;
--text-shadow: 0 1px 1px rgba(0, 0, 0, 0.8);
}
}
@media (prefers-color-scheme: light) {
cyp-app[theme=auto] {
--fg: #333;
--bg: #f0f0f0;
--bg-alt: #e0e0e0;
--text-shadow: none;
}
}
cyp-app[color=dodgerblue] {
--primary-raw: 30, 144, 255;
}
cyp-app[color=darkorange] {
--primary-raw: 255, 140, 0;
}
cyp-app[color=limegreen] {
--primary-raw: 50, 205, 50;
}
@media (max-width: 480px) {
:root {
--spacing: var(--icon-spacing);
}
}
cyp-song {
border-left: 4px solid transparent;
flex-direction: row;
align-items: center;
}
cyp-song.selected {
border-left-color: var(--primary);
}
cyp-song:not([hidden]) {
display: flex;
}
cyp-song .info {
flex-grow: 1;
overflow: hidden;
}
cyp-song .info .icon {
color: var(--primary);
margin-right: var(--icon-spacing);
filter: drop-shadow(var(--text-shadow));
}
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-menu,
cyp-commands {
flex-direction: row;
align-items: center;
height: 100%;
}
cyp-menu:not([hidden]),
cyp-commands:not([hidden]) {
cyp-playlist:not([hidden]) {
display: flex;
}
cyp-menu button,
cyp-commands button {
height: 100%;
flex-direction: column;
align-items: center;
justify-content: center;
}
cyp-menu button:not([hidden]),
cyp-commands button:not([hidden]) {
display: flex;
}
@media (max-width: 480px) {
cyp-menu button,
cyp-commands button {
flex-direction: row;
}
cyp-menu button span:not([id]),
cyp-commands button span:not([id]) {
display: none;
}
}
cyp-menu button {
flex: 1 0 0;
border-top: 4px solid transparent;
border-bottom: 4px solid transparent;
}
cyp-menu button .icon {
margin-right: var(--icon-spacing);
}
cyp-menu button.active {
border-top-color: var(--primary);
color: var(--primary);
background-color: rgb(var(--primary-raw), 0.1);
}
cyp-commands {
position: absolute;
left: 0;
top: 0;
width: 100%;
transition: top 300ms;
background-color: var(--bg);
}
cyp-commands[hidden] {
display: flex;
top: 100%;
}
cyp-commands button {
flex: 0 0 80px;
}
cyp-commands button.last {
order: 1;
margin-left: auto;
cyp-playlist:nth-child(odd) {
background-color: var(--bg-alt);
}

1
app/icons/delete.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="M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z" /></svg>

After

Width:  |  Height:  |  Size: 376 B

View File

@ -4,7 +4,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Control Your Player</title>
<link rel="stylesheet" href="app.css" />
<link rel="stylesheet" href="cyp.css" />
</head>
<body>
<cyp-app theme="dark" color="dodgerblue">
@ -103,6 +103,15 @@
</cyp-menu>
</footer>
</cyp-app>
<script type="module" src="js/app.js"></script>
<script type="module" src="js/elements/range.js"></script>
<script type="module" src="js/elements/app.js"></script>
<script type="module" src="js/elements/menu.js"></script>
<script type="module" src="js/elements/player.js"></script>
<script type="module" src="js/elements/queue.js"></script>
<script type="module" src="js/elements/playlists.js"></script>
<script type="module" src="js/elements/settings.js"></script>
<script type="module" src="js/elements/yt.js"></script>
<script type="module" src="js/elements/song.js"></script>
</body>
</html>

View File

@ -1,7 +1,6 @@
import * as mpd from "./mpd.js";
import * as parser from "./parser.js";
import * as html from "./html.js";
import * as conf from "../conf.js";
import * as conf from "./conf.js";
let cache = {};
const MIME = "image/jpeg";

View File

@ -1,10 +1,17 @@
import Selection from "./lib/selection.js";
import Selection from "./selection.js";
export class HasApp extends HTMLElement {
get _app() { return this.closest("cyp-app"); }
get _mpd() { return this._app.mpd; }
}
export class Item extends HasApp {
constructor() {
super();
this.addEventListener("click", _ => this.parentNode.selection.toggle(this));
}
}
export default class Component extends HasApp {
constructor() {
super();
@ -20,6 +27,6 @@ export default class Component extends HasApp {
});
}
_onComponentChange(_component, _isThis) {}
_onAppLoad() {}
_onComponentChange(_component, _isThis) {}
}

View File

@ -1,17 +1,9 @@
import "./lib/range.js";
import "./menu.js";
import "./player.js";
import "./queue.js";
import * as mpd from "../mpd.js";
import * as mpdMock from "../mpd-mock.js";
import * as html from "../html.js";
import * as mpd from "./lib/mpd.js";
import * as mpdMock from "./lib/mpd-mock.js";
import * as html from "./lib/html.js";
import * as library from "./library.js";
import * as fs from "./fs.js";
import * as playlists from "./playlists.js";
import * as yt from "./yt.js";
import * as settings from "./settings.js";
// import * as library from "./library.js";
// import * as fs from "./fs.js";
function initIcons() {
Array.from(document.querySelectorAll("[data-icon]")).forEach(/** @param {HTMLElement} node */ node => {
@ -37,13 +29,12 @@ class App extends HTMLElement {
super();
initIcons();
this._mpdPromise = initMpd().then(mpd => this.mpd = mpd);
}
async connectedCallback() {
this.mpd = await initMpd();
const promises = ["cyp-player"].map(name => customElements.whenDefined(name));
promises.push(this._mpdPromise);
await Promise.all(promises);

View File

@ -1,4 +1,4 @@
import Component from "./component.js";
import Component from "../component.js";
class Menu extends Component {
constructor() {

View File

@ -1,7 +1,8 @@
import * as art from "./lib/art.js";
import * as html from "./lib/html.js";
import * as format from "./lib/format.js";
import Component from "./component.js";
import * as art from "../art.js";
import * as html from "../html.js";
import * as format from "../format.js";
import Component from "../component.js";
const DELAY = 1000;

View File

@ -0,0 +1,22 @@
import * as html from "../html.js";
import { Item } from "../component.js";
export default class Playlist extends Item {
constructor(name) {
super();
this.name = name;
}
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);
*/
}
}
customElements.define("cyp-playlist", Playlist);

View File

@ -1,7 +1,8 @@
import * as html from "./lib/html.js";
import * as ui from "./lib/ui.js";
import * as html from "../html.js";
import * as ui from "../ui.js";
import Component from "./component.js";
import Component from "../component.js";
import Playlist from "./playlist.js";
class Playlists extends Component {
@ -28,10 +29,9 @@ class Playlists extends Component {
}
_buildLists(lists) {
let ul = this.querySelector("ul");
html.clear(ul);
html.clear(this);
lists.map(list => ui.playlist(list, ul));
lists.forEach(name => this.appendChild(new Playlist(name)));
}
}

View File

@ -1,33 +1,12 @@
import * as html from "./lib/html.js";
import * as format from "./lib/format.js";
import Component, { HasApp } from "./component.js";
import * as html from "../html.js";
import Component from "../component.js";
import Song from "./song.js";
class Queue extends Component {
constructor() {
super();
this._currentId = null;
/*
this.querySelector(".clear").addEventListener("click", async _ => {
await this._mpd.command("clear");
this._sync(); FIXME!
});
*/
this.selection.addCommand(_ => {
}, {label:"Select all", icon:"plus"});
this.selection.addCommand(async items => {
let name = prompt("Save selected songs as a playlist?", "name");
if (name === null) { return; }
name = this._mpd.escape(name);
const commands = items.map(item => {
return `playlistadd "${name}" "${this._mpd.escape(item.data["file"])}"`;
});
await this._mpd.command(commands);
// FIXME notify?
}, {label:"Save", icon:"content-save"});
this._initCommands();
}
handleEvent(e) {
@ -56,6 +35,7 @@ class Queue extends Component {
}
async _sync() {
this.selection.clear();
let songs = await this._mpd.listQueue();
this._buildSongs(songs);
@ -76,60 +56,36 @@ class Queue extends Component {
this._updateCurrent();
}
_initCommands() {
const sel = this.selection;
sel.addCommandAll();
sel.addCommand(async items => {
let name = prompt("Save selected songs as a playlist?", "name");
if (name === null) { return; }
name = this._mpd.escape(name);
const commands = items.map(item => {
return `playlistadd "${name}" "${this._mpd.escape(item.data["file"])}"`;
});
await this._mpd.command(commands);
// FIXME notify?
}, {label:"Save", icon:"content-save"});
sel.addCommand(async items => {
if (!confirm(`Remove these ${items.length} songs from the queue?`)) { return; }
const commands = items.map(item => `deleteid ${item.data["Id"]}`);
await this._mpd.command(commands);
this._sync();
}, {label:"Remove", icon:"delete"});
sel.addCommandClear();
}
}
customElements.define("cyp-queue", Queue);
class Item extends HasApp {
constructor() {
super();
this.addEventListener("click", e => this.parentNode.selection.toggle(this));
}
}
class Song extends Item {
constructor(data) {
super();
this.data = data;
this.dataset.songId = data["Id"];
}
connectedCallback() {
let info = html.node("div", {className:"info"}, "", this);
let lines = formatSongInfo(this.data);
html.node("h2", {}, lines.shift(), info);
lines.length && html.node("div", {}, lines.shift(), info);
/*
playButton(TYPE_ID, id, node);
deleteButton(TYPE_ID, id, node);
*/
}
}
customElements.define("cyp-song", Song);
// FIXME vyfaktorovat nekam do haje
function formatSongInfo(data) {
let lines = [];
let tokens = [];
if (data["Title"]) {
tokens.push(data["Title"]);
lines.push(tokens.join(" "));
lines.push(format.subtitle(data));
} else {
lines.push(fileName(data));
lines.push("\u00A0");
}
return lines;
}
// FIXME vyfaktorovat nekam do haje
function fileName(data) {
return data["file"].split("/").pop();
}

View File

@ -1,4 +1,4 @@
import Component from "./component.js";
import Component from "../component.js";
const prefix = "cyp";

48
app/js/elements/song.js Normal file
View File

@ -0,0 +1,48 @@
import * as format from "../format.js";
import * as html from "../html.js";
import { Item } from "../component.js";
export default class Song extends Item {
constructor(data) {
super();
this.data = data;
this.dataset.songId = data["Id"];
}
connectedCallback() {
let info = html.node("div", {className:"info"}, "", this);
let lines = formatSongInfo(this.data);
html.node("h2", {}, lines.shift(), info);
lines.length && html.node("div", {}, lines.shift(), info);
/*
playButton(TYPE_ID, id, node);
*/
}
}
customElements.define("cyp-song", Song);
// FIXME vyfaktorovat nekam do haje
function formatSongInfo(data) {
let lines = [];
let tokens = [];
if (data["Title"]) {
tokens.push(data["Title"]);
lines.push(tokens.join(" "));
lines.push(format.subtitle(data));
} else {
lines.push(fileName(data));
lines.push("\u00A0");
}
return lines;
}
// FIXME vyfaktorovat nekam do haje
function fileName(data) {
return data["file"].split("/").pop();
}

View File

@ -1,7 +1,7 @@
import * as html from "./lib/html.js";
import * as conf from "./conf.js";
import * as html from "../html.js";
import * as conf from "../conf.js";
import Component from "./component.js";
import Component from "../component.js";
const decoder = new TextDecoder("utf-8");

View File

@ -1,27 +1,15 @@
let ICONS={};
ICONS["playlist-music"] = `<svg 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"/>
ICONS["library-music"] = `<svg 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["plus"] = `<svg viewBox="0 0 24 24">
<path d="M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z"/>
</svg>`;
ICONS["folder"] = `<svg 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["shuffle"] = `<svg viewBox="0 0 24 24">
<path d="M14.83,13.41L13.42,14.82L16.55,17.95L14.5,20H20V14.5L17.96,16.54L14.83,13.41M14.5,4L16.54,6.04L4,18.59L5.41,20L17.96,7.46L20,9.5V4M10.59,9.17L5.41,4L4,5.41L9.17,10.58L10.59,9.17Z"/>
</svg>`;
ICONS["artist"] = `<svg viewBox="0 0 24 24">
<path d="M11,14C12,14 13.05,14.16 14.2,14.44C13.39,15.31 13,16.33 13,17.5C13,18.39 13.25,19.23 13.78,20H3V18C3,16.81 3.91,15.85 5.74,15.12C7.57,14.38 9.33,14 11,14M11,12C9.92,12 9,11.61 8.18,10.83C7.38,10.05 7,9.11 7,8C7,6.92 7.38,6 8.18,5.18C9,4.38 9.92,4 11,4C12.11,4 13.05,4.38 13.83,5.18C14.61,6 15,6.92 15,8C15,9.11 14.61,10.05 13.83,10.83C13.05,11.61 12.11,12 11,12M18.5,10H20L22,10V12H20V17.5A2.5,2.5 0 0,1 17.5,20A2.5,2.5 0 0,1 15,17.5A2.5,2.5 0 0,1 17.5,15C17.86,15 18.19,15.07 18.5,15.21V10Z"/>
</svg>`;
ICONS["download"] = `<svg viewBox="0 0 24 24">
<path d="M5,20H19V18H5M19,9H15V3H9V9H5L12,16L19,9Z"/>
</svg>`;
ICONS["magnify"] = `<svg viewBox="0 0 24 24">
<path d="M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z"/>
</svg>`;
ICONS["rewind"] = `<svg viewBox="0 0 24 24">
<path d="M11.5,12L20,18V6M11,18V6L2.5,12L11,18Z"/>
</svg>`;
ICONS["account-multiple"] = `<svg viewBox="0 0 24 24">
<path d="M16,13C15.71,13 15.38,13 15.03,13.05C16.19,13.89 17,15 17,16.5V19H23V16.5C23,14.17 18.33,13 16,13M8,13C5.67,13 1,14.17 1,16.5V19H15V16.5C15,14.17 10.33,13 8,13M8,11A3,3 0 0,0 11,8A3,3 0 0,0 8,5A3,3 0 0,0 5,8A3,3 0 0,0 8,11M16,11A3,3 0 0,0 19,8A3,3 0 0,0 16,5A3,3 0 0,0 13,8A3,3 0 0,0 16,11Z"/>
ICONS["playlist-music"] = `<svg 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["settings"] = `<svg viewBox="0 0 24 24">
<path d="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z"/>
@ -29,40 +17,55 @@ ICONS["settings"] = `<svg viewBox="0 0 24 24">
ICONS["pause"] = `<svg viewBox="0 0 24 24">
<path d="M14,19H18V5H14M6,19H10V5H6V19Z"/>
</svg>`;
ICONS["artist"] = `<svg viewBox="0 0 24 24">
<path d="M11,14C12,14 13.05,14.16 14.2,14.44C13.39,15.31 13,16.33 13,17.5C13,18.39 13.25,19.23 13.78,20H3V18C3,16.81 3.91,15.85 5.74,15.12C7.57,14.38 9.33,14 11,14M11,12C9.92,12 9,11.61 8.18,10.83C7.38,10.05 7,9.11 7,8C7,6.92 7.38,6 8.18,5.18C9,4.38 9.92,4 11,4C12.11,4 13.05,4.38 13.83,5.18C14.61,6 15,6.92 15,8C15,9.11 14.61,10.05 13.83,10.83C13.05,11.61 12.11,12 11,12M18.5,10H20L22,10V12H20V17.5A2.5,2.5 0 0,1 17.5,20A2.5,2.5 0 0,1 15,17.5A2.5,2.5 0 0,1 17.5,15C17.86,15 18.19,15.07 18.5,15.21V10Z"/>
</svg>`;
ICONS["volume-off"] = `<svg viewBox="0 0 24 24">
<path d="M12,4L9.91,6.09L12,8.18M4.27,3L3,4.27L7.73,9H3V15H7L12,20V13.27L16.25,17.53C15.58,18.04 14.83,18.46 14,18.7V20.77C15.38,20.45 16.63,19.82 17.68,18.96L19.73,21L21,19.73L12,10.73M19,12C19,12.94 18.8,13.82 18.46,14.64L19.97,16.15C20.62,14.91 21,13.5 21,12C21,7.72 18,4.14 14,3.23V5.29C16.89,6.15 19,8.83 19,12M16.5,12C16.5,10.23 15.5,8.71 14,7.97V10.18L16.45,12.63C16.5,12.43 16.5,12.21 16.5,12Z"/>
</svg>`;
ICONS["close"] = `<svg viewBox="0 0 24 24">
<path d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"/>
</svg>`;
ICONS["music"] = `<svg 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["minus"] = `<svg viewBox="0 0 24 24">
<path d="M19,13H5V11H19V13Z"/>
</svg>`;
ICONS["repeat"] = `<svg viewBox="0 0 24 24">
<path d="M17,17H7V14L3,18L7,22V19H19V13H17M7,7H17V10L21,6L17,2V5H5V11H7V7Z"/>
</svg>`;
ICONS["play"] = `<svg viewBox="0 0 24 24">
<path d="M8,5.14V19.14L19,12.14L8,5.14Z"/>
</svg>`;
ICONS["plus"] = `<svg viewBox="0 0 24 24">
<path d="M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z"/>
</svg>`;
ICONS["content-save"] = `<svg viewBox="0 0 24 24">
<path d="M15,9H5V5H15M12,19A3,3 0 0,1 9,16A3,3 0 0,1 12,13A3,3 0 0,1 15,16A3,3 0 0,1 12,19M17,3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V7L17,3Z"/>
</svg>`;
ICONS["library-music"] = `<svg 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["fast-forward"] = `<svg viewBox="0 0 24 24">
<path d="M13,6V18L21.5,12M4,18L12.5,12L4,6V18Z"/>
</svg>`;
ICONS["delete"] = `<svg viewBox="0 0 24 24">
<path d="M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z"/>
</svg>`;
ICONS["volume-high"] = `<svg viewBox="0 0 24 24">
<path d="M14,3.23V5.29C16.89,6.15 19,8.83 19,12C19,15.17 16.89,17.84 14,18.7V20.77C18,19.86 21,16.28 21,12C21,7.72 18,4.14 14,3.23M16.5,12C16.5,10.23 15.5,8.71 14,7.97V16C15.5,15.29 16.5,13.76 16.5,12M3,9V15H7L12,20V4L7,9H3Z"/>
</svg>`;
ICONS["minus"] = `<svg viewBox="0 0 24 24">
<path d="M19,13H5V11H19V13Z"/>
</svg>`;
ICONS["play"] = `<svg viewBox="0 0 24 24">
<path d="M8,5.14V19.14L19,12.14L8,5.14Z"/>
</svg>`;
ICONS["magnify"] = `<svg viewBox="0 0 24 24">
<path d="M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z"/>
</svg>`;
ICONS["music"] = `<svg 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["rewind"] = `<svg viewBox="0 0 24 24">
<path d="M11.5,12L20,18V6M11,18V6L2.5,12L11,18Z"/>
</svg>`;
ICONS["album"] = `<svg viewBox="0 0 24 24">
<path d="M12,11A1,1 0 0,0 11,12A1,1 0 0,0 12,13A1,1 0 0,0 13,12A1,1 0 0,0 12,11M12,16.5C9.5,16.5 7.5,14.5 7.5,12C7.5,9.5 9.5,7.5 12,7.5C14.5,7.5 16.5,9.5 16.5,12C16.5,14.5 14.5,16.5 12,16.5M12,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>`;
ICONS["download"] = `<svg viewBox="0 0 24 24">
<path d="M5,20H19V18H5M19,9H15V3H9V9H5L12,16L19,9Z"/>
</svg>`;
ICONS["account-multiple"] = `<svg viewBox="0 0 24 24">
<path d="M16,13C15.71,13 15.38,13 15.03,13.05C16.19,13.89 17,15 17,16.5V19H23V16.5C23,14.17 18.33,13 16,13M8,13C5.67,13 1,14.17 1,16.5V19H15V16.5C15,14.17 10.33,13 8,13M8,11A3,3 0 0,0 11,8A3,3 0 0,0 8,5A3,3 0 0,0 5,8A3,3 0 0,0 8,11M16,11A3,3 0 0,0 19,8A3,3 0 0,0 16,5A3,3 0 0,0 13,8A3,3 0 0,0 16,11Z"/>
</svg>`;
ICONS["close"] = `<svg viewBox="0 0 24 24">
<path d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"/>
</svg>`;
ICONS["content-save"] = `<svg viewBox="0 0 24 24">
<path d="M15,9H5V5H15M12,19A3,3 0 0,1 9,16A3,3 0 0,1 12,13A3,3 0 0,1 15,16A3,3 0 0,1 12,19M17,3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V7L17,3Z"/>
</svg>`;
ICONS["shuffle"] = `<svg viewBox="0 0 24 24">
<path d="M14.83,13.41L13.42,14.82L16.55,17.95L14.5,20H20V14.5L17.96,16.54L14.83,13.41M14.5,4L16.54,6.04L4,18.59L5.41,20L17.96,7.46L20,9.5V4M10.59,9.17L5.41,4L4,5.41L9.17,10.58L10.59,9.17Z"/>
</svg>`;
ICONS["repeat"] = `<svg viewBox="0 0 24 24">
<path d="M17,17H7V14L3,18L7,22V19H19V13H17M7,7H17V10L21,6L17,2V5H5V11H7V7Z"/>
</svg>`;
export default ICONS;

View File

@ -1,17 +0,0 @@
let storage = new Map();
export function publish(message, publisher, data) {
console.log(message, publisher, data);
if (!storage.has(message)) { return; }
storage.get(message).forEach(listener => listener(message, publisher, data));
}
export function subscribe(message, listener) {
if (!storage.has(message)) { storage.set(message, new Set()); }
storage.get(message).add(listener);
}
export function unsubscribe(message, listener) {
if (!storage.has(message)) { storage.set(message, new Set()); }
storage.get(message).remove(listener);
}

View File

@ -28,19 +28,18 @@ export function status() {
export function listQueue() {
return [
{id:1, Track:5, Title:"Title 1", Artist:"AAA", Album:"BBB", duration:30},
{Id:1, Track:5, Title:"Title 1", Artist:"AAA", Album:"BBB", duration:30},
status(),
{id:3, Track:7, Title:"Title 3", Artist:"CCC", Album:"DDD", duration:230},
{Id:3, Track:7, Title:"Title 3", Artist:"CCC", Album:"DDD", duration:230},
];
}
export async function listPlaylists() {
let lines = await command("listplaylists");
let parsed = parser.linesToStruct(lines);
let list = parsed["playlist"];
if (!list) { return []; }
return (list instanceof Array ? list : [list]);
return [
"Playlist 1",
"Playlist 2",
"Playlist 3"
];
}
export async function enqueueByFilter(filter, sort = null) {

View File

@ -1,5 +1,5 @@
import * as html from "./html.js";
import * as conf from "../conf.js";
import * as conf from "./conf.js";
const OPEN = "open";
const collator = new Intl.Collator(conf.locale, {usage:"search", sensitivity:"base"});
@ -16,7 +16,7 @@ export default class Search extends EventTarget {
if (e.target == this._input) { return; }
if (this._node.classList.contains(OPEN)) {
this.reset();
this.dispatchEvent(new Event("input"));
this.dispatchEvent(new Event("input"));
} else {
this._node.classList.add(OPEN);
}

View File

@ -5,21 +5,10 @@ export default class Selection {
this._component = component;
this._items = []; // FIXME ukladat skutecne HTML? co kdyz nastane refresh?
this._node = html.node("cyp-commands", {hidden:true});
const button = this.addCommand(_ => this.clear(), {icon:"close", label:"Clear"});
button.classList.add("last");
}
clear() {
while (this._items.length) { this._remove(this._items[0]); }
}
toggle(node) {
if (this._items.includes(node)) {
this._remove(node);
} else {
this._add(node);
}
while (this._items.length) { this.remove(this._items[0]); }
}
addCommand(cb, options) {
@ -29,14 +18,35 @@ export default class Selection {
return button;
}
_add(node) {
addCommandAll(items) {
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"});
button.classList.add("last");
return button;
}
toggle(node) {
if (this._items.includes(node)) {
this.remove(node);
} else {
this.add(node);
}
}
add(node) {
if (this._items.includes(node)) { return; }
const length = this._items.length;
this._items.push(node);
node.classList.add("selected");
if (length == 0) { this._show(); }
}
_remove(node) {
remove(node) {
const index = this._items.indexOf(node);
this._items.splice(index, 1);
node.classList.remove("selected");

View File

@ -1,9 +1,7 @@
import * as mpd from "./mpd.js";
import * as html from "./html.js";
import * as pubsub from "./pubsub.js";
import * as format from "./format.js";
import * as art from "./art.js";
import * as player from "../player.js";
export const CTX_FS = 1;
export const CTX_QUEUE = 2;