Skip to content

Commit

Permalink
feat(playbar): add Widget (spicetify#2347)
Browse files Browse the repository at this point in the history
  • Loading branch information
kyrie25 authored May 15, 2023
1 parent 73908b5 commit da5bdd9
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 39 deletions.
7 changes: 3 additions & 4 deletions CustomApps/lyrics-plus/PlaybarButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
? Spicetify.Platform.History.push("/lyrics-plus")
: Spicetify.Platform.History.goBack(),
false,
false,
Spicetify.Platform.History.location.pathname === "/lyrics-plus",
false
);

const style = document.createElement("style");
style.innerHTML = `
.main-nowPlayingBar-lyricsButton {
display: none !important;
.main-nowPlayingBar-lyricsButton {
display: none !important;
}
li[data-id="/lyrics-plus"] {
display: none;
Expand All @@ -32,7 +32,6 @@
if (event.detail?.name === "playbar-button") event.detail.value ? setPlaybarButton() : removePlaybarButton();
});

button.active = Spicetify.Platform.History.location.pathname === "/lyrics-plus";
Spicetify.Platform.History.listen(location => (button.active = location.pathname === "/lyrics-plus"));

function setPlaybarButton() {
Expand Down
114 changes: 83 additions & 31 deletions Extensions/trashbin.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@
content.appendChild(header);

content.appendChild(createSlider("trashbin-enabled", "Enabled", trashbinStatus, refreshEventListeners));
content.appendChild(
createSlider("TrashbinWidgetIcon", "Show Widget Icon", enableWidget, state =>
state && initValue("trashbin-enabled", true) ? widget.register() : widget.deregister()
)
);

// Local Storage
header = document.createElement("h2");
Expand All @@ -69,7 +74,9 @@
createButton("Clear ", "Clear all items from trashbin (cannot be reverted).", () => {
trashSongList = {};
trashArtistList = {};
setWidgetState(false);
putDataLocal();
Spicetify.showNotification("Trashbin cleared!");
})
);
}
Expand Down Expand Up @@ -118,35 +125,49 @@
color: rgba(var(--spice-rgb-text), .3);
}
button.reset {
line-height: 1.5rem;
font-weight: 700;
border: 0;
-moz-user-select: none;
background-color: var(--spice-text);
color: var(--spice-main);
font-size: inherit;
padding-block: 12px;
padding-inline: 32px;
}
button.reset:active {
background-color: var(--spice-subtext);
box-shadow: none;
transform: scale(1);
font-weight: 700;
font-size: medium;
background-color: transparent;
border-radius: 500px;
transition-duration: 33ms;
transition-property: background-color, border-color, color, box-shadow, filter, transform;
padding-inline: 15px;
border: 1px solid #727272;
color: var(--spice-text);
min-block-size: 32px;
cursor: pointer;
}
button.reset:hover {
transform: scale(1.04);
border-color: var(--spice-text);
}`;
content.appendChild(style);
}

function initValue(item, defaultValue) {
try {
const value = JSON.parse(Spicetify.LocalStorage.get(item));
return value ?? defaultValue;
} catch {
return defaultValue;
}
}

// Settings Variables - Initial Values
const trashbinStatus = JSON.parse(Spicetify.LocalStorage.get("trashbin-enabled")) ?? true;
const trashbinStatus = initValue("trashbin-enabled", true);
const enableWidget = initValue("TrashbinWidgetIcon", true);

// Settings Menu Initialization
const content = document.createElement("div");
styleSettings();
settingsContent();

const trashbinIcon =
'<svg xmlns="http://www.w3.org/2000/svg" width="18px" height="18px" viewBox="0 0 24 24" fill="currentcolor"><path d="M3 6v18h18v-18h-18zm5 14c0 .552-.448 1-1 1s-1-.448-1-1v-10c0-.552.448-1 1-1s1 .448 1 1v10zm5 0c0 .552-.448 1-1 1s-1-.448-1-1v-10c0-.552.448-1 1-1s1 .448 1 1v10zm5 0c0 .552-.448 1-1 1s-1-.448-1-1v-10c0-.552.448-1 1-1s1 .448 1 1v10zm4-18v2h-20v-2h5.711c.9 0 1.631-1.099 1.631-2h5.315c0 .901.73 2 1.631 2h5.712z"/></svg>';

const THROW_TEXT = "Place in Trashbin";
const UNTHROW_TEXT = "Remove from Trashbin";

