Fixed dragging and improved mobile phone support
This commit is contained in:
parent
241b5c5a3e
commit
2ac96108d8
2 changed files with 77 additions and 51 deletions
|
|
@ -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();
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue