add save playlist modal and other things

This commit is contained in:
XenGi 2024-01-21 20:29:00 +01:00
parent d97a33b800
commit fa7e9bd497
Signed by: xengi
SSH key fingerprint: SHA256:FGp51kRvGOcWnTHiOI39ImwVO4A3fpvR30nPX3LpV7g
3 changed files with 221 additions and 44 deletions

143
static/controls.js vendored
View file

@ -3,6 +3,11 @@ const VOLUME_STEP = 5;
// Get control elements
const dialog_save_playlist = document.getElementById("save-playlist");
const control_playlist_name = document.getElementById("control-playlist-name");
const dialog_save_playlist_submit = document.querySelector("#save-playlist button");
const dialog_save_playlist_close = document.querySelector("#save-playlist .close");
const connection_state = document.getElementById("connection-state");
const control_update_db = document.getElementById("control-update-db");
const control_previous = document.getElementById("control-previous");
@ -25,9 +30,57 @@ const tabs = document.getElementById("tabs");
const tab_browser = document.getElementById("tab-browser");
const tab_search = document.getElementById("tab-search");
const tab_playlists = document.getElementById("tab-playlists");
const control_playlist_list = document.getElementById("control-playlist-list");
const control_replace_playlist = document.getElementById("control-replace-playlist");
const control_attach_playlist = document.getElementById("control-attach-playlist");
const control_save_playlist = document.getElementById("control-save-playlist");
const control_delete_playlist = document.getElementById("control-delete-playlist");
// UI controls
control_replace_playlist.addEventListener("click", e => {
fetch(`${API_URL}/`).then(async r => {
if (r.status !== 200) {
console.error(`API returned ${r.status}: ${r.statusText}`);
}
});
});
control_attach_playlist.addEventListener("click", e => {
fetch(`${API_URL}/`).then(async r => {
if (r.status !== 200) {
console.error(`API returned ${r.status}: ${r.statusText}`);
}
});
});
control_save_playlist.addEventListener("click", e => {
dialog_save_playlist.showModal()
});
dialog_save_playlist_close.addEventListener("click", e => {
dialog_save_playlist.close()
});
dialog_save_playlist_submit.addEventListener("click", e => {
fetch(`${API_URL}/playlists`, {method: "PUT"}).then(async r => {
if (r.status === 201) {
console.log(`Playlist "${control_playlist_name.value}" saved`)
}
});
});
control_delete_playlist.addEventListener("click", e => {
const playlist_id = control_playlist_list.value;
fetch(`${API_URL}/playlists/${playlist_id}`, {method: "DELETE"}).then(r => {
if (r.status === 204) {
console.log(`Playlist ${playlist_id} successfully deleted.`);
} else {
console.error(`API returned ${r.status}: ${r.statusText}`);
}
});
});
tab_browser.addEventListener("click", e => {
if (!tab_browser.classList.contains("active")) {
tab_browser.classList.add("active");
@ -68,31 +121,55 @@ control_update_db.addEventListener("click", e => {
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})`)
console.log(`Update started (Job ID: ${job_id})`);
e.target.disabled = true;
} else {
console.error(`API returned ${r.status}: ${r.statusText}`)
console.error(`API returned ${r.status}: ${r.statusText}`);
}
});
});
control_previous.addEventListener("click", e => {
fetch(`${API_URL}/previous_track`);
fetch(`${API_URL}/previous_track`).then(async r => {
if (200 >= r.status < 300) {
console.error(`API returned ${r.status}: ${r.statusText}`);
}
});
});
control_play_pause.addEventListener("click", e => {
if (e.target.innerHTML === "&#x23F5;&#xFE0E;") { // TODO: check is never true
fetch(`${API_URL}/pause`);
fetch(`${API_URL}/pause`).then(async r => {
if (200 >= r.status < 300) {
console.error(`API returned ${r.status}: ${r.statusText}`);
}
});
} else { // Pause
fetch(`${API_URL}/play`);
fetch(`${API_URL}/play`).then(async r => {
if (200 >= r.status < 300) {
console.error(`API returned ${r.status}: ${r.statusText}`);
}
});
}
});
control_stop.addEventListener("click", e => {
fetch(`${API_URL}/stop`);
fetch(`${API_URL}/stop`).then(async r => {
if (200 >= r.status < 300) {
console.error(`API returned ${r.status}: ${r.statusText}`);
}
});
});
control_next.addEventListener("click", e => {
fetch(`${API_URL}/next_track`);
fetch(`${API_URL}/next_track`).then(async r => {
if (200 >= r.status < 300) {
console.error(`API returned ${r.status}: ${r.statusText}`);
}
});
});
control_progress.addEventListener("change", e => {
fetch(`${API_URL}/seek/${e.target.value}`)
fetch(`${API_URL}/seek/${e.target.value}`).then(async r => {
if (200 >= r.status < 300) {
console.error(`API returned ${r.status}: ${r.statusText}`);
}
});
});
control_repeat.addEventListener("click", e => {
if (e.target.dataset.state === "on") { // TODO: check is never true
@ -102,7 +179,11 @@ control_repeat.addEventListener("click", e => {
e.target.innerHTML = "&#x1F534; repeat";
e.target.dataset.state = "on";
}
fetch(`${API_URL}/repeat`);
fetch(`${API_URL}/repeat`).then(async r => {
if (200 >= r.status < 300) {
console.error(`API returned ${r.status}: ${r.statusText}`);
}
});
});
control_shuffle.addEventListener("click", e => {
if (e.target.dataset.state === "on") { // TODO: check is never true
@ -112,29 +193,53 @@ control_shuffle.addEventListener("click", e => {
e.target.innerHTML = "&#x1F534; shuffle";
e.target.dataset.state = "on";
}
fetch(`${API_URL}/random`);
fetch(`${API_URL}/random`).then(async r => {
if (200 >= r.status < 300) {
console.error(`API returned ${r.status}: ${r.statusText}`);
}
});
});
control_xfade_minus.addEventListener("click", e => {
// TODO: not yet implemented
fetch(`${API_URL}/xfade`);
fetch(`${API_URL}/xfade`).then(async r => {
if (200 >= r.status < 300) {
console.error(`API returned ${r.status}: ${r.statusText}`);
}
});
});
control_xfade_plus.addEventListener("click", e => {
// TODO: not yet implemented
fetch(`${API_URL}/xfade`);
fetch(`${API_URL}/xfade`).then(async r => {
if (200 >= r.status < 300) {
console.error(`API returned ${r.status}: ${r.statusText}`);
}
});
});
control_volume_up.addEventListener("click", e => {
const v = Math.min(parseInt(control_volume.value) + VOLUME_STEP, 100);
fetch(`${API_URL}/volume/${v}`);
fetch(`${API_URL}/volume/${v}`).then(async r => {
if (200 >= r.status < 300) {
console.error(`API returned ${r.status}: ${r.statusText}`);
}
});
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}`);
fetch(`${API_URL}/volume/${v}`).then(async r => {
if (200 >= r.status < 300) {
console.error(`API returned ${r.status}: ${r.statusText}`);
}
});
control_volume.value = v;
});
control_volume.addEventListener("change", e => {
fetch(`${API_URL}/volume/${e.target.value}`);
fetch(`${API_URL}/volume/${e.target.value}`).then(async r => {
if (200 >= r.status < 300) {
console.error(`API returned ${r.status}: ${r.statusText}`);
}
});
});
// Create WebSocket connection.
@ -231,10 +336,14 @@ socket.addEventListener("message", (e) => {
// update song info
if ("mpd_current_song" in msg && msg.mpd_current_song != null) {
let track;
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}`
track = `<span>${msg.mpd_current_song.Artist} - ${msg.mpd_current_song.Title}</span>`
} else {
control_track.value = msg.mpd_current_song.file;
track = `<span>${msg.mpd_current_song.file}</span>`;
}
if (control_track.innerHTML.toString() !== track) {
control_track.innerHTML = track;
}
}

View file

@ -7,6 +7,17 @@
<link rel="icon" href="/favicon.ico" sizes="16x16 32x32 48x48 64x64" type="image/png" />
</head>
<body>
<dialog id="save-playlist">
<h1>Save Playlist</h1>
<button class="close">&times;</button>
<form method="dialog">
<label for="control-playlist-name">Name</label>
<input type="text" id="control-playlist-name" name="playlist-name" autofocus />
<button>Save</button>
</form>
</dialog>
<main>
<div id="nav">
<div id="control-admin">
@ -48,7 +59,10 @@
<div class="wide">
<div>
<label for="control-track">Now playing:</label>
<input type="text" id="control-track" name="track" disabled="disabled" />
<!--<input type="text" id="control-track" name="track" disabled="disabled" />-->
<div class="marquee" id="control-track">
<span>Fall On Your Sword - Shatner Of The Mount by Fall On Your Sword</span>
</div>
</div>
<div>
<label for="control-time">Time:</label>
@ -107,9 +121,10 @@
<option value="1">basedrive</option><!-- TODO: Remove this line -->
</select><!--/#control-playlist-list-->
<div>
<button>&#x2795; New</button><!-- Plus -->
<button>&#x2B06; Attach</button><!-- ⬆️ Up Arrow -->
<button>&#x1F5D1;&#xFE0F; Delete</button><!-- 🗑️ Wastebasket -->
<button id="control-replace-playlist">&#x1F504; Replace</button><!-- 🔄 Counterclockwise Arrows Button -->
<button id="control-attach-playlist">&#x2B06; Attach</button><!-- ⬆️ Up Arrow -->
<button id="control-save-playlist">&#x1F4BE; Save</button><!-- 💾 Floppy Disk -->
<button id="control-delete-playlist">&#x1F5D1;&#xFE0F; Delete</button><!-- 🗑️ Wastebasket -->
</div>
</div><!--/#playlist-browser-->
</div><!--/#browser-->

View file

@ -4,6 +4,10 @@
--background-color: #041936;
--text-color: #bbb;
--input-background-color: #28374a;
--input-border-light: #545454;
--input-border-dark: #3a3a3a;
}
/* #################### */
@ -193,12 +197,12 @@ a {
}
button {
background-color: #28374a;
background-color: var(--input-background-color);
color: var(--text-color);
border-top-color: #545454;
border-right-color: #3a3a3a;
border-bottom-color: #3a3a3a;
border-left-color: #545454;
border-top-color: var(--input-border-light);
border-right-color: var(--input-border-dark);
border-bottom-color: var(--input-border-dark);
border-left-color: var(--input-border-light);
}
button[disabled] {
@ -241,11 +245,47 @@ button .loader {
}
input[type=text] {
background-color: #28374a;
background-color: var(--input-background-color);
color: var(--text-color);
border: 1px solid black;
border-right-color: #545454;
border-bottom-color: #545454;
border-right-color: var(--input-border-light);
border-bottom-color: var(--input-border-light);
}
.marquee {
display: flex;
position: relative;
overflow: hidden;
white-space: nowrap;
width: var(--ribbon-width);
font-size: 10pt;
background-color: var(--input-background-color);
color: var(--text-color);
border: 1px solid black;
border-right-color: var(--input-border-light);
border-bottom-color: var(--input-border-light);
container-type: inline-size;
}
.wide {
display: flex;
flex-direction: column;
justify-content: space-around;
}
.marquee > * {
-webkit-animation: marquee 10s linear infinite both alternate;
animation: marquee 10s linear infinite both alternate;
}
@-webkit-keyframes marquee {
to {
transform: translateX(min(100cqw - 100%, 0px));
}
}
@keyframes marquee {
to {
transform: translateX(min(100cqw - 100%, 0px));
}
}
#nav {
@ -258,8 +298,7 @@ input[type=text] {
}
thead {
background: rgb(15,29,47);
background: linear-gradient(0deg, rgba(15,29,47,1) 0%, rgba(15,29,47,1) 50%, rgba(7,14,23,1) 100%);
background: #0F1D2F linear-gradient(0deg, rgba(15, 29, 47, 1) 0%, rgba(15, 29, 47, 1) 50%, rgba(7, 14, 23, 1) 100%);
}
th {
@ -295,7 +334,7 @@ tbody td.actions {
/* make arrow for currently playing song look nice */
#queue table tr.playing td:first-of-type::before {
content: '\2BC8'; //
content: '\2BC8'; /* ⯈ Black Medium Right-Pointing Triangle Centred */
}
#queue table tr td:first-of-type {
@ -310,11 +349,11 @@ tbody td.actions {
}
table tr:nth-child(odd) td {
background: #1e1f1a;
background-color: #1e1f1a;
}
table tr:nth-child(even) td {
background: #171812;
background-color: #171812;
}
#queue table tr:nth-child(odd).playing td,
@ -323,7 +362,7 @@ table tr:nth-child(even) td {
}
table tr:hover td {
background-color: #354158 !important; /* TODO: remove !important */
background-color: #354158;
}
#tabs {
@ -335,9 +374,9 @@ table tr:hover td {
padding: 3pt;
display: inline-block;
text-align: center;
background-color: #28374a;
background-color: var(--input-background-color);
color: var(--text-color);
border: 1px solid #545454;
border: 1px solid var(--input-border-light);
border-top-left-radius: 5pt;
border-top-right-radius: 5pt;
}
@ -346,10 +385,6 @@ table tr:hover td {
background-color: #1a1a1a;
color: var(--text-color);
border-bottom: none;
/*border-top-color: #1a1a1a;*/
/*border-right-color: #545454;*/
/*border-bottom-color: #545454;*/
/*border-left-color: #1a1a1a;*/
}
#browser {
@ -360,11 +395,11 @@ table tr:hover td {
#control-playlist-list {
font-size: 12pt;
width: 100%;
background-color: #28374a;
background-color: var(--input-background-color);
color: var(--text-color);
border: 1px solid black;
border-right-color: #545454;
border-bottom-color: #545454;
border-right-color: var(--input-border-light);
border-bottom-color: var(--input-border-light);
scrollbar-color: #490b00 #09101d; /* only in firefox: https://developer.mozilla.org/en-US/docs/Web/CSS/scrollbar-color */
}
@ -373,3 +408,21 @@ footer svg {
width: 12pt;
height: 12pt;
}
/*dialog {*/
/* position: fixed;*/
/* left: 50%;*/
/* top: 50%;*/
/* transform: translate(-50%, -50%);*/
/*}*/
dialog {
background-color: var(--background-color);
color: var(--text-color);
}
dialog .close {
position: absolute;
top: 1pt;
right: 1pt;
}