gui functions nearly done

This commit is contained in:
XenGi 2023-12-30 15:02:13 +01:00
parent 114d6ba4da
commit f65918db8b
Signed by: xengi
SSH key fingerprint: SHA256:FGp51kRvGOcWnTHiOI39ImwVO4A3fpvR30nPX3LpV7g
6 changed files with 297 additions and 81 deletions

172
static/controls.js vendored
View file

@ -1,7 +1,9 @@
const API_URL = `${document.location.protocol}://${document.location.host}/api`;
const API_URL = `${document.location.protocol}//${document.location.host}/api`;
const VOLUME_STEP = 5;
// Get control elements
const connection_state = document.getElementById("connection-state");
const control_update_db = document.getElementById("control-update-db");
const control_previous = document.getElementById("control-previous");
const control_play_pause = document.getElementById("control-play-pause");
@ -13,18 +15,31 @@ const control_shuffle = document.getElementById("control-shuffle");
const control_xfade = document.getElementById("control-xfade");
const control_xfade_minus = document.getElementById("control-xfade-minus");
const control_xfade_plus = document.getElementById("control-xfade-plus");
const control_volume = document.getElementById("control-volume");
const control_volume_up = document.getElementById("control-volume-up");
const control_volume_down = document.getElementById("control-volume-down");
const queue_table = document.querySelector("#queue tbody");
const control_track = document.getElementById("control-track");
const control_time = document.getElementById("control-time");
// Add API calls to controls
control_update_db.addEventListener("click", e => {
fetch(`${API_URL}/update_db`);
console.log("Issuing database update")
fetch(`${API_URL}/update_db`).then(async r => {
if (r.status === 200) {
const job_id = await r.text();
console.log(`Update started (Job ID: ${job_id})`)
} else {
console.error(`API returned ${r.status}: ${r.statusText}`)
}
});
});
control_previous.addEventListener("click", e => {
fetch(`${API_URL}/previous_track`);
});
control_play_pause.addEventListener("click", e => {
if (e.target.innerText === "⏵︎") { // Play
if (e.target.innerHTML === "⏵︎") { // TODO: check is never true
fetch(`${API_URL}/pause`);
} else { // Pause
fetch(`${API_URL}/play`);
@ -40,10 +55,20 @@ control_progress.addEventListener("change", e => {
fetch(`${API_URL}/seek/${e.target.value}`)
});
control_repeat.addEventListener("click", e => {
if (e.target.innerHTML === "🔴 repeat") { // TODO: check is never true
e.target.innerHTML = "🔘 repeat";
} else {
e.target.innerHTML = "🔴 repeat";
}
fetch(`${API_URL}/repeat`);
});
control_shuffle.addEventListener("click", e => {
fetch(`${API_URL}/shuffle`);
if (e.target.innerHTML === "🔴 shuffle") { // TODO: check is never true
e.target.innerHTML = "🔘 shuffle";
} else {
e.target.innerHTML = "🔴 shuffle";
}
fetch(`${API_URL}/random`);
});
control_xfade_minus.addEventListener("click", e => {
// TODO: not yet implemented
@ -53,46 +78,129 @@ control_xfade_plus.addEventListener("click", e => {
// TODO: not yet implemented
fetch(`${API_URL}/xfade`);
});
control_volume_up.addEventListener("click", e => {
const v = Math.min(parseInt(control_volume.value) + VOLUME_STEP, 100);
fetch(`${API_URL}/volume/${v}`);
control_volume.value = v;
});
control_volume_down.addEventListener("click", e => {
const v = Math.max(parseInt(control_volume.value) - VOLUME_STEP, 0);
fetch(`${API_URL}/volume/${v}`);
control_volume.value = v;
});
control_volume.addEventListener("change", e => {
fetch(`${API_URL}/volume/${e.target.value}`);
});
// Create WebSocket connection.
const socket = new WebSocket(`${document.location.protocol === "https" ? "wss" : "ws"}://${document.location.host}/ws`);
const socket = new WebSocket(`${document.location.protocol === "https:" ? "wss" : "ws"}://${document.location.host}/ws`);
// Connection opened
socket.addEventListener("open", (e) => {
socket.send("Hello Server!");
});
// Listen for messages
// Listen for messages and update UI state
socket.addEventListener("message", (e) => {
console.log("Message from server");
// Print out mpd response
console.log(`DEBUG: ${e.data}`); // DEBUG
const msg = JSON.parse(e.data);
if ("error" in msg.mpd_status) {
console.error(msg.mpd_status.error);
if ("mpd_status" in msg) {
if (msg.mpd_status == null) {
connection_state.innerHTML = "❌ Disconnected"; // ✅ Check Mark Button
} else {
// print error if present
if ("error" in msg.mpd_status) {
console.error(msg.mpd_status.error);
}
// update "Update DB" button
if ("updating_db" in msg.mpd_status) {
control_update_db.disabled = true;
} else {
if (control_update_db.disabled) {
console.log("Database update done.")
}
control_update_db.disabled = false;
}
// update play/pause button
if ("state" in msg.mpd_status && msg.mpd_status.state === "play") {
control_play_pause.innerHTML = "⏸︎"; // Pause
} else {
control_play_pause.innerHTML = "⏵︎"; // Play
}
// update playback time
if ("elapsed" in msg.mpd_status && "duration" in msg.mpd_status) {
const elapsed_hours = Math.floor(msg.mpd_status.elapsed / 3600);
const elapsed_minutes = Math.floor((msg.mpd_status.elapsed - elapsed_hours * 3600) / 60);
const elapsed_seconds = Math.floor(msg.mpd_status.elapsed - elapsed_hours * 3600 - elapsed_minutes * 60);
const duration_hours = Math.floor(msg.mpd_status.duration / 3600);
const duration_minutes = Math.floor((msg.mpd_status.duration - duration_hours * 3600) / 60);
const duration_seconds = Math.floor(msg.mpd_status.duration - duration_hours * 3600 - duration_minutes * 60);
control_time.value = `${elapsed_hours}:${elapsed_minutes.toString().padStart(2, '0')}:${elapsed_seconds.toString().padStart(2, '0')}/${duration_hours}:${duration_minutes.toString().padStart(2, '0')}:${duration_seconds.toString().padStart(2, '0')}`;
}
if ("elapsed" in msg.mpd_status) {
control_progress.value = msg.mpd_status.elapsed;
}
if ("duration" in msg.mpd_status) {
control_progress.max = msg.mpd_status.duration;
}
// update repeat state
if ("repeat" in msg.mpd_status) {
if (msg.mpd_status.repeat === "1") {
control_repeat.innerHTML = "🔴 repeat"; // 🔴 Red Circle
} else {
control_repeat.innerHTML = "🔘 repeat"; // 🔘 Radio Button
}
}
// update shuffle state
if ("random" in msg.mpd_status) {
if (msg.mpd_status.random === "1") {
control_shuffle.innerHTML = "🔴 shuffle"; // 🔴 Red Circle
} else {
control_shuffle.innerHTML = "🔘 shuffle"; // 🔘 Radio Button
}
}
// update crossfade state
if ("xfade" in msg.mpd_status) {
control_xfade.value = msg.mpd_status.xfade;
}
// update volume
if ("volume" in msg.mpd_status) {
control_volume.value = msg.mpd_status.volume;
}
}
}
if ("updating_db" in msg.mpd_status) {
control_update_db.disable();
} else {
control_update_db.enable();
// update song info
if ("mpd_current_song" in msg && msg.mpd_current_song != null) {
if ("Artist" in msg.mpd_current_song && "Title" in msg.mpd_current_song) {
control_track.value = `${msg.mpd_current_song.Artist} - ${msg.mpd_current_song.Title}`
} else {
control_track.value = msg.mpd_current_song.file;
}
}
if ("state" in msg.mpd_status && msg.mpd_status.state === "play") {
control_play_pause.innerText = "⏸︎"; // Pause
} else {
control_play_pause.innerText = "⏵︎"; // Play
}
if ("elapsed" in msg.mpd_status) {
control_progress.value = msg.mpd_status.elapsed;
}
if ("duration" in msg.mpd_status) {
control_progress.max = msg.mpd_status.duration;
}
if ("repeat" in msg.mpd_status) {
control_repeat.checked = msg.mpd_status.repeat;
}
if ("random" in msg.mpd_status) {
control_shuffle.checked = msg.mpd_status.random;
}
if ("xfade" in msg.mpd_status) {
control_xfade.value = msg.mpd_status.xfade;
if ("mpd_error" in msg) {
console.error(`MPD Error: ${msg.mpd_error}`)
}
});
// Request MPD status every second
window.setInterval(() => {
if (socket.readyState === socket.OPEN) {
socket.send("#status");
connection_state.innerHTML = "✅ Connected"; // ❌ Cross Mark
} else {
connection_state.innerHTML = "❌ Disconnected"; // ✅ Check Mark Button
}
}, 1000);

View file

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" xmlns="http://www.w3.org/1999/html">
<head>
<meta charset="UTF-8" />
<title>Sanic</title>
@ -9,13 +9,14 @@
<body>
<main>
<div id="nav">
<div>
<div id="control-admin">
<button id="connection-state">&#x274C; Disconnected</button> <!-- ❌ Cross Mark -->
<button>Login</button>
<button>Config</button>
<button id="control-update-db" disabled="disabled">Update DB</button>
</div>
<div>
<div>
<div class="spaced">
<button id="control-previous">&#x23EE;&#xFE0E;</button> <!-- ⏮️ Last Track Button -->
<button id="control-stop">&#x23F9;&#xFE0E;</button> <!-- ⏹️ Stop Button -->
<button id="control-play-pause">&#x23F5;&#xFE0E;</button> <!-- ▶️ Play or ⏸️ Pause Button -->
@ -27,24 +28,38 @@
</div>
</div>
<div>
<div>
<div class="spaced">
<button id="control-repeat">&#x1F518; repeat</button>
<button id="control-shuffle">&#x1F518; shuffle</button>
</div>
<div class="spaced">
<label for="control-xfade">xfade</label>
<div>
<div>
<label for="control-repeat">repeat</label>
<input type="checkbox" id="control-repeat" name="repeat" />
</div>
<div>
<label for="control-shuffle">shuffle</label>
<input type="checkbox" id="control-shuffle" name="shuffle" />
</div>
</div>
<div>
<label for="control-xfade">xfade</label>
<button id="control-xfade-minus">&#x2796;</button>
<input type="number" id="control-xfade" name="xfade" value="00" />
<button id="control-xfade-plus">&#x2795;</button>
</div>
</div>
<div class="spaced">
<button id="control-volume-down">&#x1F509;</button> <!-- 🔉 Speaker with sound wave -->
<input id="control-volume" name="volume" type="range" min="0" max="100" value="50" />
<button id="control-volume-up">&#x1F50A;</button> <!-- 🔊 Speaker with sound waves -->
</div>
</div>
<div>
<p>Now playing:</p>
<div>
<label for="control-track">Track:</label>
<input type="text" id="control-track" name="track" disabled="disabled" />
</div>
<div>
<label for="control-time">Time:</label>
<input type="text" id="control-time" name="time" value="00:00:00/00:00:00" disabled="disabled" />
</div>
</div>
<div>
<img id="sanic-logo" alt="sanic logo" src="/img/sanic-logo.webp" />
<div>Sanic &copy; 2023</div>
</div>
</div>
<div id="queue">

View file

@ -1,5 +1,5 @@
/* #################### */
/* ####### main ####### */
/* #### structure ##### */
/* #################### */
html, body { margin: 0; height: 100%; }
@ -37,6 +37,21 @@ table {
width: 100%;
}
#control-admin {
display: flex;
flex-direction: column;
}
#sanic-logo {
max-width: 80%;
max-height: 80%;
}
.spaced {
display: flex;
justify-content: space-between;
}
/* #################### */
/* ###### debug ####### */
/* #################### */
@ -62,3 +77,58 @@ div {
#control-xfade[type=number] {
-moz-appearance: textfield;
}
#control-xfade {
width: 2em;
}
#control-previous,
#control-play-pause,
#control-stop,
#control-next {
width: 2.5em;
height: 2.5em;
text-align: center;
}
/*
#control-track {
transform: translateX(100%);
-moz-transform: translateX(100%);
-webkit-transform: translateX(100%);
animation: scroll-left 20s linear infinite;
-moz-animation: scroll-left 2s linear infinite;
-webkit-animation: scroll-left 2s linear infinite;
}
@keyframes scroll-left {
0% {
transform: translateX(100%);
-moz-transform: translateX(100%);
-webkit-transform: translateX(100%);
}
100% {
transform: translateX(-100%);
-moz-transform: translateX(-100%);
-webkit-transform: translateX(-100%);
}
}
@-moz-keyframes scroll-left {
0% {
-moz-transform: translateX(100%);
}
100% {
-moz-transform: translateX(-100%);
}
}
@-webkit-keyframes scroll-left {
0% {
-webkit-transform: translateX(100%);
}
100% {
-webkit-transform: translateX(-100%);
}
}
*/