Fixed dragging and improved mobile phone support

This commit is contained in:
Jurn Wubben 2026-02-03 14:23:25 +01:00
parent 241b5c5a3e
commit 2ac96108d8
2 changed files with 77 additions and 51 deletions

View file

@ -1,4 +1,5 @@
const DEFAULT_WS_URL = "ws://localhost:8000/ws/spotify"; const DEFAULT_WS_URL =
localStorage.getItem("spotiqueueUrl") ?? "ws://localhost:8000/ws/spotify";
const DEFAULT_RECONNECT_MS = 1000; const DEFAULT_RECONNECT_MS = 1000;
class SpotiQueue { class SpotiQueue {
@ -72,14 +73,16 @@ class SpotiQueue {
this.socket = null; this.socket = null;
} }
if (this.button) this.button.style.color = "#e22134";
console.log("[SpotiQueue] Disconnecting from server, Bye!"); console.log("[SpotiQueue] Disconnecting from server, Bye!");
} }
send(objOrString) { send(objOrString) {
if (!this.socket || this.socket.readyState !== WebSocket.OPEN) return; if (!this.socket || this.socket.readyState !== WebSocket.OPEN) return;
const payload = (typeof objOrString === "string") const payload =
? objOrString typeof objOrString === "string"
: JSON.stringify(objOrString); ? objOrString
: JSON.stringify(objOrString);
try { try {
this.socket.send(payload); this.socket.send(payload);
} catch (err) { } catch (err) {
@ -141,7 +144,7 @@ class SpotiQueue {
includeAuthors: true, includeAuthors: true,
}); });
console.log(data) console.log(data);
songs = data.searchV2.tracksV2.items.reduce((o, c) => { songs = data.searchV2.tracksV2.items.reduce((o, c) => {
const item = c.item.data; const item = c.item.data;
@ -151,14 +154,12 @@ class SpotiQueue {
o.push({ o.push({
name: item.name, name: item.name,
uri: item.uri, uri: item.uri,
artists: item.artists.items.map(v => v.profile?.name), artists: item.artists.items.map((v) => v.profile?.name),
album: { album: {
name: album.name, name: album.name,
coverUrl: album.coverArt.sources.sort((a, b) => coverUrl: album.coverArt.sources
b.height - a.height .sort((a, b) => b.height - a.height)
).map((v) => .map((v) => v.url)[0],
v.url
)[0],
}, },
}); });
@ -171,16 +172,17 @@ class SpotiQueue {
console.log("[SpotiQueue] Found", songs); console.log("[SpotiQueue] Found", songs);
this.send({ this.send({
"c": "search", c: "search",
"d": { d: {
"id": id, id: id,
"results": songs, results: songs,
}, },
}); });
} }
} }
_onError() { _onError(e) {
try { try {
console.log(e);
if (this.socket) this.socket.close(); if (this.socket) this.socket.close();
} catch { } catch {
// ignore // ignore
@ -200,35 +202,45 @@ class SpotiQueue {
); );
} }
if (this.button) this.button.style.color = ""; if (this.button) this.button.style.color = ""
this.closing = false; this.closing = false;
} }
initUi() { initUi() {
const btn = new Spicetify.Topbar.Button( const btn = new Spicetify.Topbar.Button(
"SpotiQueue", "SpotiQueue",
`<p>hi</p>`, // SVG icon or markup "enhance", // SVG icon or markup
() => { () => {},
if (!this.socket) {
if (Spicetify.GraphQL.Definitions.searchDesktop === undefined) {
Spicetify.showNotification("Please search something (e.g a song) before trying to use SpotiQueue. This will load required dependencies.")
return
}
this.connect();
this.button.innerText = "bye";
} else {
this.stop();
this.button.innerText = "hi";
}
},
); );
this.button = btn.button; this.button = btn.button;
this.button.style.color = "#e22134"
this.button.addEventListener("click", (e) => {
console.log(e.pointerId);
if (this.socket) {
this.stop();
return
}
if (Spicetify.GraphQL.Definitions.searchDesktop === undefined) {
Spicetify.Platform.History.push("/search/");
setTimeout(() => {
this.button.click();
Spicetify.Platform.History.goBack();
}, 200);
return;
}
this.closing = false;
this.button.style.color = ""
this.connect();
});
Spicetify.Player.addEventListener("songchange", (info) => { Spicetify.Player.addEventListener("songchange", (info) => {
console.log(info); console.log(info);
if ( if (
this.socket && this.socket.readyState === WebSocket.OPEN && this.socket &&
this.socket.readyState === WebSocket.OPEN &&
!this.startedPlaying !this.startedPlaying
) { ) {
Spicetify.Player.pause(); Spicetify.Player.pause();
@ -242,14 +254,17 @@ class SpotiQueue {
(function init() { (function init() {
if ( if (
!Spicetify.Player || !Spicetify.Platform || !Spicetify.GraphQL || !Spicetify.Player ||
!Spicetify.GraphQL.Request || !Spicetify.GraphQL.Definitions !Spicetify.Platform ||
!Spicetify.GraphQL ||
!Spicetify.GraphQL.Request ||
!Spicetify.GraphQL.Definitions
) { ) {
setTimeout(init, 100); setTimeout(init, 100);
console.log("[SpotiQueue] loading extension... "); console.log("[SpotiQueue] loading extension... ");
return; return;
} }
const client = new SpotiQueue(); globalThis.spotiQueue = new SpotiQueue();
client.initUi(); globalThis.spotiQueue.initUi();
})(); })();

View file

@ -7,6 +7,11 @@
<title>SpotiQueue</title> <title>SpotiQueue</title>
<link href="https://cdn.jsdelivr.net/npm/daisyui@5" rel="stylesheet" type="text/css"> <link href="https://cdn.jsdelivr.net/npm/daisyui@5" rel="stylesheet" type="text/css">
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script> <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<style>
li.song.draggable-mirror {
@apply bg-base-200
}
</style>
</head> </head>
<body> <body>
@ -35,7 +40,7 @@
<!-- Main page --> <!-- Main page -->
<div id="m-page" class="hidden w-full h-dvh flex flex-col p-2"> <div id="m-page" class="hidden w-full h-dvh flex flex-col p-2">
<!-- Desktop navbar --> <!-- Desktop navbar -->
<div class="hidden sm:block pb-2 w-full flex-none"> <div class="hidden md:block pb-2 w-full flex-none">
<div class="navbar bg-base-300 shadow-sm rounded-md"> <div class="navbar bg-base-300 shadow-sm rounded-md">
<div class="navbar-start"> <div class="navbar-start">
<a class="btn btn-ghost text-xl">SpotiQueue</a> <a class="btn btn-ghost text-xl">SpotiQueue</a>
@ -69,12 +74,12 @@
<!-- Main content --> <!-- Main content -->
<div class="flex-grow flex flex-col md:flex-row-reverse gap-4 m-2"> <div class="flex-grow flex flex-col md:flex-row-reverse gap-4 m-2">
<div class="flex-3 bg-base-200 rounded-md shadow-md"> <div class="flex-3 bg-base-200 rounded-md shadow-md">
<ul class="list bg-base-100 rounded-box shadow-md m-1" id="songlist-merged"> <ul class="list bg-base-100 rounded-box shadow-md m-1 overflow-y-scroll" id="songlist-merged">
<li class="p-4 pb-2 text-xs opacity-60 tracking-wide">Merged Queue</li> <li class="p-4 pb-2 text-xs opacity-60 tracking-wide">Merged Queue</li>
</ul> </ul>
</div> </div>
<div class="flex-5 md:flex-4 lg:flex-5 bg-base-200 rounded-md shadow-md"> <div class="flex-5 md:flex-4 lg:flex-5 bg-base-200 rounded-md shadow-md">
<ul class="list bg-base-100 rounded-box shadow-md m-1" id="songlist-personal"> <ul class="list bg-base-100 rounded-box shadow-md m-1 overflow-y-scroll" id="songlist-personal">
<li class="p-4 pb-2 text-xs opacity-60 tracking-wide">Personal Queue</li> <li class="p-4 pb-2 text-xs opacity-60 tracking-wide">Personal Queue</li>
</ul> </ul>
</div> </div>
@ -82,7 +87,7 @@
<!-- Mobile dock --> <!-- Mobile dock -->
<div class="dock sm:hidden justify-self-end z-0 static"> <div class="dock md:hidden justify-self-end z-0 static">
<button class="dock-active"> <button class="dock-active">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 256 256" class="size-[1.2em]"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 256 256" class="size-[1.2em]">
<path fill="currentColor" <path fill="currentColor"
@ -118,7 +123,7 @@
<div class="hidden"> <div class="hidden">
<!-- template song personal --> <!-- template song personal -->
<template id="template-song-personal"> <template id="template-song-personal">
<li class="list-row song"> <li class="list-row song " draggable="true">
<div class="drag-handle cursor-move select-none touch-none text-2xl opacity-50 mr-2">⋮⋮</div> <div class="drag-handle cursor-move select-none touch-none text-2xl opacity-50 mr-2">⋮⋮</div>
<div><img class="size-10 rounded-box" <div><img class="size-10 rounded-box"
src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" alt="thumbnail"></div> src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" alt="thumbnail"></div>
@ -168,7 +173,7 @@
</template> </template>
</div> </div>
<dialog id="modal" class="modal modal-bottom sm:modal-middle"> <dialog id="modal" class="modal modal-bottom md:modal-middle">
<div class="modal-box"> <div class="modal-box">
<h3 id="modal-header" class="text-lg font-bold"></h3> <h3 id="modal-header" class="text-lg font-bold"></h3>
<p id="modal-text" class="py-4"></p> <p id="modal-text" class="py-4"></p>
@ -183,8 +188,8 @@
<button>close</button> <button>close</button>
</form> </form>
</dialog> </dialog>
<dialog id="search-dock-modal" class="modal modal-bottom sm:modal-middle"> <dialog id="search-dock-modal" class="modal modal-bottom md:modal-middle">
<div class="modal-box flex flex-col sm:h-auto max-h-[50dvh]"> <div class="modal-box flex flex-col md:h-auto max-h-[50dvh]">
<ul id="search-dock-songs" class="list overflow-y-auto flex-grow"> <ul id="search-dock-songs" class="list overflow-y-auto flex-grow">
</ul> </ul>
@ -397,6 +402,9 @@
connected.value = msg.d.status ? 0 : 1; connected.value = msg.d.status ? 0 : 1;
} }
else if (msg.c === "search") { else if (msg.c === "search") {
DOMSearchNavDropown.innerHTML = "";
DOMSearchDockSongs.innerHTML = "";
for (let song of msg.d.songs) { for (let song of msg.d.songs) {
const el = document.importNode(DOMTemplateSongSearch.content, true); const el = document.importNode(DOMTemplateSongSearch.content, true);
//el.querySelector(".tabular-nums").innerText = (index + 1).toString().padStart(2, "0"); //el.querySelector(".tabular-nums").innerText = (index + 1).toString().padStart(2, "0");
@ -471,7 +479,7 @@
d: {query} d: {query}
})) }))
DOMSearchNavDropown.innerHTML = ""; DOMSearchNavDropown.innerHTML = '<div class="skeleton w-full h-16"></div>';
DOMSearchNavDropown.showPopover(); DOMSearchNavDropown.showPopover();
} }
function dockSearch(e) { function dockSearch(e) {
@ -484,6 +492,8 @@
c: "search", c: "search",
d: {query} d: {query}
})) }))
DOMSearchDockSongs.innerHTML = '<div class="skeleton w-full h-16 mb-4"></div>';
} }
Array.prototype.move = function (from, to) { Array.prototype.move = function (from, to) {
@ -492,12 +502,13 @@
let sortable = new Sortable(DOMMainSongPersonal, { let sortable = new Sortable(DOMMainSongPersonal, {
draggable: '.song', draggable: '.song',
handle: '.drag-handle', handle: '.drag-handle',
mirror: { /*mirror: {
constrainDimensions: true, constrainDimensions: true,
} }*/
}); });
sortable.on("drag:start", (evt) => { sortable.on("sortable:start", (evt) => {
if (evt.source.draggable) return; if (evt.dragEvent.source.draggable) return;
evt.cancel() evt.cancel()
}) })
sortable.on('sortable:stop', (evt) => { sortable.on('sortable:stop', (evt) => {
@ -518,7 +529,7 @@
DOMSearchNavForm.addEventListener("submit", navSearch) DOMSearchNavForm.addEventListener("submit", navSearch)
DOMSearchDockForm.addEventListener("submit", dockSearch) DOMSearchDockForm.addEventListener("submit", dockSearch)
DOMSearchDockButton.addEventListener("click", () => { DOMSearchDockButton.addEventListener("click", () => {
DOMSearchDockSongs.innerHTML = ""; DOMSearchDockSongs.innerHTML = '';
DOMSearchDockModal.showModal() DOMSearchDockModal.showModal()
DOMSearchDockInput.focus(); DOMSearchDockInput.focus();
setTimeout(window.scrollTo({left: 0, top: document.body.scrollHeight, behavior: "smooth"}), 500); setTimeout(window.scrollTo({left: 0, top: document.body.scrollHeight, behavior: "smooth"}), 500);