Use dotter

This commit is contained in:
Kristofers Solo
2024-08-28 09:02:07 +03:00
parent d888080cc7
commit a42ded1119
1200 changed files with 1231 additions and 2261 deletions

View File

@@ -0,0 +1,156 @@
(async function() {
while (!Spicetify.React || !Spicetify.ReactDOM) {
await new Promise(resolve => setTimeout(resolve, 10));
}
"use strict";
var library = (() => {
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/extensions/collection_wrapper.tsx
var collection_wrapper_exports = {};
__export(collection_wrapper_exports, {
default: () => collection_wrapper_default
});
// node_modules/uuid/dist/esm-browser/rng.js
var getRandomValues;
var rnds8 = new Uint8Array(16);
function rng() {
if (!getRandomValues) {
getRandomValues = typeof crypto !== "undefined" && crypto.getRandomValues && crypto.getRandomValues.bind(crypto);
if (!getRandomValues) {
throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");
}
}
return getRandomValues(rnds8);
}
// node_modules/uuid/dist/esm-browser/stringify.js
var byteToHex = [];
for (let i = 0; i < 256; ++i) {
byteToHex.push((i + 256).toString(16).slice(1));
}
function unsafeStringify(arr, offset = 0) {
return byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]];
}
// node_modules/uuid/dist/esm-browser/native.js
var randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto);
var native_default = {
randomUUID
};
// node_modules/uuid/dist/esm-browser/v4.js
function v4(options, buf, offset) {
if (native_default.randomUUID && !buf && !options) {
return native_default.randomUUID();
}
options = options || {};
const rnds = options.random || (options.rng || rng)();
rnds[6] = rnds[6] & 15 | 64;
rnds[8] = rnds[8] & 63 | 128;
if (buf) {
offset = offset || 0;
for (let i = 0; i < 16; ++i) {
buf[offset + i] = rnds[i];
}
return buf;
}
return unsafeStringify(rnds);
}
var v4_default = v4;
// src/extensions/collection_wrapper.tsx
var CollectionWrapper = class {
constructor() {
this.getCollections = () => {
return this._collections;
};
this.createCollection = (name) => {
const collection = {
id: v4_default(),
name,
items: []
};
this._collections.push(collection);
this.saveCollections();
Spicetify.showNotification("Collection Created");
return collection;
};
this.deleteCollection = (collectionID) => {
this._collections = this._collections.filter((collection) => collection.id !== collectionID);
this.saveCollections();
Spicetify.showNotification("Collection Deleted");
};
this.getCollection = (collectionID) => {
return this._collections.find((collection) => collection.id === collectionID);
};
this.renameCollection = (collectionID, name) => {
const collection = this.getCollection(collectionID);
if (!collection)
throw new Error("Collection is not defined");
collection.name = name;
this.saveCollections();
Spicetify.showNotification("Collection Renamed");
};
this.addToCollection = (collectionID, albumURI) => {
const collection = this.getCollection(collectionID);
if (!collection)
throw new Error("Collection is not defined");
Spicetify.GraphQL.Request(Spicetify.GraphQL.Definitions.getAlbum, {
uri: albumURI,
locale: "en",
offset: 0,
limit: 1
}).then((res) => {
var _a, _b, _c, _d, _e, _f, _g;
const data = res.data.albumUnion;
const albumItem = {
uri: data.uri,
name: data.name,
artist: (_d = (_c = (_b = (_a = data.artists) == null ? void 0 : _a.items) == null ? void 0 : _b[0]) == null ? void 0 : _c.profile) == null ? void 0 : _d.name,
image: ((_g = (_f = (_e = data.coverArt) == null ? void 0 : _e.sources) == null ? void 0 : _f[0]) == null ? void 0 : _g.url) || ""
};
collection.items.push(albumItem);
this.saveCollections();
});
Spicetify.showNotification("Item Added to Collection");
};
this.removeFromCollection = (collectionID, albumURI) => {
const collection = this.getCollection(collectionID);
if (!collection)
throw new Error("Collection is not defined");
collection.items = collection.items.filter((album) => album.uri !== albumURI);
this.saveCollections();
Spicetify.showNotification("Item Removed from Collection");
};
this.getCollectionForItem = (albumURI) => {
return this._collections.filter((collection) => collection.items.some((item) => item.uri === albumURI));
};
this.saveCollections = () => {
localStorage.setItem("library:collections", JSON.stringify(this._collections));
};
this._collections = JSON.parse(localStorage.getItem("library:collections") || "[]");
}
};
var collection_wrapper_default = CollectionWrapper;
return __toCommonJS(collection_wrapper_exports);
})();
})();

View File

@@ -0,0 +1,300 @@
(async function() {
while (!Spicetify.React || !Spicetify.ReactDOM) {
await new Promise(resolve => setTimeout(resolve, 10));
}
"use strict";
var library = (() => {
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __publicField = (obj, key, value) => {
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
return value;
};
// src/extensions/collections_wrapper.ts
var collections_wrapper_exports = {};
__export(collections_wrapper_exports, {
default: () => collections_wrapper_default
});
// ../node_modules/uuid/dist/esm-browser/rng.js
var getRandomValues;
var rnds8 = new Uint8Array(16);
function rng() {
if (!getRandomValues) {
getRandomValues = typeof crypto !== "undefined" && crypto.getRandomValues && crypto.getRandomValues.bind(crypto);
if (!getRandomValues) {
throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");
}
}
return getRandomValues(rnds8);
}
// ../node_modules/uuid/dist/esm-browser/stringify.js
var byteToHex = [];
for (let i = 0; i < 256; ++i) {
byteToHex.push((i + 256).toString(16).slice(1));
}
function unsafeStringify(arr, offset = 0) {
return byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]];
}
// ../node_modules/uuid/dist/esm-browser/native.js
var randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto);
var native_default = {
randomUUID
};
// ../node_modules/uuid/dist/esm-browser/v4.js
function v4(options, buf, offset) {
if (native_default.randomUUID && !buf && !options) {
return native_default.randomUUID();
}
options = options || {};
const rnds = options.random || (options.rng || rng)();
rnds[6] = rnds[6] & 15 | 64;
rnds[8] = rnds[8] & 63 | 128;
if (buf) {
offset = offset || 0;
for (let i = 0; i < 16; ++i) {
buf[offset + i] = rnds[i];
}
return buf;
}
return unsafeStringify(rnds);
}
var v4_default = v4;
// src/extensions/collections_wrapper.ts
var _CollectionsWrapper = class extends EventTarget {
_collections;
constructor() {
super();
this._collections = JSON.parse(localStorage.getItem("library:collections") || "[]");
}
saveCollections() {
localStorage.setItem("library:collections", JSON.stringify(this._collections));
this.dispatchEvent(new CustomEvent("update", { detail: this._collections }));
}
getCollection(uri) {
return this._collections.find((collection) => collection.uri === uri);
}
async getCollectionContents(uri) {
const collection = this.getCollection(uri);
if (!collection)
throw new Error("Collection not found");
const items = this._collections.filter((collection2) => collection2.parentCollection === uri);
const albums = await Spicetify.Platform.LibraryAPI.getContents({
filters: ["0"],
offset: 0,
limit: 9999
});
items.push(...albums.items.filter((album) => collection.items.includes(album.uri)));
return items;
}
async getContents(props) {
const { collectionUri, offset, limit, textFilter } = props;
let items = collectionUri ? await this.getCollectionContents(collectionUri) : this._collections;
const openedCollectionName = collectionUri ? this.getCollection(collectionUri)?.name : void 0;
if (textFilter) {
const regex = new RegExp(`\\b${textFilter}`, "i");
items = items.filter((collection) => regex.test(collection.name));
}
items = items.slice(offset, offset + limit);
return { items, totalLength: this._collections.length, offset, openedCollectionName };
}
async cleanCollections() {
for (const collection of this._collections) {
const boolArray = await Spicetify.Platform.LibraryAPI.contains(...collection.items);
if (boolArray.includes(false)) {
collection.items = collection.items.filter((_, i) => boolArray[i]);
this.saveCollections();
Spicetify.showNotification("Album removed from collection");
this.syncCollection(collection.uri);
}
}
}
async syncCollection(uri) {
const collection = this.getCollection(uri);
if (!collection)
return;
const { PlaylistAPI } = Spicetify.Platform;
if (!collection.syncedPlaylistUri)
return;
const playlist = await PlaylistAPI.getPlaylist(collection.syncedPlaylistUri);
const playlistTracks = playlist.contents.items.filter((t) => t.type === "track").map((t) => t.uri);
const collectionTracks = await this.getTracklist(uri);
const wanted = collectionTracks.filter((track) => !playlistTracks.includes(track));
const unwanted = playlistTracks.filter((track) => !collectionTracks.includes(track)).map((uri2) => ({ uri: uri2, uid: [] }));
if (wanted.length)
await PlaylistAPI.add(collection.syncedPlaylistUri, wanted, { before: "end" });
if (unwanted.length)
await PlaylistAPI.remove(collection.syncedPlaylistUri, unwanted);
}
unsyncCollection(uri) {
const collection = this.getCollection(uri);
if (!collection)
return;
collection.syncedPlaylistUri = void 0;
this.saveCollections();
}
async getTracklist(collectionUri) {
const collection = this.getCollection(collectionUri);
if (!collection)
return [];
return Promise.all(
collection.items.map(async (uri) => {
const album = await Spicetify.Platform.LibraryAPI.getAlbum(uri);
return album.items.map((t) => t.uri);
})
).then((tracks) => tracks.flat());
}
async convertToPlaylist(uri) {
const collection = this.getCollection(uri);
if (!collection)
return;
const { Platform, showNotification } = Spicetify;
const { RootlistAPI, PlaylistAPI } = Platform;
if (collection.syncedPlaylistUri) {
showNotification("Synced Playlist already exists", true);
return;
}
try {
const playlistUri = await RootlistAPI.createPlaylist(collection.name, { before: "start" });
const items = await this.getTracklist(uri);
await PlaylistAPI.add(playlistUri, items, { before: "start" });
collection.syncedPlaylistUri = playlistUri;
} catch (error) {
console.error(error);
showNotification("Failed to create playlist", true);
}
}
async createCollectionFromDiscog(artistUri) {
const [raw, info] = await Promise.all([
Spicetify.GraphQL.Request(Spicetify.GraphQL.Definitions.queryArtistDiscographyAlbums, {
uri: artistUri,
offset: 0,
limit: 50
}),
Spicetify.GraphQL.Request(Spicetify.GraphQL.Definitions.queryArtistOverview, {
uri: artistUri,
locale: Spicetify.Locale.getLocale(),
includePrerelease: false
})
]);
const items = raw?.data?.artistUnion.discography.albums?.items;
const name = info?.data?.artistUnion.profile.name;
const image = info?.data?.artistUnion.visuals.avatarImage?.sources?.[0]?.url;
if (!name || !items?.length) {
Spicetify.showNotification("Artist not found or has no albums");
return;
}
const collectionUri = this.createCollection(`${name} Albums`);
if (image)
this.setCollectionImage(collectionUri, image);
for (const album of items) {
this.addAlbumToCollection(collectionUri, album.releases.items[0].uri);
}
}
createCollection(name, parentCollection = "") {
const id = v4_default();
this._collections.push({
type: "collection",
uri: id,
name,
items: [],
addedAt: new Date(),
lastPlayedAt: new Date(),
parentCollection
});
this.saveCollections();
Spicetify.showNotification("Collection created");
return id;
}
deleteCollection(uri) {
this._collections = this._collections.filter((collection) => collection.uri !== uri);
this.saveCollections();
Spicetify.showNotification("Collection deleted");
}
deleteCollectionAndAlbums(uri) {
const collection = this.getCollection(uri);
if (!collection)
return;
for (const album of collection.items) {
Spicetify.Platform.LibraryAPI.remove({ uris: [album] });
}
this.deleteCollection(uri);
}
async addAlbumToCollection(collectionUri, albumUri) {
const collection = this.getCollection(collectionUri);
if (!collection)
return;
await Spicetify.Platform.LibraryAPI.add({ uris: [albumUri] });
collection.items.push(albumUri);
this.saveCollections();
Spicetify.showNotification("Album added to collection");
this.syncCollection(collectionUri);
}
removeAlbumFromCollection(collectionUri, albumUri) {
const collection = this.getCollection(collectionUri);
if (!collection)
return;
collection.items = collection.items.filter((item) => item !== albumUri);
this.saveCollections();
Spicetify.showNotification("Album removed from collection");
this.syncCollection(collectionUri);
}
getCollectionsWithAlbum(albumUri) {
return this._collections.filter((collection) => {
return collection.items.some((item) => item === albumUri);
});
}
renameCollection(uri, name) {
const collection = this.getCollection(uri);
if (!collection)
return;
collection.name = name;
this.saveCollections();
Spicetify.showNotification("Collection renamed");
}
setCollectionImage(uri, url) {
const collection = this.getCollection(uri);
if (!collection)
return;
collection.image = url;
this.saveCollections();
Spicetify.showNotification("Collection image set");
}
removeCollectionImage(uri) {
const collection = this.getCollection(uri);
if (!collection)
return;
collection.image = void 0;
this.saveCollections();
Spicetify.showNotification("Collection image removed");
}
};
var CollectionsWrapper = _CollectionsWrapper;
__publicField(CollectionsWrapper, "INSTANCE", new _CollectionsWrapper());
window.CollectionsWrapper = CollectionsWrapper.INSTANCE;
var collections_wrapper_default = CollectionsWrapper;
return __toCommonJS(collections_wrapper_exports);
})();
})();

View File

@@ -0,0 +1,280 @@
(async function() {
while (!Spicetify.React || !Spicetify.ReactDOM) {
await new Promise(resolve => setTimeout(resolve, 10));
}
"use strict";
var library = (() => {
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// external-global-plugin:react
var require_react = __commonJS({
"external-global-plugin:react"(exports, module) {
module.exports = Spicetify.React;
}
});
// src/extensions/config_loader.tsx
var config_loader_exports = {};
__export(config_loader_exports, {
default: () => config_loader_default
});
// src/components/settings_modal.tsx
var import_react = __toESM(require_react());
var TextInput = (props) => {
const textId = `text-input:${props.storageKey}`;
return /* @__PURE__ */ import_react.default.createElement("label", {
className: "text-input-wrapper"
}, /* @__PURE__ */ import_react.default.createElement("input", {
className: "text-input",
type: "text",
value: props.value || "",
"data-storage-key": props.storageKey,
placeholder: props.placeholder,
id: textId,
title: `Text input for ${props.storageKey}`,
onChange: props.onChange
}));
};
var Dropdown = (props) => {
const dropdownId = `dropdown:${props.storageKey}`;
return /* @__PURE__ */ import_react.default.createElement("label", {
className: "dropdown-wrapper"
}, /* @__PURE__ */ import_react.default.createElement("select", {
className: "dropdown-input",
value: props.value,
"data-storage-key": props.storageKey,
id: dropdownId,
title: `Dropdown for ${props.storageKey}`,
onChange: props.onChange
}, props.options.map((option, index) => /* @__PURE__ */ import_react.default.createElement("option", {
key: index,
value: option
}, option))));
};
var TooltipIcon = () => {
return /* @__PURE__ */ import_react.default.createElement("svg", {
role: "img",
height: "16",
width: "16",
className: "Svg-sc-ytk21e-0 uPxdw nW1RKQOkzcJcX6aDCZB4",
viewBox: "0 0 16 16"
}, /* @__PURE__ */ import_react.default.createElement("path", {
d: "M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8z"
}), /* @__PURE__ */ import_react.default.createElement("path", {
d: "M7.25 12.026v-1.5h1.5v1.5h-1.5zm.884-7.096A1.125 1.125 0 007.06 6.39l-1.431.448a2.625 2.625 0 115.13-.784c0 .54-.156 1.015-.503 1.488-.3.408-.7.652-.973.818l-.112.068c-.185.116-.26.203-.302.283-.046.087-.097.245-.097.57h-1.5c0-.47.072-.898.274-1.277.206-.385.507-.645.827-.846l.147-.092c.285-.177.413-.257.526-.41.169-.23.213-.397.213-.602 0-.622-.503-1.125-1.125-1.125z"
}));
};
var ConfigRow = (props) => {
console.log(props);
const enabled = !!props.modalConfig[props.storageKey];
const value = props.modalConfig[props.storageKey];
const updateItem = (storageKey, state) => {
props.modalConfig[storageKey] = state;
console.debug(`toggling ${storageKey} to ${state}`);
localStorage.setItem(`library:config:${storageKey}`, String(state));
props.updateConfig(props.modalConfig);
};
const settingsToggleChange = (newValue, storageKey) => {
updateItem(storageKey, newValue);
if (props.callback)
props.callback(newValue);
};
const settingsTextChange = (event) => {
console.log("yoohoo");
updateItem(event.target.dataset.storageKey, event.target.value);
console.log(props.callback);
if (props.callback)
props.callback(event.target.value);
};
const settingsDropdownChange = (event) => {
updateItem(event.target.dataset.storageKey, event.target.value);
if (props.callback)
props.callback(event.target.value);
};
const element = () => {
switch (props.type) {
case "dropdown":
return /* @__PURE__ */ import_react.default.createElement(Dropdown, {
name: props.name,
storageKey: props.storageKey,
value,
options: props.options || [],
onChange: settingsDropdownChange
});
case "text":
return /* @__PURE__ */ import_react.default.createElement(TextInput, {
name: props.name,
storageKey: props.storageKey,
value,
placeholder: props.placeholder,
onChange: settingsTextChange
});
default:
return /* @__PURE__ */ import_react.default.createElement(Spicetify.ReactComponent.Toggle, {
id: `toggle:${props.storageKey}`,
value: enabled,
onSelected: (newValue) => {
settingsToggleChange(newValue, props.storageKey);
}
});
}
};
return /* @__PURE__ */ import_react.default.createElement("div", {
className: "setting-row"
}, /* @__PURE__ */ import_react.default.createElement("label", {
className: "col description"
}, props.name, props.desc && /* @__PURE__ */ import_react.default.createElement(Spicetify.ReactComponent.TooltipWrapper, {
label: /* @__PURE__ */ import_react.default.createElement("div", {
dangerouslySetInnerHTML: { __html: props.desc }
}),
renderInline: true,
showDelay: 10,
placement: "top",
labelClassName: "tooltip",
disabled: false
}, /* @__PURE__ */ import_react.default.createElement("div", {
className: "tooltip-icon"
}, /* @__PURE__ */ import_react.default.createElement(TooltipIcon, null)))), /* @__PURE__ */ import_react.default.createElement("div", {
className: "col action"
}, element()));
};
var SettingsModal = ({ CONFIG, settings, updateAppConfig }) => {
const [modalConfig, setModalConfig] = import_react.default.useState(__spreadValues({}, CONFIG));
const updateConfig = (CONFIG2) => {
updateAppConfig(__spreadValues({}, CONFIG2));
setModalConfig(__spreadValues({}, CONFIG2));
};
const configRows = settings.map((setting, index) => {
console.log(setting);
if (setting.sectionHeader) {
return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, index != 0 ? /* @__PURE__ */ import_react.default.createElement("br", null) : /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null), /* @__PURE__ */ import_react.default.createElement("h2", {
className: "section-header"
}, setting.sectionHeader), /* @__PURE__ */ import_react.default.createElement(ConfigRow, {
name: setting.name,
storageKey: setting.key,
type: setting.type,
options: setting.options,
placeholder: setting.placeholder,
desc: setting.desc,
modalConfig,
updateConfig,
callback: setting.callback
}));
}
return /* @__PURE__ */ import_react.default.createElement(ConfigRow, {
name: setting.name,
storageKey: setting.key,
type: setting.type,
options: setting.options,
placeholder: setting.placeholder,
desc: setting.desc,
modalConfig,
updateConfig,
callback: setting.callback
});
});
return /* @__PURE__ */ import_react.default.createElement("div", {
id: "stats-config-container"
}, configRows);
};
var settings_modal_default = SettingsModal;
// src/extensions/config_loader.tsx
var import_react2 = __toESM(require_react());
var getLocalStorageDataFromKey = (key, fallback) => {
const data = localStorage.getItem(key);
if (data) {
try {
return JSON.parse(data);
} catch (err) {
return data;
}
} else {
return fallback;
}
};
(function wait() {
const { LocalStorageAPI } = Spicetify == null ? void 0 : Spicetify.Platform;
if (!LocalStorageAPI) {
setTimeout(wait, 100);
return;
}
})();
async function loadConfig(configSettings) {
const { PopupModal } = Spicetify;
await new Promise((resolve) => {
(function checkPopupModal() {
if (PopupModal) {
resolve(void 0);
} else {
setTimeout(checkPopupModal, 100);
}
})();
});
const settingsArray = configSettings.map((setting) => {
return { [setting.key]: getLocalStorageDataFromKey(`library:config:${setting.key}`, setting.def) };
});
let CONFIG = window.CONFIG = Object.assign({}, ...settingsArray);
const updateConfig = (config) => {
window.CONFIG = __spreadValues({}, config);
console.log("updated config", config);
};
const launchModal = window.launchModal = () => {
console.log(settingsArray);
PopupModal.display({
title: "Library Settings",
content: /* @__PURE__ */ import_react2.default.createElement(settings_modal_default, {
CONFIG,
settings: configSettings,
updateAppConfig: updateConfig
}),
isLarge: true
});
};
return { CONFIG, launchModal };
}
var config_loader_default = loadConfig;
return __toCommonJS(config_loader_exports);
})();
})();

View File

@@ -0,0 +1,269 @@
(async function() {
while (!Spicetify.React || !Spicetify.ReactDOM) {
await new Promise(resolve => setTimeout(resolve, 10));
}
"use strict";
var library = (() => {
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// external-global-plugin:react
var require_react = __commonJS({
"external-global-plugin:react"(exports, module) {
module.exports = Spicetify.React;
}
});
// src/extensions/config_wrapper.tsx
var config_wrapper_exports = {};
__export(config_wrapper_exports, {
default: () => config_wrapper_default
});
var import_react2 = __toESM(require_react());
// src/components/config/config_modal.tsx
var import_react = __toESM(require_react());
var TextInput = (props) => {
const handleTextChange = (event) => {
props.callback(event.target.value);
};
return /* @__PURE__ */ import_react.default.createElement("label", {
className: "text-input-wrapper"
}, /* @__PURE__ */ import_react.default.createElement("input", {
className: "text-input",
type: "text",
value: props.value || "",
"data-storage-key": props.storageKey,
placeholder: props.placeholder,
id: `text-input:${props.storageKey}`,
title: `Text input for ${props.storageKey}`,
onChange: handleTextChange
}));
};
var Dropdown = (props) => {
const handleDropdownChange = (event) => {
props.callback(event.target.value);
};
return /* @__PURE__ */ import_react.default.createElement("label", {
className: "dropdown-wrapper"
}, /* @__PURE__ */ import_react.default.createElement("select", {
className: "dropdown-input",
value: props.value,
"data-storage-key": props.storageKey,
id: `dropdown:${props.storageKey}`,
title: `Dropdown for ${props.storageKey}`,
onChange: handleDropdownChange
}, props.options.map((option, index) => /* @__PURE__ */ import_react.default.createElement("option", {
key: index,
value: option
}, option))));
};
var ToggleInput = (props) => {
const { Toggle } = Spicetify.ReactComponent;
const handleToggleChange = (newValue) => {
props.callback(newValue);
};
return /* @__PURE__ */ import_react.default.createElement(Toggle, {
id: `toggle:${props.storageKey}`,
value: props.value,
onSelected: (newValue) => handleToggleChange(newValue)
});
};
var SliderInput = (props) => {
const { Slider } = Spicetify.ReactComponent;
const handleSliderChange = (newValue) => {
const calculatedValue = props.min + newValue * (props.max - props.min);
props.callback(calculatedValue);
};
const value = (props.value - props.min) / (props.max - props.min);
return /* @__PURE__ */ import_react.default.createElement(Slider, {
id: `slider:${props.storageKey}`,
value,
min: 0,
max: 1,
step: 0.1,
onDragMove: (newValue) => handleSliderChange(newValue),
onDragStart: () => {
},
onDragEnd: () => {
}
});
};
var TooltipIcon = () => {
return /* @__PURE__ */ import_react.default.createElement("svg", {
role: "img",
height: "16",
width: "16",
className: "Svg-sc-ytk21e-0 uPxdw nW1RKQOkzcJcX6aDCZB4",
viewBox: "0 0 16 16"
}, /* @__PURE__ */ import_react.default.createElement("path", {
d: "M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8z"
}), /* @__PURE__ */ import_react.default.createElement("path", {
d: "M7.25 12.026v-1.5h1.5v1.5h-1.5zm.884-7.096A1.125 1.125 0 007.06 6.39l-1.431.448a2.625 2.625 0 115.13-.784c0 .54-.156 1.015-.503 1.488-.3.408-.7.652-.973.818l-.112.068c-.185.116-.26.203-.302.283-.046.087-.097.245-.097.57h-1.5c0-.47.072-.898.274-1.277.206-.385.507-.645.827-.846l.147-.092c.285-.177.413-.257.526-.41.169-.23.213-.397.213-.602 0-.622-.503-1.125-1.125-1.125z"
}));
};
var ConfigRow = (props) => {
return /* @__PURE__ */ import_react.default.createElement("div", {
className: "setting-row"
}, /* @__PURE__ */ import_react.default.createElement("label", {
className: "col description"
}, props.name, props.desc && /* @__PURE__ */ import_react.default.createElement(Spicetify.ReactComponent.TooltipWrapper, {
label: /* @__PURE__ */ import_react.default.createElement("div", {
dangerouslySetInnerHTML: { __html: props.desc }
}),
renderInline: true,
showDelay: 10,
placement: "top",
labelClassName: "tooltip",
disabled: false
}, /* @__PURE__ */ import_react.default.createElement("div", {
className: "tooltip-icon"
}, /* @__PURE__ */ import_react.default.createElement(TooltipIcon, null)))), /* @__PURE__ */ import_react.default.createElement("div", {
className: "col action"
}, props.children));
};
var ConfigModal = (props) => {
const { config, structure, updateAppConfig } = props;
const [modalConfig, setModalConfig] = import_react.default.useState(__spreadValues({}, config));
const modalRows = structure.map((modalRow, index) => {
const key = modalRow.key;
const currentValue = modalConfig[key];
const updateItem = (state) => {
console.debug(`toggling ${key} to ${state}`);
localStorage.setItem(`library:config:${key}`, String(state));
if (modalRow.callback)
modalRow.callback(state);
const newConfig = __spreadValues({}, modalConfig);
newConfig[key] = state;
updateAppConfig(newConfig);
setModalConfig(newConfig);
};
const header = modalRow.sectionHeader;
const element = () => {
switch (modalRow.type) {
case "toggle":
return /* @__PURE__ */ import_react.default.createElement(ToggleInput, {
storageKey: key,
value: currentValue,
callback: updateItem
});
case "text":
return /* @__PURE__ */ import_react.default.createElement(TextInput, {
storageKey: key,
value: currentValue,
callback: updateItem
});
case "dropdown":
return /* @__PURE__ */ import_react.default.createElement(Dropdown, {
storageKey: key,
value: currentValue,
options: modalRow.options,
callback: updateItem
});
case "slider":
return /* @__PURE__ */ import_react.default.createElement(SliderInput, {
storageKey: key,
value: currentValue,
min: modalRow.min,
max: modalRow.max,
step: modalRow.step,
callback: updateItem
});
}
};
return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, header && index !== 0 && /* @__PURE__ */ import_react.default.createElement("br", null), header && /* @__PURE__ */ import_react.default.createElement("h2", {
className: "section-header"
}, modalRow.sectionHeader), /* @__PURE__ */ import_react.default.createElement(ConfigRow, {
name: modalRow.name,
desc: modalRow.desc
}, element()));
});
return /* @__PURE__ */ import_react.default.createElement("div", {
id: "library-config-container"
}, modalRows);
};
var config_modal_default = ConfigModal;
// src/extensions/config_wrapper.tsx
var _ConfigWrapper = class {
constructor(modalStructure) {
const config = modalStructure.map((modalStructureRow) => {
var _a;
const value = _ConfigWrapper.getLocalStorageDataFromKey(`library:config:${modalStructureRow.key}`, modalStructureRow.def);
(_a = modalStructureRow.callback) == null ? void 0 : _a.call(modalStructureRow, value);
return { [modalStructureRow.key]: value };
});
this.Config = Object.assign({}, ...config);
this.launchModal = (callback) => {
const updateConfig = (config2) => {
this.Config = __spreadValues({}, config2);
callback == null ? void 0 : callback(config2);
};
Spicetify.PopupModal.display({
title: "Library Settings",
content: /* @__PURE__ */ import_react2.default.createElement(config_modal_default, {
config: this.Config,
structure: modalStructure,
updateAppConfig: updateConfig
}),
isLarge: true
});
};
}
};
var ConfigWrapper = _ConfigWrapper;
ConfigWrapper.getLocalStorageDataFromKey = (key, fallback) => {
const data = localStorage.getItem(key);
if (data) {
try {
return JSON.parse(data);
} catch (err) {
return data;
}
} else {
return fallback;
}
};
var config_wrapper_default = ConfigWrapper;
return __toCommonJS(config_wrapper_exports);
})();
})();

View File

