v0.51.0: the web mix player has previous and next buttons
This commit is contained in:
@@ -659,7 +659,8 @@ persistence) → GUI (Qt widgets that read the manager and connect to its signal
|
|||||||
nobody can answer a prompt. Steps: `ServerCheckWorker` (`ssh test -d`; an
|
nobody can answer a prompt. Steps: `ServerCheckWorker` (`ssh test -d`; an
|
||||||
existing folder asks before replacing it), `ExportWorker`, then
|
existing folder asks before replacing it), `ExportWorker`, then
|
||||||
`UploadWorker`, all shown in `gui/web_upload_progress.py`. The web
|
`UploadWorker`, all shown in `gui/web_upload_progress.py`. The web
|
||||||
player has a **shuffle** button (and the S key): a shuffled walk that starts at
|
player has **previous/next** buttons (the arrow keys, wrapping at either
|
||||||
|
end, through the shuffle order when on) and a **shuffle** button (and the S key): a shuffled walk that starts at
|
||||||
the current song and plays every song once. A mix can carry several
|
the current song and plays every song once. A mix can carry several
|
||||||
images (`plan.images`): the dialog always keeps one empty row to add
|
images (`plan.images`): the dialog always keeps one empty row to add
|
||||||
another, and the page shows them in order under the description, a blank
|
another, and the page shows them in order under the description, a blank
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
"""LinTunes — iTunes-style music library manager and player for Linux."""
|
"""LinTunes — iTunes-style music library manager and player for Linux."""
|
||||||
|
|
||||||
__version__ = "0.50.0"
|
__version__ = "0.51.0"
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
.audiojs audio { position: absolute; left: -1px; }
|
.audiojs audio { position: absolute; left: -1px; }
|
||||||
|
|
||||||
.audiojs {
|
.audiojs {
|
||||||
width: 286px;
|
width: 334px;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
background: #eee;
|
background: #eee;
|
||||||
color: #000;
|
color: #000;
|
||||||
@@ -40,9 +40,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.audiojs .play-pause {
|
.audiojs .play-pause {
|
||||||
width: 40px;
|
width: 36px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
padding: 4px 6px;
|
padding: 4px 2px;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
float: left;
|
float: left;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -88,6 +88,21 @@
|
|||||||
.audiojs .time em { padding: 0px 2px 0px 0px; color: #000; font-style: normal; }
|
.audiojs .time em { padding: 0px 2px 0px 0px; color: #000; font-style: normal; }
|
||||||
.audiojs .time strong { padding: 0px 0px 0px 2px; font-weight: normal; }
|
.audiojs .time strong { padding: 0px 0px 0px 2px; font-weight: normal; }
|
||||||
|
|
||||||
|
/* Previous / next, either side of play-pause: plain black glyphs. */
|
||||||
|
.audiojs .skip {
|
||||||
|
float: left;
|
||||||
|
width: 24px;
|
||||||
|
height: 36px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #000;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.audiojs .prev { margin-left: 4px; }
|
||||||
|
.audiojs .skip:hover { color: var(--accent, #8c764a); }
|
||||||
|
.audiojs .skip:focus-visible { outline: 1px dotted #000; }
|
||||||
|
|
||||||
/* Shuffle: black like the other glyphs while off, the accent while on
|
/* Shuffle: black like the other glyphs while off, the accent while on
|
||||||
* (the same fill as the progress bar, with the text color picked for it). */
|
* (the same fill as the progress bar, with the text color picked for it). */
|
||||||
.audiojs .shuffle {
|
.audiojs .shuffle {
|
||||||
|
|||||||
@@ -24,10 +24,20 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var WRAPPER =
|
var WRAPPER =
|
||||||
|
'<div class="skip prev" role="button" tabindex="0" title="Previous (←)">' +
|
||||||
|
'<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">' +
|
||||||
|
'<path d="M6 5h2.5v14H6zM20 5v14L9 12z" fill="currentColor"/>' +
|
||||||
|
'</svg>' +
|
||||||
|
'</div>' +
|
||||||
'<div class="play-pause">' +
|
'<div class="play-pause">' +
|
||||||
'<p class="play"></p><p class="pause"></p>' +
|
'<p class="play"></p><p class="pause"></p>' +
|
||||||
'<p class="loading"></p><p class="error"></p>' +
|
'<p class="loading"></p><p class="error"></p>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
|
'<div class="skip next" role="button" tabindex="0" title="Next (→)">' +
|
||||||
|
'<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">' +
|
||||||
|
'<path d="M15.5 5H18v14h-2.5zM4 5v14l11-7z" fill="currentColor"/>' +
|
||||||
|
'</svg>' +
|
||||||
|
'</div>' +
|
||||||
'<div class="scrubber"><div class="progress"></div><div class="loaded"></div></div>' +
|
'<div class="scrubber"><div class="progress"></div><div class="loaded"></div></div>' +
|
||||||
'<div class="time"><em class="played">00:00</em>/<strong class="duration">00:00</strong></div>' +
|
'<div class="time"><em class="played">00:00</em>/<strong class="duration">00:00</strong></div>' +
|
||||||
'<div class="error-message"></div>' +
|
'<div class="error-message"></div>' +
|
||||||
@@ -62,7 +72,9 @@
|
|||||||
played: wrap.querySelector(".played"),
|
played: wrap.querySelector(".played"),
|
||||||
duration: wrap.querySelector(".duration"),
|
duration: wrap.querySelector(".duration"),
|
||||||
error: wrap.querySelector(".error-message"),
|
error: wrap.querySelector(".error-message"),
|
||||||
shuffle: wrap.querySelector(".shuffle")
|
shuffle: wrap.querySelector(".shuffle"),
|
||||||
|
prev: wrap.querySelector(".prev"),
|
||||||
|
next: wrap.querySelector(".next")
|
||||||
};
|
};
|
||||||
|
|
||||||
function state(name) {
|
function state(name) {
|
||||||
@@ -127,6 +139,8 @@
|
|||||||
return {
|
return {
|
||||||
element: audio,
|
element: audio,
|
||||||
shuffleButton: ui.shuffle,
|
shuffleButton: ui.shuffle,
|
||||||
|
prevButton: ui.prev,
|
||||||
|
nextButton: ui.next,
|
||||||
load: function (src) {
|
load: function (src) {
|
||||||
ui.progress.style.width = "0%";
|
ui.progress.style.width = "0%";
|
||||||
ui.loaded.style.width = "0%";
|
ui.loaded.style.width = "0%";
|
||||||
@@ -176,16 +190,30 @@
|
|||||||
function first() { return (order || items)[0]; }
|
function first() { return (order || items)[0]; }
|
||||||
function last() { var list = order || items; return list[list.length - 1]; }
|
function last() { var list = order || items; return list[list.length - 1]; }
|
||||||
|
|
||||||
player.shuffleButton.addEventListener("click", function () {
|
// The previous/next buttons and the arrow keys: wrap around at either
|
||||||
setShuffle(!order);
|
// end, through the shuffled walk when shuffle is on.
|
||||||
});
|
function goNext() {
|
||||||
player.shuffleButton.addEventListener("keydown", function (e) {
|
var current = document.querySelector("ol li.playing");
|
||||||
|
select((current && step(current, 1)) || first(), true);
|
||||||
|
}
|
||||||
|
function goPrev() {
|
||||||
|
var current = document.querySelector("ol li.playing");
|
||||||
|
select((current && step(current, -1)) || last(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function button(el, action) {
|
||||||
|
el.addEventListener("click", action);
|
||||||
|
el.addEventListener("keydown", function (e) {
|
||||||
if (e.key === "Enter" || e.key === " ") {
|
if (e.key === "Enter" || e.key === " ") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setShuffle(!order);
|
action();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
button(player.shuffleButton, function () { setShuffle(!order); });
|
||||||
|
button(player.prevButton, goPrev);
|
||||||
|
button(player.nextButton, goNext);
|
||||||
|
|
||||||
function select(li, autoplay) {
|
function select(li, autoplay) {
|
||||||
items.forEach(function (other) { other.classList.remove("playing"); });
|
items.forEach(function (other) { other.classList.remove("playing"); });
|
||||||
@@ -215,13 +243,12 @@
|
|||||||
var tag = (target.tagName || "").toLowerCase();
|
var tag = (target.tagName || "").toLowerCase();
|
||||||
if (tag === "input" || tag === "textarea" || target.isContentEditable) return;
|
if (tag === "input" || tag === "textarea" || target.isContentEditable) return;
|
||||||
|
|
||||||
var current = document.querySelector("ol li.playing");
|
|
||||||
if (e.key === "ArrowRight") {
|
if (e.key === "ArrowRight") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
select((current && step(current, 1)) || first(), true);
|
goNext();
|
||||||
} else if (e.key === "ArrowLeft") {
|
} else if (e.key === "ArrowLeft") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
select((current && step(current, -1)) || last(), true);
|
goPrev();
|
||||||
} else if ((e.key === "s" || e.key === "S") && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
} else if ((e.key === "s" || e.key === "S") && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setShuffle(!order);
|
setShuffle(!order);
|
||||||
|
|||||||
@@ -565,3 +565,11 @@ def test_upload_window_shows_the_size(qapp, window):
|
|||||||
_wait_for(qapp, lambda: _FakeUpload.runs)
|
_wait_for(qapp, lambda: _FakeUpload.runs)
|
||||||
from lintunes.gui import web_upload_progress as wp
|
from lintunes.gui import web_upload_progress as wp
|
||||||
assert "KB" in window._web_progress.status(wp.EXPORT)
|
assert "KB" in window._web_progress.status(wp.EXPORT)
|
||||||
|
|
||||||
|
|
||||||
|
def test_player_ships_previous_and_next_buttons():
|
||||||
|
js = (exporter.templates() / "player.js").read_text()
|
||||||
|
css = (exporter.templates() / "player.css").read_text()
|
||||||
|
assert 'class="skip prev"' in js and 'class="skip next"' in js
|
||||||
|
assert "button(player.prevButton, goPrev)" in js
|
||||||
|
assert ".audiojs .skip" in css
|
||||||
|
|||||||
Reference in New Issue
Block a user