new Spicetify.Menu.Item(
"Trashbin",
false,
Expand All @@ -156,38 +177,67 @@
content
});
},
'<svg xmlns="http://www.w3.org/2000/svg" width="18px" height="18px" viewBox="0 0 24 24" fill="currentcolor"><path d="M3 6v18h18v-18h-18zm5 14c0 .552-.448 1-1 1s-1-.448-1-1v-10c0-.552.448-1 1-1s1 .448 1 1v10zm5 0c0 .552-.448 1-1 1s-1-.448-1-1v-10c0-.552.448-1 1-1s1 .448 1 1v10zm5 0c0 .552-.448 1-1 1s-1-.448-1-1v-10c0-.552.448-1 1-1s1 .448 1 1v10zm4-18v2h-20v-2h5.711c.9 0 1.631-1.099 1.631-2h5.315c0 .901.73 2 1.631 2h5.712z"/></svg>'
trashbinIcon
).register();

// LocalStorage Setup
let trashSongList = {};
let trashArtistList = {};
let userHitBack = false;
const widget = new Spicetify.Playbar.Widget(
THROW_TEXT,
trashbinIcon,
self => {
const uri = Spicetify.Player.data.track.uri;
const uriObj = Spicetify.URI.fromString(uri);
const type = uriObj.type;

if (!trashSongList[uri]) {
trashSongList[uri] = true;
if (shouldSkipCurrentTrack(uri, type)) Spicetify.Player.next();
Spicetify.showNotification("Song added to trashbin");
} else {
delete trashSongList[uri];
setWidgetState(false);
Spicetify.showNotification("Song removed from trashbin");
}

const THROW_TEXT = "Place In Trashbin";
const UNTHROW_TEXT = "Remove From Trashbin";
putDataLocal();
},
false,
false,
enableWidget
);

trashSongList = JSON.parse(Spicetify.LocalStorage.get("TrashSongList")) || {};
trashArtistList = JSON.parse(Spicetify.LocalStorage.get("TrashArtistList")) || {};
// LocalStorage Setup
let trashSongList = initValue("TrashSongList", {});
let trashArtistList = initValue("TrashArtistList", {});
let userHitBack = false;
const eventListener = () => (userHitBack = true);

putDataLocal();
refreshEventListeners(trashbinStatus);
setWidgetState(trashSongList[Spicetify.Player.data.track.uri]);

function refreshEventListeners(state) {
if (state) {
skipBackBtn.addEventListener("click", () => (userHitBack = true));
skipBackBtn.addEventListener("click", eventListener);
Spicetify.Player.addEventListener("songchange", watchChange);
widget.register();
} else {
skipBackBtn.removeEventListener("click", () => (userHitBack = true));
skipBackBtn.removeEventListener("click", eventListener);
Spicetify.Player.removeEventListener("songchange", watchChange);
widget.deregister();
}
}

function setWidgetState(state) {
widget.active = !!state;
widget.label = state ? UNTHROW_TEXT : THROW_TEXT;
}

function watchChange() {
const data = Spicetify.Player.data || Spicetify.Queue;
if (!data) return;

const isBanned = trashSongList[data.track.uri];
setWidgetState(isBanned);

if (userHitBack) {
userHitBack = false;
Expand Down Expand Up @@ -255,11 +305,13 @@

if (!list[uri]) {
list[uri] = true;
if (shouldSkipCurrentTrack(uri, type)) {
Spicetify.Player.next();
}
if (shouldSkipCurrentTrack(uri, type)) Spicetify.Player.next();
Spicetify.Player.data?.track.uri === uri && setWidgetState(true);
Spicetify.showNotification(type === Spicetify.URI.Type.TRACK ? "Song added to trashbin" : "Artist added to trashbin");
} else {
delete list[uri];
Spicetify.Player.data?.track.uri === uri && setWidgetState(false);
Spicetify.showNotification(type === Spicetify.URI.Type.TRACK ? "Song removed from trashbin" : "Artist removed from trashbin");
}

putDataLocal();
Expand All @@ -271,7 +323,7 @@
* @returns {boolean}
*/
function shouldAddContextMenu(uris) {
if (uris.length > 1) {
if (uris.length > 1 || !initValue("trashbin-enabled", true)) {
return false;
}

Expand All @@ -290,7 +342,7 @@
return false;
}

const cntxMenu = new Spicetify.ContextMenu.Item(THROW_TEXT, toggleThrow, shouldAddContextMenu);
const cntxMenu = new Spicetify.ContextMenu.Item(THROW_TEXT, toggleThrow, shouldAddContextMenu, trashbinIcon);
cntxMenu.register();

function putDataLocal() {
Expand Down
21 changes: 20 additions & 1 deletion globals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1108,8 +1108,11 @@ declare namespace Spicetify {
* Add button in player controls
*/
namespace Playbar {
/**
* Create a button on the right side of the playbar
*/
class Button {
constructor(label: string, icon: Icon | string, onClick: (self: Button) => void, disabled?: boolean, active?: boolean, registerOnCreate?: boolean);
constructor(label: string, icon: Icon | string, onClick?: (self: Button) => void, disabled?: boolean, active?: boolean, registerOnCreate?: boolean);
label: string;
icon: string;
onClick: (self: Button) => void;
Expand All @@ -1120,6 +1123,22 @@ declare namespace Spicetify {
register: () => void;
deregister: () => void;
}

/**
* Create a widget next to track info
*/
class Widget {
constructor(label: string, icon: Icon | string, onClick?: (self: Widget) => void, disabled?: boolean, active?: boolean, registerOnCreate?: boolean);
label: string;
icon: string;
onClick: (self: Widget) => void;
disabled: boolean;
active: boolean;
element: HTMLButtonElement;
tippy: any;
register: () => void;
deregister: () => void;
}
}

/**
Expand Down
90 changes: 87 additions & 3 deletions jsHelper/spicetifyWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -1328,7 +1328,6 @@ Spicetify.Topbar = (function() {
this.disabled = disabled;
this.tippy = Spicetify.Tippy?.(this.element, {
content: label,
placement: "bottom",
...Spicetify.TippyProps,
});
this.label = label;
Expand Down Expand Up @@ -1403,7 +1402,7 @@ Spicetify.Playbar = (function() {
const buttonsStash = new Set();

class Button {
constructor(label, icon, onClick, disabled = false, active = false, registerOnCreate = true) {
constructor(label, icon, onClick = () => {}, disabled = false, active = false, registerOnCreate = true) {
this.element = document.createElement("button");
this.element.classList.add("main-genericButton-button");
this.element.style.display = "block";
Expand Down Expand Up @@ -1480,7 +1479,92 @@ Spicetify.Playbar = (function() {
});
}

return { Button };
const widgetStash = new Set();
let nowPlayingWidget;

class Widget {
constructor(label, icon, onClick = () => {}, disabled = false, active = false, registerOnCreate = true) {
this.element = document.createElement("button");
this.element.classList.add("main-addButton-button");
this.icon = icon;
this.onClick = onClick;
this.disabled = disabled;
this.active = active;
this.tippy = Spicetify.Tippy?.(this.element, {
content: label,
...Spicetify.TippyProps,
});
this.label = label;
registerOnCreate && this.register();
}
get label() { return this._label; }
set label(text) {
this._label = text;
if (!this.tippy) this.element.setAttribute("title", text);
else this.tippy.setContent(text);
}
get icon() { return this._icon; }
set icon(input) {
if (input && Spicetify.SVGIcons[input]) {
input = `<svg height="16" width="16" viewBox="0 0 16 16" fill="currentColor">${Spicetify.SVGIcons[input]}</svg>`;
}
this._icon = input;
this.element.innerHTML = input;
}
get onClick() { return this._onClick; }
set onClick(func) {
this._onClick = func;
this.element.onclick = () => this._onClick(this);
}
get disabled() { return this._disabled; }
set disabled(bool) {
this._disabled = bool;
this.element.disabled = bool;
this.element.classList.toggle("main-addButton-disabled", bool);
}
set active(bool) {
this._active = bool;
this.element.classList.toggle("main-addButton-active", bool);
}
get active() { return this._active; }
register() {
widgetStash.add(this.element);
nowPlayingWidget?.append(this.element);
}
deregister() {
widgetStash.delete(this.element);
this.element.remove();
}
}

function waitForWidgetMounted() {
nowPlayingWidget = document.querySelector(".main-nowPlayingWidget-nowPlaying");
if (!nowPlayingWidget) {
setTimeout(waitForWidgetMounted, 300);
return;
}
nowPlayingWidget.append(...widgetStash);
};

(function attachObserver() {
const leftPlayer = document.querySelector(".main-nowPlayingBar-left");
if (!leftPlayer) {
setTimeout(attachObserver, 300);
return;
}
waitForWidgetMounted();
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.removedNodes.length > 0) {
nowPlayingWidget = null;
waitForWidgetMounted();
}
});
});
observer.observe(leftPlayer, {childList: true});
})();

return { Button, Widget };
})();

(function waitForHistoryAPI() {
Expand Down

0 comments on commit da5bdd9

Please sign in to comment.