@@ -0,0 +1,19 @@
(async function() {
while (!Spicetify.React || !Spicetify.ReactDOM) {
await new Promise(resolve => setTimeout(resolve, 10));
}
"use strict";
var library = (() => {
// src/extensions/context_menu_handler.tsx
var observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.addedNodes.length) {
const node = mutation.addedNodes[0];
console.log(node);
}
});
});
observer.observe(document.body, { childList: true, subtree: false });
})();
})();

View File

@@ -0,0 +1,983 @@
(async function() {
while (!Spicetify.React || !Spicetify.ReactDOM) {
await new Promise(resolve => setTimeout(resolve, 10));
}
"use strict";
var library = (() => {
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __publicField = (obj, key, value) => {
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
return value;
};
// external-global-plugin:react
var require_react = __commonJS({
"external-global-plugin:react"(exports, module) {
module.exports = Spicetify.React;
}
});
// external-global-plugin:react-dom
var require_react_dom = __commonJS({
"external-global-plugin:react-dom"(exports, module) {
module.exports = Spicetify.ReactDOM;
}
});
// ../shared/config/config_wrapper.tsx
var import_react2 = __toESM(require_react());
// ../shared/config/config_modal.tsx
var import_react = __toESM(require_react());
var TextInput = (props) => {
const handleTextChange = (event) => {
props.callback(event.target.value);
};
return /* @__PURE__ */ import_react.default.createElement("label", {
className: "text-input-wrapper"
}, /* @__PURE__ */ import_react.default.createElement("input", {
className: "text-input",
type: "text",
value: props.value || "",
"data-storage-key": props.storageKey,
placeholder: props.placeholder,
id: `text-input:${props.storageKey}`,
title: `Text input for ${props.storageKey}`,
onChange: handleTextChange
}));
};
var Dropdown = (props) => {
const handleDropdownChange = (event) => {
props.callback(event.target.value);
};
return /* @__PURE__ */ import_react.default.createElement("label", {
className: "dropdown-wrapper"
}, /* @__PURE__ */ import_react.default.createElement("select", {
className: "dropdown-input",
value: props.value,
"data-storage-key": props.storageKey,
id: `dropdown:${props.storageKey}`,
title: `Dropdown for ${props.storageKey}`,
onChange: handleDropdownChange
}, props.options.map((option, index) => /* @__PURE__ */ import_react.default.createElement("option", {
key: index,
value: option
}, option))));
};
var ToggleInput = (props) => {
const { Toggle } = Spicetify.ReactComponent;
const handleToggleChange = (newValue) => {
props.callback(newValue);
};
return /* @__PURE__ */ import_react.default.createElement(Toggle, {
id: `toggle:${props.storageKey}`,
value: props.value,
onSelected: (newValue) => handleToggleChange(newValue)
});
};
var SliderInput = (props) => {
const { Slider } = Spicetify.ReactComponent;
const handleSliderChange = (newValue) => {
const calculatedValue = props.min + newValue * (props.max - props.min);
props.callback(calculatedValue);
};
const value = (props.value - props.min) / (props.max - props.min);
return /* @__PURE__ */ import_react.default.createElement(Slider, {
id: `slider:${props.storageKey}`,
value,
min: 0,
max: 1,
step: 0.1,
onDragMove: (newValue) => handleSliderChange(newValue),
onDragStart: () => {
},
onDragEnd: () => {
}
});
};
var TooltipIcon = () => {
return /* @__PURE__ */ import_react.default.createElement("svg", {
role: "img",
height: "16",
width: "16",
className: "Svg-sc-ytk21e-0 uPxdw nW1RKQOkzcJcX6aDCZB4",
viewBox: "0 0 16 16"
}, /* @__PURE__ */ import_react.default.createElement("path", {
d: "M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8z"
}), /* @__PURE__ */ import_react.default.createElement("path", {
d: "M7.25 12.026v-1.5h1.5v1.5h-1.5zm.884-7.096A1.125 1.125 0 007.06 6.39l-1.431.448a2.625 2.625 0 115.13-.784c0 .54-.156 1.015-.503 1.488-.3.408-.7.652-.973.818l-.112.068c-.185.116-.26.203-.302.283-.046.087-.097.245-.097.57h-1.5c0-.47.072-.898.274-1.277.206-.385.507-.645.827-.846l.147-.092c.285-.177.413-.257.526-.41.169-.23.213-.397.213-.602 0-.622-.503-1.125-1.125-1.125z"
}));
};
var ConfigRow = (props) => {
return /* @__PURE__ */ import_react.default.createElement("div", {
className: "setting-row"
}, /* @__PURE__ */ import_react.default.createElement("label", {
className: "col description"
}, props.name, props.desc && /* @__PURE__ */ import_react.default.createElement(Spicetify.ReactComponent.TooltipWrapper, {
label: /* @__PURE__ */ import_react.default.createElement("div", {
dangerouslySetInnerHTML: { __html: props.desc }
}),
renderInline: true,
showDelay: 10,
placement: "top",
labelClassName: "tooltip",
disabled: false
}, /* @__PURE__ */ import_react.default.createElement("div", {
className: "tooltip-icon"
}, /* @__PURE__ */ import_react.default.createElement(TooltipIcon, null)))), /* @__PURE__ */ import_react.default.createElement("div", {
className: "col action"
}, props.children));
};
var ConfigModal = (props) => {
const { config, structure, appKey, updateAppConfig } = props;
const [modalConfig, setModalConfig] = import_react.default.useState({ ...config });
const modalRows = structure.map((modalRow, index) => {
const key = modalRow.key;
const currentValue = modalConfig[key];
const updateItem = (state) => {
console.debug(`toggling ${key} to ${state}`);
localStorage.setItem(`${appKey}:config:${key}`, String(state));
if (modalRow.callback)
modalRow.callback(state);
const newConfig = { ...modalConfig };
newConfig[key] = state;
updateAppConfig(newConfig);
setModalConfig(newConfig);
};
const header = modalRow.sectionHeader;
const element = () => {
switch (modalRow.type) {
case "toggle":
return /* @__PURE__ */ import_react.default.createElement(ToggleInput, {
storageKey: key,
value: currentValue,
callback: updateItem
});
case "text":
return /* @__PURE__ */ import_react.default.createElement(TextInput, {
storageKey: key,
value: currentValue,
callback: updateItem
});
case "dropdown":
return /* @__PURE__ */ import_react.default.createElement(Dropdown, {
storageKey: key,
value: currentValue,
options: modalRow.options,
callback: updateItem
});
case "slider":
return /* @__PURE__ */ import_react.default.createElement(SliderInput, {
storageKey: key,
value: currentValue,
min: modalRow.min,
max: modalRow.max,
step: modalRow.step,
callback: updateItem
});
}
};
return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, header && index !== 0 && /* @__PURE__ */ import_react.default.createElement("br", null), header && /* @__PURE__ */ import_react.default.createElement("h2", {
className: "section-header"
}, modalRow.sectionHeader), /* @__PURE__ */ import_react.default.createElement(ConfigRow, {
name: modalRow.name,
desc: modalRow.desc
}, element()));
});
return /* @__PURE__ */ import_react.default.createElement("div", {
className: "config-container"
}, modalRows);
};
var config_modal_default = ConfigModal;
// ../shared/config/config_wrapper.tsx
var _ConfigWrapper = class {
Config;
launchModal;
constructor(modalStructure, key) {
const config = modalStructure.map((modalStructureRow) => {
const value = _ConfigWrapper.getLocalStorageDataFromKey(
`${key}:config:${modalStructureRow.key}`,
modalStructureRow.def
);
modalStructureRow.callback?.(value);
return { [modalStructureRow.key]: value };
});
this.Config = Object.assign({}, ...config);
this.launchModal = (callback) => {
const updateConfig = (config2) => {
this.Config = { ...config2 };
callback?.(config2);
};
Spicetify.PopupModal.display({
title: `${key.charAt(0).toUpperCase() + key.slice(1)} Settings`,
content: /* @__PURE__ */ import_react2.default.createElement(config_modal_default, {
config: this.Config,
structure: modalStructure,
appKey: key,
updateAppConfig: updateConfig
}),
isLarge: true
});
};
}
};
var ConfigWrapper = _ConfigWrapper;
__publicField(ConfigWrapper, "getLocalStorageDataFromKey", (key, fallback) => {
const data = localStorage.getItem(key);
if (data) {
try {
return JSON.parse(data);
} catch (err) {
return data;
}
} else {
return fallback;
}
});
var config_wrapper_default = ConfigWrapper;
// src/extensions/extension.tsx
var import_react10 = __toESM(require_react());
var import_react_dom = __toESM(require_react_dom());
// src/components/toggle_filters.tsx
var import_react3 = __toESM(require_react());
var UpIcon = () => {
const { IconComponent } = Spicetify.ReactComponent;
return /* @__PURE__ */ import_react3.default.createElement(IconComponent, {
semanticColor: "textSubdued",
dangerouslySetInnerHTML: {
__html: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M.998 8.81A.749.749 0 0 1 .47 7.53L7.99 0l7.522 7.53a.75.75 0 1 1-1.06 1.06L8.74 2.87v12.38a.75.75 0 1 1-1.498 0V2.87L1.528 8.59a.751.751 0 0 1-.53.22z"></path></svg>'
},
iconSize: 16
});
};
var DownIcon = () => {
const { IconComponent } = Spicetify.ReactComponent;
return /* @__PURE__ */ import_react3.default.createElement(IconComponent, {
semanticColor: "textSubdued",
dangerouslySetInnerHTML: {
__html: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M.998 7.19A.749.749 0 0 0 .47 8.47L7.99 16l7.522-7.53a.75.75 0 1 0-1.06-1.06L8.74 13.13V.75a.75.75 0 1 0-1.498 0v12.38L1.528 7.41a.749.749 0 0 0-.53-.22z"></path></svg>'
},
iconSize: 16
});
};
var ToggleFiltersButton = () => {
const [direction, setDirection] = import_react3.default.useState(
document.body.classList.contains("show-ylx-filters") ? "up" : "down"
);
const { ButtonTertiary } = Spicetify.ReactComponent;
const toggleDirection = () => {
if (direction === "down") {
document.body.classList.add("show-ylx-filters");
setDirection("up");
} else {
setDirection("down");
document.body.classList.remove("show-ylx-filters");
}
};
const Icon = direction === "down" ? DownIcon : UpIcon;
return /* @__PURE__ */ import_react3.default.createElement(ButtonTertiary, {
buttonSize: "sm",
"aria-label": "Show Filters",
iconOnly: Icon,
onClick: toggleDirection
});
};
var toggle_filters_default = ToggleFiltersButton;
// src/components/collapse_button.tsx
var import_react4 = __toESM(require_react());
var collapseLibrary = () => {
Spicetify.Platform.LocalStorageAPI.setItem("ylx-sidebar-state", 1);
};
var CollapseIcon = () => {
const { IconComponent } = Spicetify.ReactComponent;
return /* @__PURE__ */ import_react4.default.createElement(IconComponent, {
semanticColor: "textSubdued",
dangerouslySetInnerHTML: {
__html: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M8.81 1A.749.749 0 0 0 7.53.47L0 7.99l7.53 7.521a.75.75 0 0 0 1.234-.815.75.75 0 0 0-.174-.243L2.87 8.74h12.38a.75.75 0 1 0 0-1.498H2.87l5.72-5.713c.14-.14.22-.331.22-.53z"></path></svg>'
},
iconSize: 16
});
};
var CollapseButton = () => {
const { ButtonTertiary } = Spicetify.ReactComponent;
return /* @__PURE__ */ import_react4.default.createElement(ButtonTertiary, {
buttonSize: "sm",
"aria-label": "Show Filters",
iconOnly: CollapseIcon,
onClick: collapseLibrary
});
};
var collapse_button_default = CollapseButton;
// src/components/album_menu_item.tsx
var import_react8 = __toESM(require_react());
// src/components/leading_icon.tsx
var import_react5 = __toESM(require_react());
var LeadingIcon = ({ path }) => {
return /* @__PURE__ */ import_react5.default.createElement(Spicetify.ReactComponent.IconComponent, {
semanticColor: "textSubdued",
dangerouslySetInnerHTML: {
__html: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">${path}</svg>`
},
iconSize: 16
});
};
var leading_icon_default = LeadingIcon;
// src/components/text_input_dialog.tsx
var import_react6 = __toESM(require_react());
var TextInputDialog = (props) => {
const { def, placeholder, onSave } = props;
const [value, setValue] = import_react6.default.useState(def || "");
const onSubmit = (e) => {
e.preventDefault();
Spicetify.PopupModal.hide();
onSave(value);
};
return /* @__PURE__ */ import_react6.default.createElement(import_react6.default.Fragment, null, /* @__PURE__ */ import_react6.default.createElement("form", {
className: "text-input-form",
onSubmit
}, /* @__PURE__ */ import_react6.default.createElement("label", {
className: "text-input-wrapper"
}, /* @__PURE__ */ import_react6.default.createElement("input", {
className: "text-input",
type: "text",
value,
placeholder,
onChange: (e) => setValue(e.target.value)
})), /* @__PURE__ */ import_react6.default.createElement("button", {
type: "submit",
"data-encore-id": "buttonPrimary",
className: "Button-sc-qlcn5g-0 Button-small-buttonPrimary"
}, /* @__PURE__ */ import_react6.default.createElement("span", {
className: "ButtonInner-sc-14ud5tc-0 ButtonInner-small encore-bright-accent-set"
}, "Save"))));
};
var text_input_dialog_default = TextInputDialog;
// src/components/searchbar.tsx
var import_react7 = __toESM(require_react());
var SearchBar = (props) => {
const { setSearch, placeholder } = props;
const handleChange = (e) => {
setSearch(e.target.value);
};
return /* @__PURE__ */ import_react7.default.createElement("div", {
className: "x-filterBox-filterInputContainer x-filterBox-expandedOrHasFilter",
role: "search"
}, /* @__PURE__ */ import_react7.default.createElement("input", {
type: "text",
className: "x-filterBox-filterInput",
role: "searchbox",
maxLength: 80,
autoCorrect: "off",
autoCapitalize: "off",
spellCheck: "false",
placeholder: `Search ${placeholder}`,
"aria-hidden": "false",
onChange: handleChange
}), /* @__PURE__ */ import_react7.default.createElement("div", {
className: "x-filterBox-overlay"
}, /* @__PURE__ */ import_react7.default.createElement("span", {
className: "x-filterBox-searchIconContainer"
}, /* @__PURE__ */ import_react7.default.createElement("svg", {
"data-encore-id": "icon",
role: "img",
"aria-hidden": "true",
className: "Svg-sc-ytk21e-0 Svg-img-icon-small x-filterBox-searchIcon",
viewBox: "0 0 16 16"
}, /* @__PURE__ */ import_react7.default.createElement("path", {
d: "M7 1.75a5.25 5.25 0 1 0 0 10.5 5.25 5.25 0 0 0 0-10.5zM.25 7a6.75 6.75 0 1 1 12.096 4.12l3.184 3.185a.75.75 0 1 1-1.06 1.06L11.304 12.2A6.75 6.75 0 0 1 .25 7z"
})))), /* @__PURE__ */ import_react7.default.createElement("button", {
className: "x-filterBox-expandButton",
"aria-hidden": "false",
"aria-label": "Search Playlists",
type: "button"
}, /* @__PURE__ */ import_react7.default.createElement("svg", {
"data-encore-id": "icon",
role: "img",
"aria-hidden": "true",
className: "Svg-sc-ytk21e-0 Svg-img-icon-small x-filterBox-searchIcon",
viewBox: "0 0 16 16"
}, /* @__PURE__ */ import_react7.default.createElement("path", {
d: "M7 1.75a5.25 5.25 0 1 0 0 10.5 5.25 5.25 0 0 0 0-10.5zM.25 7a6.75 6.75 0 1 1 12.096 4.12l3.184 3.185a.75.75 0 1 1-1.06 1.06L11.304 12.2A6.75 6.75 0 0 1 .25 7z"
}))));
};
var searchbar_default = SearchBar;
// src/components/album_menu_item.tsx
var createCollection = () => {
const onSave = (value) => {
CollectionsWrapper.createCollection(value);
};
Spicetify.PopupModal.display({
title: "Create Collection",
content: /* @__PURE__ */ import_react8.default.createElement(text_input_dialog_default, {
def: "New Collection",
placeholder: "Collection Name",
onSave
})
});
};
var CollectionSearchMenu = () => {
const { MenuItem } = Spicetify.ReactComponent;
const { SVGIcons } = Spicetify;
const [textFilter, setTextFilter] = import_react8.default.useState("");
const [collections, setCollections] = import_react8.default.useState(null);
const context = import_react8.default.useContext(Spicetify.ContextMenuV2._context);
const uri = context?.props?.uri;
import_react8.default.useEffect(() => {
const fetchCollections = async () => {
setCollections(await CollectionsWrapper.getContents({ textFilter, limit: 20, offset: 0 }));
};
fetchCollections();
}, [textFilter]);
if (!collections)
return /* @__PURE__ */ import_react8.default.createElement(import_react8.default.Fragment, null);
const addToCollection = (collectionUri) => {
CollectionsWrapper.addAlbumToCollection(collectionUri, uri);
};
const activeCollections = CollectionsWrapper.getCollectionsWithAlbum(uri);
const hasCollections = activeCollections.length > 0;
const removeFromCollections = () => {
for (const collection of activeCollections) {
CollectionsWrapper.removeAlbumFromCollection(collection.uri, uri);
}
};
const allCollectionsLength = collections.totalLength;
const menuItems = collections.items.map((collection, index) => {
return /* @__PURE__ */ import_react8.default.createElement(MenuItem, {
key: collection.uri,
onClick: () => {
addToCollection(collection.uri);
},
divider: index === 0 ? "before" : void 0
}, collection.name);
});
const menuLength = allCollectionsLength + (hasCollections ? 1 : 0);
return /* @__PURE__ */ import_react8.default.createElement("div", {
className: "main-contextMenu-filterPlaylistSearchContainer",
style: { "--context-menu-submenu-length": `${menuLength}` }
}, /* @__PURE__ */ import_react8.default.createElement("li", {
role: "presentation",
className: "main-contextMenu-filterPlaylistSearch"
}, /* @__PURE__ */ import_react8.default.createElement("div", {
role: "menuitem"
}, /* @__PURE__ */ import_react8.default.createElement(searchbar_default, {
setSearch: setTextFilter,
placeholder: "collections"
}))), /* @__PURE__ */ import_react8.default.createElement(MenuItem, {
key: "new-collection",
leadingIcon: /* @__PURE__ */ import_react8.default.createElement(leading_icon_default, {
path: SVGIcons.plus2px
}),
onClick: createCollection
}, "Create collection"), hasCollections && /* @__PURE__ */ import_react8.default.createElement(MenuItem, {
key: "remove-collection",
leadingIcon: /* @__PURE__ */ import_react8.default.createElement(leading_icon_default, {
path: SVGIcons.minus
}),
onClick: removeFromCollections
}, "Remove from all"), menuItems);
};
var AlbumMenuItem = () => {
const { MenuSubMenuItem } = Spicetify.ReactComponent;
const { SVGIcons } = Spicetify;
return /* @__PURE__ */ import_react8.default.createElement(MenuSubMenuItem, {
displayText: "Add to collection",
divider: "after",
leadingIcon: /* @__PURE__ */ import_react8.default.createElement(leading_icon_default, {
path: SVGIcons.plus2px
})
}, /* @__PURE__ */ import_react8.default.createElement(CollectionSearchMenu, null));
};
var album_menu_item_default = AlbumMenuItem;
// src/components/artist_menu_item.tsx
var import_react9 = __toESM(require_react());
var ArtistMenuItem = () => {
const { MenuItem } = Spicetify.ReactComponent;
const { SVGIcons } = Spicetify;
const context = import_react9.default.useContext(Spicetify.ContextMenuV2._context);
const uri = context?.props?.uri;
return /* @__PURE__ */ import_react9.default.createElement(MenuItem, {
divider: "after",
leadingIcon: /* @__PURE__ */ import_react9.default.createElement(leading_icon_default, {
path: SVGIcons.plus2px
}),
onClick: () => CollectionsWrapper.createCollectionFromDiscog(uri)
}, "Create Discog Collection");
};
var artist_menu_item_default = ArtistMenuItem;
// ../node_modules/uuid/dist/esm-browser/rng.js
var getRandomValues;
var rnds8 = new Uint8Array(16);
function rng() {
if (!getRandomValues) {
getRandomValues = typeof crypto !== "undefined" && crypto.getRandomValues && crypto.getRandomValues.bind(crypto);
if (!getRandomValues) {
throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");
}
}
return getRandomValues(rnds8);
}
// ../node_modules/uuid/dist/esm-browser/stringify.js
var byteToHex = [];
for (let i = 0; i < 256; ++i) {
byteToHex.push((i + 256).toString(16).slice(1));
}
function unsafeStringify(arr, offset = 0) {
return byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]];
}
// ../node_modules/uuid/dist/esm-browser/native.js
var randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto);
var native_default = {
randomUUID
};
// ../node_modules/uuid/dist/esm-browser/v4.js
function v4(options, buf, offset) {
if (native_default.randomUUID && !buf && !options) {
return native_default.randomUUID();
}
options = options || {};
const rnds = options.random || (options.rng || rng)();
rnds[6] = rnds[6] & 15 | 64;
rnds[8] = rnds[8] & 63 | 128;
if (buf) {
offset = offset || 0;
for (let i = 0; i < 16; ++i) {
buf[offset + i] = rnds[i];
}
return buf;
}
return unsafeStringify(rnds);
}
var v4_default = v4;
// src/extensions/collections_wrapper.ts
var _CollectionsWrapper = class extends EventTarget {
_collections;
constructor() {
super();
this._collections = JSON.parse(localStorage.getItem("library:collections") || "[]");
}
saveCollections() {
localStorage.setItem("library:collections", JSON.stringify(this._collections));
this.dispatchEvent(new CustomEvent("update", { detail: this._collections }));
}
getCollection(uri) {
return this._collections.find((collection) => collection.uri === uri);
}
async getCollectionContents(uri) {
const collection = this.getCollection(uri);
if (!collection)
throw new Error("Collection not found");
const items = this._collections.filter((collection2) => collection2.parentCollection === uri);
const albums = await Spicetify.Platform.LibraryAPI.getContents({
filters: ["0"],
offset: 0,
limit: 9999
});
items.push(...albums.items.filter((album) => collection.items.includes(album.uri)));
return items;
}
async getContents(props) {
const { collectionUri, offset, limit, textFilter } = props;
let items = collectionUri ? await this.getCollectionContents(collectionUri) : this._collections;
const openedCollectionName = collectionUri ? this.getCollection(collectionUri)?.name : void 0;
if (textFilter) {
const regex = new RegExp(`\\b${textFilter}`, "i");
items = items.filter((collection) => regex.test(collection.name));
}
items = items.slice(offset, offset + limit);
return { items, totalLength: this._collections.length, offset, openedCollectionName };
}
async cleanCollections() {
for (const collection of this._collections) {
const boolArray = await Spicetify.Platform.LibraryAPI.contains(...collection.items);
if (boolArray.includes(false)) {
collection.items = collection.items.filter((_, i) => boolArray[i]);
this.saveCollections();
Spicetify.showNotification("Album removed from collection");
this.syncCollection(collection.uri);
}
}
}
async syncCollection(uri) {
const collection = this.getCollection(uri);
if (!collection)
return;
const { PlaylistAPI } = Spicetify.Platform;
if (!collection.syncedPlaylistUri)
return;
const playlist = await PlaylistAPI.getPlaylist(collection.syncedPlaylistUri);
const playlistTracks = playlist.contents.items.filter((t) => t.type === "track").map((t) => t.uri);
const collectionTracks = await this.getTracklist(uri);
const wanted = collectionTracks.filter((track) => !playlistTracks.includes(track));
const unwanted = playlistTracks.filter((track) => !collectionTracks.includes(track)).map((uri2) => ({ uri: uri2, uid: [] }));
if (wanted.length)
await PlaylistAPI.add(collection.syncedPlaylistUri, wanted, { before: "end" });
if (unwanted.length)
await PlaylistAPI.remove(collection.syncedPlaylistUri, unwanted);
}
unsyncCollection(uri) {
const collection = this.getCollection(uri);
if (!collection)
return;
collection.syncedPlaylistUri = void 0;
this.saveCollections();
}
async getTracklist(collectionUri) {
const collection = this.getCollection(collectionUri);
if (!collection)
return [];
return Promise.all(
collection.items.map(async (uri) => {
const album = await Spicetify.Platform.LibraryAPI.getAlbum(uri);
return album.items.map((t) => t.uri);
})
).then((tracks) => tracks.flat());
}
async convertToPlaylist(uri) {
const collection = this.getCollection(uri);
if (!collection)
return;
const { Platform, showNotification } = Spicetify;
const { RootlistAPI, PlaylistAPI } = Platform;
if (collection.syncedPlaylistUri) {
showNotification("Synced Playlist already exists", true);
return;
}
try {
const playlistUri = await RootlistAPI.createPlaylist(collection.name, { before: "start" });
const items = await this.getTracklist(uri);
await PlaylistAPI.add(playlistUri, items, { before: "start" });
collection.syncedPlaylistUri = playlistUri;
} catch (error) {
console.error(error);
showNotification("Failed to create playlist", true);
}
}
async createCollectionFromDiscog(artistUri) {
const [raw, info] = await Promise.all([
Spicetify.GraphQL.Request(Spicetify.GraphQL.Definitions.queryArtistDiscographyAlbums, {
uri: artistUri,
offset: 0,
limit: 50
}),
Spicetify.GraphQL.Request(Spicetify.GraphQL.Definitions.queryArtistOverview, {
uri: artistUri,
locale: Spicetify.Locale.getLocale(),
includePrerelease: false
})
]);
const items = raw?.data?.artistUnion.discography.albums?.items;
const name = info?.data?.artistUnion.profile.name;
const image = info?.data?.artistUnion.visuals.avatarImage?.sources?.[0]?.url;
if (!name || !items?.length) {
Spicetify.showNotification("Artist not found or has no albums");
return;
}
const collectionUri = this.createCollection(`${name} Albums`);
if (image)
this.setCollectionImage(collectionUri, image);
for (const album of items) {
this.addAlbumToCollection(collectionUri, album.releases.items[0].uri);
}
}
createCollection(name, parentCollection = "") {
const id = v4_default();
this._collections.push({
type: "collection",
uri: id,
name,
items: [],
addedAt: new Date(),
lastPlayedAt: new Date(),
parentCollection
});
this.saveCollections();
Spicetify.showNotification("Collection created");
return id;
}
deleteCollection(uri) {
this._collections = this._collections.filter((collection) => collection.uri !== uri);
this.saveCollections();
Spicetify.showNotification("Collection deleted");
}
deleteCollectionAndAlbums(uri) {
const collection = this.getCollection(uri);
if (!collection)
return;
for (const album of collection.items) {
Spicetify.Platform.LibraryAPI.remove({ uris: [album] });
}
this.deleteCollection(uri);
}
async addAlbumToCollection(collectionUri, albumUri) {
const collection = this.getCollection(collectionUri);
if (!collection)
return;
await Spicetify.Platform.LibraryAPI.add({ uris: [albumUri] });
collection.items.push(albumUri);
this.saveCollections();
Spicetify.showNotification("Album added to collection");
this.syncCollection(collectionUri);
}
removeAlbumFromCollection(collectionUri, albumUri) {
const collection = this.getCollection(collectionUri);
if (!collection)
return;
collection.items = collection.items.filter((item) => item !== albumUri);
this.saveCollections();
Spicetify.showNotification("Album removed from collection");
this.syncCollection(collectionUri);
}
getCollectionsWithAlbum(albumUri) {
return this._collections.filter((collection) => {
return collection.items.some((item) => item === albumUri);
});
}
renameCollection(uri, name) {
const collection = this.getCollection(uri);
if (!collection)
return;
collection.name = name;
this.saveCollections();
Spicetify.showNotification("Collection renamed");
}
setCollectionImage(uri, url) {
const collection = this.getCollection(uri);
if (!collection)
return;
collection.image = url;
this.saveCollections();
Spicetify.showNotification("Collection image set");
}
removeCollectionImage(uri) {
const collection = this.getCollection(uri);
if (!collection)
return;
collection.image = void 0;
this.saveCollections();
Spicetify.showNotification("Collection image removed");
}
};
var CollectionsWrapper2 = _CollectionsWrapper;
__publicField(CollectionsWrapper2, "INSTANCE", new _CollectionsWrapper());
window.CollectionsWrapper = CollectionsWrapper2.INSTANCE;
// src/extensions/folder_image_wrapper.ts
var _FolderImageWrapper = class extends EventTarget {
_folderImages;
constructor() {
super();
this._folderImages = JSON.parse(localStorage.getItem("library:folderImages") || "{}");
}
getFolderImage(uri) {
return this._folderImages[uri];
}
getFolderImages() {
return this._folderImages;
}
setFolderImage({ uri, url }) {
this._folderImages[uri] = url;
this.saveFolderImages();
Spicetify.showNotification("Folder image updated");
}
removeFolderImage(uri) {
delete this._folderImages[uri];
this.saveFolderImages();
Spicetify.showNotification("Folder image removed");
}
saveFolderImages() {
this.dispatchEvent(new CustomEvent("update", { detail: this._folderImages }));
localStorage.setItem("library:folderImages", JSON.stringify(this._folderImages));
}
};
var FolderImageWrapper2 = _FolderImageWrapper;
__publicField(FolderImageWrapper2, "INSTANCE", new _FolderImageWrapper());
window.FolderImageWrapper = FolderImageWrapper2.INSTANCE;
// src/extensions/extension.tsx
var styleLink = document.createElement("link");
styleLink.rel = "stylesheet";
styleLink.href = "/spicetify-routes-library.css";
document.head.appendChild(styleLink);
var setCardSize = (size) => {
document.documentElement.style.setProperty("--library-card-size", `${size}px`);
};
var setSearchBarSize = (enlarged) => {
const size = enlarged ? 300 : 200;
document.documentElement.style.setProperty("--library-searchbar-size", `${size}px`);
};
var FolderImage = ({ url }) => {
return /* @__PURE__ */ import_react10.default.createElement("img", {
alt: "Folder Image",
"aria-hidden": "true",
draggable: "false",
loading: "eager",
src: url,
className: "main-image-image x-entityImage-image main-image-loading main-image-loaded"
});
};
var FolderPlaceholder = () => {
return /* @__PURE__ */ import_react10.default.createElement("div", {
className: "x-entityImage-imagePlaceholder"
}, /* @__PURE__ */ import_react10.default.createElement("svg", {
"data-encore-id": "icon",
role: "img",
"aria-hidden": "true",
className: "Svg-sc-ytk21e-0 Svg-img-icon-medium",
viewBox: "0 0 24 24"
}, /* @__PURE__ */ import_react10.default.createElement("path", {
d: "M1 4a2 2 0 0 1 2-2h5.155a3 3 0 0 1 2.598 1.5l.866 1.5H21a2 2 0 0 1 2 2v13a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V4zm7.155 0H3v16h18V7H10.464L9.021 4.5a1 1 0 0 0-.866-.5z"
})));
};
var SpicetifyLibrary = class {
ConfigWrapper = new config_wrapper_default(
[
{
name: "Card Size",
key: "cardSize",
type: "slider",
min: 100,
max: 200,
step: 0.05,
def: 180,
callback: setCardSize
},
{
name: "Extend Search Bar",
key: "extendSearchBar",
type: "toggle",
def: false,
callback: setSearchBarSize
},
{
name: "Playlists Page",
key: "show-playlists",
type: "toggle",
def: true,
sectionHeader: "Pages"
},
{ name: "Albums Page", key: "show-albums", type: "toggle", def: true },
{ name: "Collections Page", key: "show-collections", type: "toggle", def: true },
{ name: "Artists Page", key: "show-artists", type: "toggle", def: true },
{ name: "Shows Page", key: "show-shows", type: "toggle", def: true }
],
"library"
);
};
window.SpicetifyLibrary = new SpicetifyLibrary();
(function wait() {
const { LocalStorageAPI } = Spicetify.Platform;
if (!LocalStorageAPI) {
setTimeout(wait, 100);
return;
}
main(LocalStorageAPI);
})();
function main(LocalStorageAPI) {
const isAlbum = (props) => props.uri?.includes("album");
const isArtist = (props) => props.uri?.includes("artist");
Spicetify.ContextMenuV2.registerItem(/* @__PURE__ */ import_react10.default.createElement(album_menu_item_default, null), isAlbum);
Spicetify.ContextMenuV2.registerItem(/* @__PURE__ */ import_react10.default.createElement(artist_menu_item_default, null), isArtist);
Spicetify.Platform.LibraryAPI.getEvents()._emitter.addListener("update", () => CollectionsWrapper.cleanCollections());
function injectFolderImages() {
const rootlist = document.querySelector(".main-rootlist-wrapper > div:nth-child(2)");
if (!rootlist)
return setTimeout(injectFolderImages, 100);
setTimeout(() => {
for (const el of Array.from(rootlist.children)) {
const uri = el.querySelector("[aria-labelledby]")?.getAttribute("aria-labelledby")?.slice(14);
if (uri?.includes("folder")) {
const imageBox = el.querySelector(".x-entityImage-imageContainer");
if (!imageBox)
return;
const imageUrl = FolderImageWrapper.getFolderImage(uri);
if (!imageUrl)
import_react_dom.default.render(/* @__PURE__ */ import_react10.default.createElement(FolderPlaceholder, null), imageBox);
else
import_react_dom.default.render(/* @__PURE__ */ import_react10.default.createElement(FolderImage, {
url: imageUrl
}), imageBox);
}
}
}, 500);
}
injectFolderImages();
FolderImageWrapper.addEventListener("update", injectFolderImages);
function injectYLXButtons() {
const ylx_filter = document.querySelector(".main-yourLibraryX-libraryRootlist > .main-yourLibraryX-libraryFilter");
if (!ylx_filter) {
return setTimeout(injectYLXButtons, 100);
}
injectFiltersButton(ylx_filter);
injectCollapseButton(ylx_filter);
}
function injectFiltersButton(ylx_filter) {
const toggleFiltersButton = document.createElement("span");
toggleFiltersButton.classList.add("toggle-filters-button");
ylx_filter.appendChild(toggleFiltersButton);
import_react_dom.default.render(/* @__PURE__ */ import_react10.default.createElement(toggle_filters_default, null), toggleFiltersButton);
}
function injectCollapseButton(ylx_filter) {
const collapseButton = document.createElement("span");
collapseButton.classList.add("collapse-button");
ylx_filter.appendChild(collapseButton);
import_react_dom.default.render(
/* @__PURE__ */ import_react10.default.createElement(Spicetify.ReactComponent.TooltipWrapper, {
label: "Collapse Sidebar",
placement: "top"
}, /* @__PURE__ */ import_react10.default.createElement(collapse_button_default, null)),
collapseButton
);
}
const state = LocalStorageAPI.getItem("ylx-sidebar-state");
if (state === 0)
injectYLXButtons();
LocalStorageAPI.getEvents()._emitter.addListener("update", (e) => {
const { key, value } = e.data;
if (key === "ylx-sidebar-state" && value === 0) {
injectFolderImages();
injectYLXButtons();
}
if (key === "ylx-sidebar-state" && value === 1) {
injectFolderImages();
}
});
}
})();
})();

View File

@@ -0,0 +1,69 @@
(async function() {
while (!Spicetify.React || !Spicetify.ReactDOM) {
await new Promise(resolve => setTimeout(resolve, 10));
}
"use strict";
var library = (() => {
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __publicField = (obj, key, value) => {
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
return value;
};
// src/extensions/folder_image_wrapper.ts
var folder_image_wrapper_exports = {};
__export(folder_image_wrapper_exports, {
default: () => folder_image_wrapper_default
});
var _FolderImageWrapper = class extends EventTarget {
_folderImages;
constructor() {
super();
this._folderImages = JSON.parse(localStorage.getItem("library:folderImages") || "{}");
}
getFolderImage(uri) {
return this._folderImages[uri];
}
getFolderImages() {
return this._folderImages;
}
setFolderImage({ uri, url }) {
this._folderImages[uri] = url;
this.saveFolderImages();
Spicetify.showNotification("Folder image updated");
}
removeFolderImage(uri) {
delete this._folderImages[uri];
this.saveFolderImages();
Spicetify.showNotification("Folder image removed");
}
saveFolderImages() {
this.dispatchEvent(new CustomEvent("update", { detail: this._folderImages }));
localStorage.setItem("library:folderImages", JSON.stringify(this._folderImages));
}
};
var FolderImageWrapper = _FolderImageWrapper;
__publicField(FolderImageWrapper, "INSTANCE", new _FolderImageWrapper());
window.FolderImageWrapper = FolderImageWrapper.INSTANCE;
var folder_image_wrapper_default = FolderImageWrapper;
return __toCommonJS(folder_image_wrapper_exports);
})();
})();

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
{
"name": "Your Library",
"icon": "<svg height=\"24\" width=\"24\" viewBox=\"0 0 24 24\">\r\n<path d=\"M14.5 2.134a1 1 0 0 1 1 0l6 3.464a1 1 0 0 1 .5.866V21a1 1 0 0 1-1 1h-6a1 1 0 0 1-1-1V3a1 1 0 0 1 .5-.866zM16 4.732V20h4V7.041l-4-2.309zM3 22a1 1 0 0 1-1-1V3a1 1 0 0 1 2 0v18a1 1 0 0 1-1 1zm6 0a1 1 0 0 1-1-1V3a1 1 0 0 1 2 0v18a1 1 0 0 1-1 1z\"></path>\r\n</svg>\r\n",
"active-icon": "<svg height=\"24\" width=\"24\" viewBox=\"0 0 24 24\">\r\n<path d=\"M3 22a1 1 0 0 1-1-1V3a1 1 0 0 1 2 0v18a1 1 0 0 1-1 1zM15.5 2.134A1 1 0 0 0 14 3v18a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V6.464a1 1 0 0 0-.5-.866l-6-3.464zM9 2a1 1 0 0 0-1 1v18a1 1 0 1 0 2 0V3a1 1 0 0 0-1-1z\"></path>\r\n</svg>",
"subfiles": [],
"subfiles_extension": [
"collections_wrapper.js",
"extension.js",
"folder_image_wrapper.js"
]
}

View File

@@ -0,0 +1,379 @@
/* ../../../AppData/Local/Temp/tmp-4464-wbZ6O1BKhuot/19178f16f304/navBar.module.css */
.navBar-module__topBarHeaderItem___piw4C_library {
-webkit-app-region: no-drag;
display: inline-block;
pointer-events: auto;
}
.navBar-module__topBarHeaderItemLink___xA4uv_library {
margin: 0 8px 0 0;
}
.navBar-module__topBarActive___XhWpm_library {
background-color: var(--spice-tab-active);
border-radius: 4px;
}
.navBar-module__topBarHeaderItemLink___xA4uv_library {
border-radius: 4px;
color: var(--spice-text);
display: inline-block;
margin: 0 8px;
padding: 8px 16px;
position: relative;
text-decoration: none !important;
cursor: pointer;
}
.navBar-module__topBarNav___qWGeZ_library {
-webkit-app-region: drag;
pointer-events: none;
width: 100%;
}
.navBar-module__topBarHeaderItem___piw4C_library .navBar-module__optionsMenuDropBox___pzfNI_library {
color: var(--spice-text);
border: 0;
max-width: 150px;
height: 42px;
padding: 0 30px 0 12px;
background-color: initial;
cursor: pointer;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
}
.navBar-module__topBarHeaderItem___piw4C_library .navBar-module__optionsMenuDropBox___pzfNI_library svg {
position: absolute;
margin-left: 8px;
}
div.navBar-module__topBarHeaderItemLink___xA4uv_library {
padding: 0;
}
/* ../../../AppData/Local/Temp/tmp-4464-wbZ6O1BKhuot/19178f16dc80/app.css */
:root {
--library-card-size: 180px;
--library-searchbar-size: 200px;
}
#library-app .header-right .x-filterBox-expandedOrHasFilter .x-filterBox-filterInput {
width: var(--library-searchbar-size);
}
#library-app .grid {
grid-template-columns: repeat(auto-fill, minmax(var(--library-card-size), 1fr)) !important;
}
#library-app .main-card-cardContainer {
width: 100%;
height: 100%;
}
#library-app .load-more-card {
display: flex;
gap: 10px;
flex-direction: column;
justify-content: center;
}
#library-app .load-more-card div:nth-child(2) {
text-align: center;
font-size: var(--encore-text-size-base);
}
#library-app .load-more-card div:first-child {
fill: var(--text-subdued);
width: 80%;
margin: 0 auto;
}
#library-app .load-more-card:hover {
cursor: pointer;
}
.text-input-form {
display: flex;
flex-direction: column;
gap: 18px;
}
.text-input-form .text-input {
background: rgba(var(--spice-rgb-selected-row), 0.1);
border: 1px solid transparent;
border-radius: 4px;
color: var(--spice-text);
font-family: inherit;
font-size: 14px;
height: 32px;
padding: 0 12px;
width: 100%;
}
.text-input-form .text-input:focus {
background-color: var(--spice-tab-active);
border: 1px solid var(--spice-button-disabled);
outline: none;
}
.text-input-form button {
align-self: end;
}
/* ../../../AppData/Local/Temp/tmp-4464-wbZ6O1BKhuot/19178f16ee21/external.css */
body:not(.show-ylx-filters) .main-yourLibraryX-filterArea:not(:has(> .main-yourLibraryX-libraryFilter)),
.main-yourLibraryX-header:not(:has(> .main-yourLibraryX-headerContent > .main-yourLibraryX-collapseButton > button:nth-child(2))),
.main-yourLibraryX-collapseButton > button:first-child,
.main-yourLibraryX-headerContent > button {
display: none;
}
.main-yourLibraryX-library {
padding-top: 8px;
}
.main-yourLibraryX-header {
margin-top: -8px;
}
.main-yourLibraryX-libraryFilter .main-yourLibraryX-librarySortWrapper button span:first-child,
.main-yourLibraryX-libraryFilter span[role=presentation] span[role=presentation] button span:first-child {
display: none;
}
.main-yourLibraryX-libraryFilter .main-yourLibraryX-librarySortWrapper,
.main-yourLibraryX-libraryFilter span[role=presentation] {
margin-left: auto;
}
.toggle-filters-button > button:after,
.collapse-button > button:after,
.expand-button > button:after {
display: none;
}
.expand-button {
display: flex;
align-items: center;
z-index: 1;
margin-left: 5px;
}
.expand-button > button {
visibility: hidden;
margin: 0 5px;
}
li.main-yourLibraryX-navItem[data-id="/library"] {
display: flex;
}
li.main-yourLibraryX-navItem[data-id="/library"] > a {
flex-grow: 1;
}
.main-yourLibraryX-libraryFilter .toggle-filters-button > button,
.main-yourLibraryX-libraryFilter .collapse-button > button,
.main-yourLibraryX-libraryFilter .main-yourLibraryX-librarySortWrapper > button,
.main-yourLibraryX-libraryFilter span[role=presentation] span[role=presentation] > button {
padding: 0;
}
.main-yourLibraryX-libraryFilter .toggle-filters-button,
.main-yourLibraryX-libraryFilter .collapse-button,
.main-yourLibraryX-libraryFilter .main-yourLibraryX-librarySortWrapper,
.main-yourLibraryX-libraryFilter span[role=presentation] span[role=presentation] {
display: flex;
flex-basis: 32px;
justify-content: center;
min-width: 24px;
flex-shrink: 10;
}
.main-yourLibraryX-libraryFilter .main-yourLibraryX-librarySortWrapper > button > span:nth-child(2),
.main-yourLibraryX-libraryFilter span[role=presentation] span[role=presentation] > button > span:nth-child(2) {
margin: 0;
}
.LayoutResizer__resize-bar {
opacity: 0 !important;
}
.Root__nav-bar .x-filterBox-expandedOrHasFilter .x-filterBox-filterInput {
width: 100%;
}
.Root__nav-bar .x-filterBox-expandedOrHasFilter {
flex-grow: 1;
margin-right: 5px;
}
.Root__nav-bar:has(> .LayoutResizer__resize-bar:hover) .expand-button > button,
.Root__nav-bar .expand-button:hover > button {
visibility: visible;
height: 32px;
background-color: black;
}
.text-input-form .Button-small-buttonPrimary {
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
font-size: 0.875rem;
font-weight: 700;
font-family: var(--font-family, CircularSp, CircularSp-Arab, CircularSp-Hebr, CircularSp-Cyrl, CircularSp-Grek, CircularSp-Deva, var(--fallback-fonts, sans-serif));
background-color: transparent;
border: 0px;
border-radius: 9999px;
cursor: pointer;
display: inline-block;
position: relative;
text-align: center;
text-decoration: none;
text-transform: none;
touch-action: manipulation;
transition-duration: 33ms;
transition-property:
background-color,
border-color,
color,
box-shadow,
filter,
transform;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
vertical-align: middle;
transform: translate3d(0px, 0px, 0px);
padding: 0px;
min-inline-size: 0px;
}
.text-input-form .ButtonInner-small {
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
position: relative;
background-color: var(--background-base, #1ed760);
color: var(--text-base, #000000);
display: flex;
border-radius: 9999px;
font-size: inherit;
min-block-size: var(--encore-control-size-smaller, 32px);
align-items: center;
justify-content: center;
padding-block-start: var(--encore-spacing-tighter-4, 4px);
padding-block-end: var(--encore-spacing-tighter-4, 4px);
padding-inline-start: var(--encore-spacing-base, 16px);
padding-inline-end: var(--encore-spacing-base, 16px);
}
.text-input-form .Button-small-buttonPrimary:hover .ButtonInner-sc-14ud5tc-0,
.text-input-form .Button-small-buttonPrimary:hover .ButtonFocus-sc-2hq6ey-0 {
transform: scale(1.04);
}
/* ../../../AppData/Local/Temp/tmp-4464-wbZ6O1BKhuot/19178f16f0a2/config_modal.css */
.config-container {
gap: 10px;
display: flex;
flex-direction: column;
}
.config-container .section-header {
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
margin-block: 0px;
font-size: 1.125rem;
font-weight: 700;
color: var(--spice-text);
}
.config-container .col.description {
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
margin-block: 0px;
font-size: 0.875rem;
font-weight: 400;
color: var(--spice-subtext);
}
.config-container .disabled {
opacity: 0;
pointer-events: none;
}
.config-container .text-input {
background: rgba(var(--spice-rgb-selected-row), 0.1);
border: 1px solid transparent;
border-radius: 4px;
color: var(--spice-text);
font-family: inherit;
font-size: 14px;
height: 32px;
padding: 0 12px;
width: 100%;
}
.config-container .text-input:focus {
background-color: var(--spice-tab-active);
border: 1px solid var(--spice-button-disabled);
outline: none;
}
.config-container .dropdown-input {
background-color: var(--spice-tab-active);
border: 0;
border-radius: 4px;
color: rgba(var(--spice-rgb-selected-row), 0.7);
font-size: 14px;
font-weight: 400;
height: 32px;
letter-spacing: 0.24px;
line-height: 20px;
padding: 0 32px 0 12px;
width: 100%;
}
.config-container .tooltip-icon {
float: right;
margin-left: 10px;
display: flex;
align-items: center;
height: 22px;
fill: var(--spice-subtext);
}
.config-container .tooltip-icon:hover {
fill: var(--spice-text);
}
.config-container .tooltip {
text-align: center;
}
.config-container .setting-row {
display: flex;
justify-content: space-between;
}
.config-container .playback-progressbar {
width: 200px;
}
/* ../../../AppData/Local/Temp/tmp-4464-wbZ6O1BKhuot/19178f16f163/shared.css */
.grid {
--grid-gap: 24px;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)) !important;
}
.loadingWrapper {
display: flex;
justify-content: center;
align-items: center;
min-height: 60vh;
flex-direction: column;
gap: 16px;
}
.loadingWrapper .status-icon {
width: 40px;
height: 40px;
fill: currentColor;
}
.page-content {
display: flex;
flex-direction: column;
gap: 24px;
}
.badge {
position: absolute;
top: 3%;
left: 3%;
height: 30px;
width: 30px;
border-radius: 50%;
background-color: rgb(65, 110, 170);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
}
.page-header {
align-content: space-between;
align-items: center;
display: flex;
justify-content: space-between;
margin: 16px 0;
}
.page-header .header-right,
.page-header .header-left {
display: flex;
align-items: center;
gap: 8px;
}
.page-header .header-right {
justify-content: flex-end;
}
.page-header .header-left {
justify-content: flex-start;
}
.new-update {
background-color: var(--spice-player);
color: var(--spice-text);
border-radius: 8px;
padding: 2px 12px;
margin: 0 24px;
border: 0px;
}