add save playlist modal and other things
This commit is contained in:
		
							parent
							
								
									d97a33b800
								
							
						
					
					
						commit
						fa7e9bd497
					
				
					 3 changed files with 221 additions and 44 deletions
				
			
		
							
								
								
									
										143
									
								
								static/controls.js
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										143
									
								
								static/controls.js
									
										
									
									
										vendored
									
									
								
							|  | @ -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 === "⏵︎") {  // 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 = "🔴 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 = "🔴 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; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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">×</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>➕ New</button><!-- ➕ Plus --> | ||||
|         <button>⬆ Attach</button><!-- ⬆️ Up Arrow --> | ||||
|         <button>🗑️ Delete</button><!-- 🗑️ Wastebasket --> | ||||
|         <button id="control-replace-playlist">🔄 Replace</button><!-- 🔄 Counterclockwise Arrows Button --> | ||||
|         <button id="control-attach-playlist">⬆ Attach</button><!-- ⬆️ Up Arrow --> | ||||
|         <button id="control-save-playlist">💾 Save</button><!-- 💾 Floppy Disk --> | ||||
|         <button id="control-delete-playlist">🗑️ Delete</button><!-- 🗑️ Wastebasket --> | ||||
|       </div> | ||||
|     </div><!--/#playlist-browser--> | ||||
|   </div><!--/#browser--> | ||||
|  |  | |||
|  | @ -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; | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue