diff --git a/.config/spicetify/CustomApps/library/collections_wrapper.js b/.config/spicetify/CustomApps/library/collections_wrapper.js
index a4175d9d..dd3e1c01 100644
--- a/.config/spicetify/CustomApps/library/collections_wrapper.js
+++ b/.config/spicetify/CustomApps/library/collections_wrapper.js
@@ -8,6 +8,7 @@ var library = (() => {
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 });
@@ -21,6 +22,10 @@ var library = (() => {
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 = {};
@@ -77,7 +82,7 @@ var library = (() => {
var v4_default = v4;
// src/extensions/collections_wrapper.ts
- var CollectionWrapper = class extends EventTarget {
+ var _CollectionsWrapper = class extends EventTarget {
_collections;
constructor() {
super();
@@ -90,115 +95,189 @@ var library = (() => {
getCollection(uri) {
return this._collections.find((collection) => collection.uri === uri);
}
- async requestAlbums({ sortOrder, textFilter }) {
+ 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"],
- sortOrder,
- textFilter,
offset: 0,
limit: 9999
});
- return albums;
+ items.push(...albums.items.filter((album) => collection.items.includes(album.uri)));
+ return items;
}
- async getCollectionItems(props) {
- const { collectionUri, textFilter, sortOrder, rootlist, limit = 9999, offset = 0 } = props;
- let collectionItems = this._collections;
- let albumItems = [];
- let unfilteredLength = this._collections.length;
- let openedCollection = "";
- if (collectionUri) {
- const collection = this.getCollection(collectionUri);
- const res = await this.requestAlbums({ sortOrder, textFilter });
- const collectionSet = new Set(collection.items);
- const commonElements = res.items.filter((item) => collectionSet.has(item.uri));
- const collections = this._collections.filter((collection2) => collection2.parentCollection === collectionUri);
- openedCollection = collection.name;
- collectionItems = collections;
- albumItems = commonElements;
- unfilteredLength = collection.totalLength;
- }
+ 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) {
- let regex = new RegExp("\\b" + textFilter, "i");
- collectionItems = collectionItems.filter((item) => {
- return regex.test(item.name);
- });
+ const regex = new RegExp(`\\b${textFilter}`, "i");
+ items = items.filter((collection) => regex.test(collection.name));
}
- if (rootlist && !collectionUri) {
- const res = await this.requestAlbums({ sortOrder, textFilter });
- albumItems = res.items;
- if (!textFilter) {
- const collectionSet = new Set(this._collections.map((collection) => collection.items).flat());
- const uncommonElements = res.items.filter((item) => !collectionSet.has(item.uri));
- collectionItems = this._collections.filter((collection) => !collection.parentCollection);
- albumItems = uncommonElements;
- unfilteredLength = this._collections.length + uncommonElements.length;
+ 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);
}
}
- if (offset > 0)
- collectionItems = [];
- return {
- openedCollection,
- items: [...collectionItems, ...albumItems.slice(offset, offset + limit)],
- totalLength: albumItems.length + collectionItems.length,
- unfilteredLength
- };
+ }
+ 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 uri = v4_default();
- const collection = {
+ const id = v4_default();
+ this._collections.push({
type: "collection",
- uri,
+ uri: id,
name,
items: [],
- totalLength: 0,
- imgUrl: "",
+ addedAt: new Date(),
+ lastPlayedAt: new Date(),
parentCollection
- };
- this._collections.push(collection);
+ });
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);
- collection.totalLength++;
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);
- collection.totalLength--;
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, newName) {
+ renameCollection(uri, name) {
const collection = this.getCollection(uri);
if (!collection)
return;
- collection.name = newName;
+ collection.name = name;
this.saveCollections();
Spicetify.showNotification("Collection renamed");
}
- setCollectionImage(uri, imgUrl) {
+ setCollectionImage(uri, url) {
const collection = this.getCollection(uri);
if (!collection)
return;
- collection.imgUrl = imgUrl;
+ collection.image = url;
this.saveCollections();
Spicetify.showNotification("Collection image set");
}
@@ -206,12 +285,15 @@ var library = (() => {
const collection = this.getCollection(uri);
if (!collection)
return;
- collection.imgUrl = "";
+ collection.image = void 0;
this.saveCollections();
Spicetify.showNotification("Collection image removed");
}
};
- var collections_wrapper_default = CollectionWrapper;
+ var CollectionsWrapper = _CollectionsWrapper;
+ __publicField(CollectionsWrapper, "INSTANCE", new _CollectionsWrapper());
+ window.CollectionsWrapper = CollectionsWrapper.INSTANCE;
+ var collections_wrapper_default = CollectionsWrapper;
return __toCommonJS(collections_wrapper_exports);
})();
diff --git a/.config/spicetify/CustomApps/library/extension.js b/.config/spicetify/CustomApps/library/extension.js
index 55f750cc..7de5c55c 100644
--- a/.config/spicetify/CustomApps/library/extension.js
+++ b/.config/spicetify/CustomApps/library/extension.js
@@ -285,7 +285,9 @@ var library = (() => {
});
};
var ToggleFiltersButton = () => {
- const [direction, setDirection] = import_react3.default.useState(document.body.classList.contains("show-ylx-filters") ? "up" : "down");
+ 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") {
@@ -332,31 +334,207 @@ var library = (() => {
};
var collapse_button_default = CollapseButton;
- // src/components/expand_button.tsx
+ // src/components/album_menu_item.tsx
+ var import_react8 = __toESM(require_react());
+
+ // src/components/leading_icon.tsx
var import_react5 = __toESM(require_react());
- var expandLibrary = () => {
- Spicetify.Platform.LocalStorageAPI.setItem("ylx-sidebar-state", 0);
- };
- var ExpandIcon = () => {
- const { IconComponent } = Spicetify.ReactComponent;
- return /* @__PURE__ */ import_react5.default.createElement(IconComponent, {
+ var LeadingIcon = ({ path }) => {
+ return /* @__PURE__ */ import_react5.default.createElement(Spicetify.ReactComponent.IconComponent, {
semanticColor: "textSubdued",
dangerouslySetInnerHTML: {
- __html: ''
+ __html: ``
},
iconSize: 16
});
};
- var ExpandButton = () => {
- const { ButtonTertiary } = Spicetify.ReactComponent;
- return /* @__PURE__ */ import_react5.default.createElement(ButtonTertiary, {
- buttonSize: "sm",
- "aria-label": "Show Filters",
- iconOnly: ExpandIcon,
- onClick: expandLibrary
+ 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 expand_button_default = ExpandButton;
+ 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;
@@ -407,7 +585,7 @@ var library = (() => {
var v4_default = v4;
// src/extensions/collections_wrapper.ts
- var CollectionWrapper = class extends EventTarget {
+ var _CollectionsWrapper = class extends EventTarget {
_collections;
constructor() {
super();
@@ -420,115 +598,189 @@ var library = (() => {
getCollection(uri) {
return this._collections.find((collection) => collection.uri === uri);
}
- async requestAlbums({ sortOrder, textFilter }) {
+ 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"],
- sortOrder,
- textFilter,
offset: 0,
limit: 9999
});
- return albums;
+ items.push(...albums.items.filter((album) => collection.items.includes(album.uri)));
+ return items;
}
- async getCollectionItems(props) {
- const { collectionUri, textFilter, sortOrder, rootlist, limit = 9999, offset = 0 } = props;
- let collectionItems = this._collections;
- let albumItems = [];
- let unfilteredLength = this._collections.length;
- let openedCollection = "";
- if (collectionUri) {
- const collection = this.getCollection(collectionUri);
- const res = await this.requestAlbums({ sortOrder, textFilter });
- const collectionSet = new Set(collection.items);
- const commonElements = res.items.filter((item) => collectionSet.has(item.uri));
- const collections = this._collections.filter((collection2) => collection2.parentCollection === collectionUri);
- openedCollection = collection.name;
- collectionItems = collections;
- albumItems = commonElements;
- unfilteredLength = collection.totalLength;
- }
+ 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) {
- let regex = new RegExp("\\b" + textFilter, "i");
- collectionItems = collectionItems.filter((item) => {
- return regex.test(item.name);
- });
+ const regex = new RegExp(`\\b${textFilter}`, "i");
+ items = items.filter((collection) => regex.test(collection.name));
}
- if (rootlist && !collectionUri) {
- const res = await this.requestAlbums({ sortOrder, textFilter });
- albumItems = res.items;
- if (!textFilter) {
- const collectionSet = new Set(this._collections.map((collection) => collection.items).flat());
- const uncommonElements = res.items.filter((item) => !collectionSet.has(item.uri));
- collectionItems = this._collections.filter((collection) => !collection.parentCollection);
- albumItems = uncommonElements;
- unfilteredLength = this._collections.length + uncommonElements.length;
+ 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);
}
}
- if (offset > 0)
- collectionItems = [];
- return {
- openedCollection,
- items: [...collectionItems, ...albumItems.slice(offset, offset + limit)],
- totalLength: albumItems.length + collectionItems.length,
- unfilteredLength
- };
+ }
+ 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 uri = v4_default();
- const collection = {
+ const id = v4_default();
+ this._collections.push({
type: "collection",
- uri,
+ uri: id,
name,
items: [],
- totalLength: 0,
- imgUrl: "",
+ addedAt: new Date(),
+ lastPlayedAt: new Date(),
parentCollection
- };
- this._collections.push(collection);
+ });
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);
- collection.totalLength++;
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);
- collection.totalLength--;
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, newName) {
+ renameCollection(uri, name) {
const collection = this.getCollection(uri);
if (!collection)
return;
- collection.name = newName;
+ collection.name = name;
this.saveCollections();
Spicetify.showNotification("Collection renamed");
}
- setCollectionImage(uri, imgUrl) {
+ setCollectionImage(uri, url) {
const collection = this.getCollection(uri);
if (!collection)
return;
- collection.imgUrl = imgUrl;
+ collection.image = url;
this.saveCollections();
Spicetify.showNotification("Collection image set");
}
@@ -536,201 +788,17 @@ var library = (() => {
const collection = this.getCollection(uri);
if (!collection)
return;
- collection.imgUrl = "";
+ collection.image = void 0;
this.saveCollections();
Spicetify.showNotification("Collection image removed");
}
};
- var collections_wrapper_default = CollectionWrapper;
-
- // src/components/album_menu_item.tsx
- var import_react9 = __toESM(require_react());
-
- // src/components/leading_icon.tsx
- var import_react6 = __toESM(require_react());
- var LeadingIcon = ({ path }) => {
- return /* @__PURE__ */ import_react6.default.createElement(Spicetify.ReactComponent.IconComponent, {
- semanticColor: "textSubdued",
- dangerouslySetInnerHTML: {
- __html: ``
- },
- iconSize: 16
- });
- };
- var leading_icon_default = LeadingIcon;
-
- // src/components/text_input_dialog.tsx
- var import_react7 = __toESM(require_react());
- var TextInputDialog = (props) => {
- const { ButtonPrimary } = Spicetify.ReactComponent;
- const { def, placeholder, onSave } = props;
- const [value, setValue] = import_react7.default.useState(def);
- const onSubmit = (e) => {
- e.preventDefault();
- Spicetify.PopupModal.hide();
- onSave(value);
- };
- return /* @__PURE__ */ import_react7.default.createElement(import_react7.default.Fragment, null, /* @__PURE__ */ import_react7.default.createElement("form", {
- className: "text-input-form",
- onSubmit
- }, /* @__PURE__ */ import_react7.default.createElement("label", {
- className: "text-input-wrapper"
- }, /* @__PURE__ */ import_react7.default.createElement("input", {
- className: "text-input",
- type: "text",
- value,
- placeholder,
- onChange: (e) => setValue(e.target.value)
- })), /* @__PURE__ */ import_react7.default.createElement("button", {
- type: "submit",
- "data-encore-id": "buttonPrimary",
- className: "Button-sc-qlcn5g-0 Button-small-buttonPrimary"
- }, /* @__PURE__ */ import_react7.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_react8 = __toESM(require_react());
- var SearchBar = (props) => {
- const { setSearch, placeholder } = props;
- const handleChange = (e) => {
- setSearch(e.target.value);
- };
- return /* @__PURE__ */ import_react8.default.createElement("div", {
- className: "x-filterBox-filterInputContainer x-filterBox-expandedOrHasFilter",
- role: "search"
- }, /* @__PURE__ */ import_react8.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_react8.default.createElement("div", {
- className: "x-filterBox-overlay"
- }, /* @__PURE__ */ import_react8.default.createElement("span", {
- className: "x-filterBox-searchIconContainer"
- }, /* @__PURE__ */ import_react8.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_react8.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_react8.default.createElement("button", {
- className: "x-filterBox-expandButton",
- "aria-hidden": "false",
- "aria-label": "Search Playlists"
- }, /* @__PURE__ */ import_react8.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_react8.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) => {
- SpicetifyLibrary.CollectionWrapper.createCollection(value);
- };
- Spicetify.PopupModal.display({
- title: "Create Collection",
- content: /* @__PURE__ */ import_react9.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_react9.default.useState("");
- const [collections, setCollections] = import_react9.default.useState(null);
- const context = import_react9.default.useContext(Spicetify.ContextMenuV2._context);
- const uri = context?.props?.uri;
- import_react9.default.useEffect(() => {
- const fetchCollections = async () => {
- const res = await SpicetifyLibrary.CollectionWrapper.getCollectionItems({ textFilter });
- setCollections(res);
- };
- fetchCollections();
- }, [textFilter]);
- if (!collections)
- return /* @__PURE__ */ import_react9.default.createElement(import_react9.default.Fragment, null);
- const addToCollection = (collectionUri) => {
- SpicetifyLibrary.CollectionWrapper.addAlbumToCollection(collectionUri, uri);
- };
- const activeCollections = SpicetifyLibrary.CollectionWrapper.getCollectionsWithAlbum(uri);
- const hasCollections = activeCollections.length > 0;
- const removeFromCollections = () => {
- activeCollections.forEach((collection) => {
- SpicetifyLibrary.CollectionWrapper.removeAlbumFromCollection(collection.uri, uri);
- });
- };
- const allCollectionsLength = collections.unfilteredLength;
- const menuItems = collections.items.map((collection, index) => {
- return /* @__PURE__ */ import_react9.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_react9.default.createElement("div", {
- className: "main-contextMenu-filterPlaylistSearchContainer",
- style: { "--context-menu-submenu-length": `${menuLength}` }
- }, /* @__PURE__ */ import_react9.default.createElement("li", {
- role: "presentation",
- className: "main-contextMenu-filterPlaylistSearch"
- }, /* @__PURE__ */ import_react9.default.createElement("div", {
- role: "menuitem"
- }, /* @__PURE__ */ import_react9.default.createElement(searchbar_default, {
- setSearch: setTextFilter,
- placeholder: "collections"
- }))), /* @__PURE__ */ import_react9.default.createElement(MenuItem, {
- key: "new-collection",
- leadingIcon: /* @__PURE__ */ import_react9.default.createElement(leading_icon_default, {
- path: SVGIcons["plus2px"]
- }),
- onClick: createCollection
- }, "Create collection"), hasCollections && /* @__PURE__ */ import_react9.default.createElement(MenuItem, {
- key: "remove-collection",
- leadingIcon: /* @__PURE__ */ import_react9.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_react9.default.createElement(MenuSubMenuItem, {
- displayText: "Add to collection",
- divider: "after",
- leadingIcon: /* @__PURE__ */ import_react9.default.createElement(leading_icon_default, {
- path: SVGIcons["plus2px"]
- })
- }, /* @__PURE__ */ import_react9.default.createElement(CollectionSearchMenu, null));
- };
- var album_menu_item_default = AlbumMenuItem;
+ var CollectionsWrapper2 = _CollectionsWrapper;
+ __publicField(CollectionsWrapper2, "INSTANCE", new _CollectionsWrapper());
+ window.CollectionsWrapper = CollectionsWrapper2.INSTANCE;
// src/extensions/folder_image_wrapper.ts
- var FolderImageWrapper = class extends EventTarget {
+ var _FolderImageWrapper = class extends EventTarget {
_folderImages;
constructor() {
super();
@@ -757,7 +825,9 @@ var library = (() => {
localStorage.setItem("library:folderImages", JSON.stringify(this._folderImages));
}
};
- var folder_image_wrapper_default = FolderImageWrapper;
+ var FolderImageWrapper2 = _FolderImageWrapper;
+ __publicField(FolderImageWrapper2, "INSTANCE", new _FolderImageWrapper());
+ window.FolderImageWrapper = FolderImageWrapper2.INSTANCE;
// src/extensions/extension.tsx
var styleLink = document.createElement("link");
@@ -773,6 +843,7 @@ var library = (() => {
};
var FolderImage = ({ url }) => {
return /* @__PURE__ */ import_react10.default.createElement("img", {
+ alt: "Folder Image",
"aria-hidden": "true",
draggable: "false",
loading: "eager",
@@ -793,7 +864,7 @@ var library = (() => {
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 SpicetifyLibrary2 = class {
+ var SpicetifyLibrary = class {
ConfigWrapper = new config_wrapper_default(
[
{
@@ -812,16 +883,25 @@ var library = (() => {
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"
);
- CollectionWrapper = new collections_wrapper_default();
- FolderImageWrapper = new folder_image_wrapper_default();
};
- window.SpicetifyLibrary = new SpicetifyLibrary2();
+ window.SpicetifyLibrary = new SpicetifyLibrary();
(function wait() {
- const { LocalStorageAPI } = Spicetify?.Platform;
+ const { LocalStorageAPI } = Spicetify.Platform;
if (!LocalStorageAPI) {
setTimeout(wait, 100);
return;
@@ -829,22 +909,23 @@ var library = (() => {
main(LocalStorageAPI);
})();
function main(LocalStorageAPI) {
- const isAlbum = (props) => {
- return props.uri?.includes("album");
- };
+ 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(() => {
- Array.from(rootlist.children).forEach((el) => {
- const uri = el.querySelector(".main-yourLibraryX-listItemGroup")?.getAttribute("aria-labelledby")?.slice(14);
+ 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 = window.SpicetifyLibrary.FolderImageWrapper.getFolderImage(uri);
+ const imageUrl = FolderImageWrapper.getFolderImage(uri);
if (!imageUrl)
import_react_dom.default.render(/* @__PURE__ */ import_react10.default.createElement(FolderPlaceholder, null), imageBox);
else
@@ -852,17 +933,13 @@ var library = (() => {
url: imageUrl
}), imageBox);
}
- });
+ }
}, 500);
}
injectFolderImages();
- window.SpicetifyLibrary.FolderImageWrapper.addEventListener("update", () => {
- injectFolderImages();
- });
+ FolderImageWrapper.addEventListener("update", injectFolderImages);
function injectYLXButtons() {
- const ylx_filter = document.querySelector(
- ".main-yourLibraryX-libraryRootlist > .main-yourLibraryX-libraryFilter"
- );
+ const ylx_filter = document.querySelector(".main-yourLibraryX-libraryRootlist > .main-yourLibraryX-libraryFilter");
if (!ylx_filter) {
return setTimeout(injectYLXButtons, 100);
}
@@ -887,37 +964,17 @@ var library = (() => {
collapseButton
);
}
- function injectExpandButton() {
- const sidebarHeader = document.querySelector("li.main-yourLibraryX-navItem[data-id='/library']");
- if (!sidebarHeader) {
- return setTimeout(injectExpandButton, 100);
- }
- const expandButton = document.createElement("span");
- expandButton.classList.add("expand-button");
- sidebarHeader.appendChild(expandButton);
- import_react_dom.default.render(/* @__PURE__ */ import_react10.default.createElement(expand_button_default, null), expandButton);
- }
- function removeExpandButton() {
- const expandButton = document.querySelector(".expand-button");
- if (expandButton)
- expandButton.remove();
- }
const state = LocalStorageAPI.getItem("ylx-sidebar-state");
- if (state === 0) {
+ if (state === 0)
injectYLXButtons();
- } else if (state === 1) {
- injectExpandButton();
- }
LocalStorageAPI.getEvents()._emitter.addListener("update", (e) => {
const { key, value } = e.data;
if (key === "ylx-sidebar-state" && value === 0) {
injectFolderImages();
injectYLXButtons();
- removeExpandButton();
}
if (key === "ylx-sidebar-state" && value === 1) {
injectFolderImages();
- injectExpandButton();
}
});
}
diff --git a/.config/spicetify/CustomApps/library/folder_image_wrapper.js b/.config/spicetify/CustomApps/library/folder_image_wrapper.js
index 57f0f545..b8711081 100644
--- a/.config/spicetify/CustomApps/library/folder_image_wrapper.js
+++ b/.config/spicetify/CustomApps/library/folder_image_wrapper.js
@@ -8,6 +8,7 @@ var library = (() => {
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 });
@@ -21,13 +22,17 @@ var library = (() => {
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 {
+ var _FolderImageWrapper = class extends EventTarget {
_folderImages;
constructor() {
super();
@@ -54,6 +59,9 @@ var library = (() => {
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);
})();
diff --git a/.config/spicetify/CustomApps/library/index.js b/.config/spicetify/CustomApps/library/index.js
index 326195fd..2e4b7eb6 100644
--- a/.config/spicetify/CustomApps/library/index.js
+++ b/.config/spicetify/CustomApps/library/index.js
@@ -47,7 +47,7 @@ var library = (() => {
});
// src/app.tsx
- var import_react23 = __toESM(require_react());
+ var import_react26 = __toESM(require_react());
// ../node_modules/spcr-navigation-bar/useNavigationBar.tsx
var import_react3 = __toESM(require_react());
@@ -105,7 +105,7 @@ var library = (() => {
});
var optionsMenu_default = OptionsMenu;
- // postcss-module:C:\Users\user\AppData\Local\Temp\tmp-5152-1Ex7Bvh4Tu6u\18f3cb8bc8b4\navBar.module.css
+ // postcss-module:C:\Users\user\AppData\Local\Temp\tmp-4464-wbZ6O1BKhuot\19178f16f304\navBar.module.css
var navBar_module_default = { "topBarHeaderItem": "navBar-module__topBarHeaderItem___piw4C_library", "topBarHeaderItemLink": "navBar-module__topBarHeaderItemLink___xA4uv_library", "topBarActive": "navBar-module__topBarActive___XhWpm_library", "topBarNav": "navBar-module__topBarNav___qWGeZ_library", "optionsMenuDropBox": "navBar-module__optionsMenuDropBox___pzfNI_library" };
// ../node_modules/spcr-navigation-bar/navBar.tsx
@@ -233,7 +233,7 @@ var library = (() => {
var useNavigationBar_default = useNavigationBar;
// src/pages/albums.tsx
- var import_react19 = __toESM(require_react());
+ var import_react21 = __toESM(require_react());
// src/components/searchbar.tsx
var import_react4 = __toESM(require_react());
@@ -271,7 +271,8 @@ var library = (() => {
})))), /* @__PURE__ */ import_react4.default.createElement("button", {
className: "x-filterBox-expandButton",
"aria-hidden": "false",
- "aria-label": "Search Playlists"
+ "aria-label": "Search Playlists",
+ type: "button"
}, /* @__PURE__ */ import_react4.default.createElement("svg", {
"data-encore-id": "icon",
role: "img",
@@ -458,87 +459,44 @@ var library = (() => {
};
var page_container_default = PageContainer;
- // ../shared/components/status.tsx
- var import_react10 = __toESM(require_react());
- var ErrorIcon = () => {
- return /* @__PURE__ */ import_react10.default.createElement("svg", {
- "data-encore-id": "icon",
- role: "img",
- "aria-hidden": "true",
- viewBox: "0 0 24 24",
- className: "status-icon"
- }, /* @__PURE__ */ import_react10.default.createElement("path", {
- d: "M11 18v-2h2v2h-2zm0-4V6h2v8h-2z"
- }), /* @__PURE__ */ import_react10.default.createElement("path", {
- d: "M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18zM1 12C1 5.925 5.925 1 12 1s11 4.925 11 11-4.925 11-11 11S1 18.075 1 12z"
- }));
- };
- var LibraryIcon = () => {
- return /* @__PURE__ */ import_react10.default.createElement("svg", {
- role: "img",
- height: "46",
- width: "46",
- "aria-hidden": "true",
- viewBox: "0 0 24 24",
- "data-encore-id": "icon",
- className: "status-icon"
- }, /* @__PURE__ */ import_react10.default.createElement("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"
- }));
- };
- var Status = (props) => {
- const [isVisible, setIsVisible] = import_react10.default.useState(false);
- import_react10.default.useEffect(() => {
- const to = setTimeout(() => {
- setIsVisible(true);
- }, 500);
- return () => clearTimeout(to);
- }, []);
- return isVisible ? /* @__PURE__ */ import_react10.default.createElement(import_react10.default.Fragment, null, /* @__PURE__ */ import_react10.default.createElement("div", {
- className: "loadingWrapper"
- }, props.icon === "error" ? /* @__PURE__ */ import_react10.default.createElement(ErrorIcon, null) : /* @__PURE__ */ import_react10.default.createElement(LibraryIcon, null), /* @__PURE__ */ import_react10.default.createElement("h1", null, props.heading), /* @__PURE__ */ import_react10.default.createElement("h3", null, props.subheading))) : /* @__PURE__ */ import_react10.default.createElement(import_react10.default.Fragment, null);
- };
- var status_default = Status;
-
// src/components/collection_menu.tsx
- var import_react13 = __toESM(require_react());
+ var import_react12 = __toESM(require_react());
// src/components/text_input_dialog.tsx
- var import_react11 = __toESM(require_react());
+ var import_react10 = __toESM(require_react());
var TextInputDialog = (props) => {
- const { ButtonPrimary } = Spicetify.ReactComponent;
const { def, placeholder, onSave } = props;
- const [value, setValue] = import_react11.default.useState(def);
+ const [value, setValue] = import_react10.default.useState(def || "");
const onSubmit = (e) => {
e.preventDefault();
Spicetify.PopupModal.hide();
onSave(value);
};
- return /* @__PURE__ */ import_react11.default.createElement(import_react11.default.Fragment, null, /* @__PURE__ */ import_react11.default.createElement("form", {
+ return /* @__PURE__ */ import_react10.default.createElement(import_react10.default.Fragment, null, /* @__PURE__ */ import_react10.default.createElement("form", {
className: "text-input-form",
onSubmit
- }, /* @__PURE__ */ import_react11.default.createElement("label", {
+ }, /* @__PURE__ */ import_react10.default.createElement("label", {
className: "text-input-wrapper"
- }, /* @__PURE__ */ import_react11.default.createElement("input", {
+ }, /* @__PURE__ */ import_react10.default.createElement("input", {
className: "text-input",
type: "text",
value,
placeholder,
onChange: (e) => setValue(e.target.value)
- })), /* @__PURE__ */ import_react11.default.createElement("button", {
+ })), /* @__PURE__ */ import_react10.default.createElement("button", {
type: "submit",
"data-encore-id": "buttonPrimary",
className: "Button-sc-qlcn5g-0 Button-small-buttonPrimary"
- }, /* @__PURE__ */ import_react11.default.createElement("span", {
+ }, /* @__PURE__ */ import_react10.default.createElement("span", {
className: "ButtonInner-sc-14ud5tc-0 ButtonInner-small encore-bright-accent-set"
}, "Save"))));
};
var text_input_dialog_default = TextInputDialog;
// src/components/leading_icon.tsx
- var import_react12 = __toESM(require_react());
+ var import_react11 = __toESM(require_react());
var LeadingIcon = ({ path }) => {
- return /* @__PURE__ */ import_react12.default.createElement(Spicetify.ReactComponent.IconComponent, {
+ return /* @__PURE__ */ import_react11.default.createElement(Spicetify.ReactComponent.IconComponent, {
semanticColor: "textSubdued",
dangerouslySetInnerHTML: {
__html: ``
@@ -551,32 +509,39 @@ var library = (() => {
// src/components/collection_menu.tsx
var editIconPath = '';
var deleteIconPath = '';
+ var addIconPath = '';
var CollectionMenu = ({ id }) => {
const { Menu, MenuItem: MenuItem2 } = Spicetify.ReactComponent;
const deleteCollection = () => {
- SpicetifyLibrary.CollectionWrapper.deleteCollection(id);
+ CollectionsWrapper.deleteCollection(id);
+ };
+ const deleteCollectionAndAlbums = () => {
+ CollectionsWrapper.deleteCollectionAndAlbums(id);
};
const renameCollection = () => {
- const name = SpicetifyLibrary.CollectionWrapper.getCollection(id).name;
+ const name = CollectionsWrapper.getCollection(id)?.name;
const rename = (newName) => {
- SpicetifyLibrary.CollectionWrapper.renameCollection(id, newName);
+ CollectionsWrapper.renameCollection(id, newName);
};
Spicetify.PopupModal.display({
title: "Rename Collection",
- content: /* @__PURE__ */ import_react13.default.createElement(text_input_dialog_default, {
+ content: /* @__PURE__ */ import_react12.default.createElement(text_input_dialog_default, {
def: name,
+ placeholder: "Collection Name",
onSave: rename
})
});
};
- const image = SpicetifyLibrary.CollectionWrapper.getCollection(id).imgUrl;
+ const collection = CollectionsWrapper.getCollection(id);
+ const image = collection?.image;
+ const synced = collection?.syncedPlaylistUri;
const setCollectionImage = () => {
const setImg = (imgUrl) => {
- SpicetifyLibrary.CollectionWrapper.setCollectionImage(id, imgUrl);
+ CollectionsWrapper.setCollectionImage(id, imgUrl);
};
Spicetify.PopupModal.display({
title: "Set Collection Image",
- content: /* @__PURE__ */ import_react13.default.createElement(text_input_dialog_default, {
+ content: /* @__PURE__ */ import_react12.default.createElement(text_input_dialog_default, {
def: image,
placeholder: "Image URL",
onSave: setImg
@@ -584,25 +549,46 @@ var library = (() => {
});
};
const removeImage = () => {
- SpicetifyLibrary.CollectionWrapper.removeCollectionImage(id);
+ CollectionsWrapper.removeCollectionImage(id);
};
- return /* @__PURE__ */ import_react13.default.createElement(Menu, null, /* @__PURE__ */ import_react13.default.createElement(MenuItem2, {
- leadingIcon: /* @__PURE__ */ import_react13.default.createElement(leading_icon_default, {
+ const convertToPlaylist = () => {
+ CollectionsWrapper.convertToPlaylist(id);
+ };
+ const unsyncPlaylist = () => {
+ CollectionsWrapper.unsyncCollection(id);
+ };
+ return /* @__PURE__ */ import_react12.default.createElement(Menu, null, /* @__PURE__ */ import_react12.default.createElement(MenuItem2, {
+ leadingIcon: /* @__PURE__ */ import_react12.default.createElement(leading_icon_default, {
path: editIconPath
}),
onClick: renameCollection
- }, "Rename"), /* @__PURE__ */ import_react13.default.createElement(MenuItem2, {
- leadingIcon: /* @__PURE__ */ import_react13.default.createElement(leading_icon_default, {
+ }, "Rename"), /* @__PURE__ */ import_react12.default.createElement(MenuItem2, {
+ leadingIcon: /* @__PURE__ */ import_react12.default.createElement(leading_icon_default, {
path: deleteIconPath
}),
onClick: deleteCollection
- }, "Delete"), /* @__PURE__ */ import_react13.default.createElement(MenuItem2, {
- leadingIcon: /* @__PURE__ */ import_react13.default.createElement(leading_icon_default, {
+ }, "Delete (Only Collection)"), /* @__PURE__ */ import_react12.default.createElement(MenuItem2, {
+ leadingIcon: /* @__PURE__ */ import_react12.default.createElement(leading_icon_default, {
+ path: deleteIconPath
+ }),
+ onClick: deleteCollectionAndAlbums
+ }, "Delete (Collection and Albums)"), synced ? /* @__PURE__ */ import_react12.default.createElement(MenuItem2, {
+ leadingIcon: /* @__PURE__ */ import_react12.default.createElement(leading_icon_default, {
+ path: deleteIconPath
+ }),
+ onClick: unsyncPlaylist
+ }, "Unsync from Playlist") : /* @__PURE__ */ import_react12.default.createElement(MenuItem2, {
+ leadingIcon: /* @__PURE__ */ import_react12.default.createElement(leading_icon_default, {
+ path: addIconPath
+ }),
+ onClick: convertToPlaylist
+ }, "Sync to Playlist"), /* @__PURE__ */ import_react12.default.createElement(MenuItem2, {
+ leadingIcon: /* @__PURE__ */ import_react12.default.createElement(leading_icon_default, {
path: editIconPath
}),
onClick: setCollectionImage
- }, "Set Collection Image"), image && /* @__PURE__ */ import_react13.default.createElement(MenuItem2, {
- leadingIcon: /* @__PURE__ */ import_react13.default.createElement(leading_icon_default, {
+ }, "Set Collection Image"), image && /* @__PURE__ */ import_react12.default.createElement(MenuItem2, {
+ leadingIcon: /* @__PURE__ */ import_react12.default.createElement(leading_icon_default, {
path: deleteIconPath
}),
onClick: removeImage
@@ -611,34 +597,35 @@ var library = (() => {
var collection_menu_default = CollectionMenu;
// src/components/folder_menu.tsx
- var import_react14 = __toESM(require_react());
+ var import_react13 = __toESM(require_react());
var editIconPath2 = '';
var deleteIconPath2 = '';
var FolderMenu = ({ uri }) => {
const { MenuItem: MenuItem2, Menu } = Spicetify.ReactComponent;
- const image = SpicetifyLibrary.FolderImageWrapper.getFolderImage(uri);
+ const image = FolderImageWrapper.getFolderImage(uri);
const setImage = () => {
const setNewImage = (newUrl) => {
- SpicetifyLibrary.FolderImageWrapper.setFolderImage({ uri, url: newUrl });
+ FolderImageWrapper.setFolderImage({ uri, url: newUrl });
};
Spicetify.PopupModal.display({
title: "Set Folder Image",
- content: /* @__PURE__ */ import_react14.default.createElement(text_input_dialog_default, {
+ content: /* @__PURE__ */ import_react13.default.createElement(text_input_dialog_default, {
def: image,
- onSave: setNewImage
+ onSave: setNewImage,
+ placeholder: "Image URL"
})
});
};
const removeImage = () => {
- SpicetifyLibrary.FolderImageWrapper.removeFolderImage(uri);
+ FolderImageWrapper.removeFolderImage(uri);
};
- return /* @__PURE__ */ import_react14.default.createElement(Menu, null, /* @__PURE__ */ import_react14.default.createElement(MenuItem2, {
- leadingIcon: /* @__PURE__ */ import_react14.default.createElement(leading_icon_default, {
+ return /* @__PURE__ */ import_react13.default.createElement(Menu, null, /* @__PURE__ */ import_react13.default.createElement(MenuItem2, {
+ leadingIcon: /* @__PURE__ */ import_react13.default.createElement(leading_icon_default, {
path: editIconPath2
}),
onClick: setImage
- }, "Set Folder Image"), image && /* @__PURE__ */ import_react14.default.createElement(MenuItem2, {
- leadingIcon: /* @__PURE__ */ import_react14.default.createElement(leading_icon_default, {
+ }, "Set Folder Image"), image && /* @__PURE__ */ import_react13.default.createElement(MenuItem2, {
+ leadingIcon: /* @__PURE__ */ import_react13.default.createElement(leading_icon_default, {
path: deleteIconPath2
}),
onClick: removeImage
@@ -647,12 +634,12 @@ var library = (() => {
var folder_menu_default = FolderMenu;
// ../shared/components/spotify_card.tsx
- var import_react16 = __toESM(require_react());
+ var import_react15 = __toESM(require_react());
// ../shared/components/folder_fallback.tsx
- var import_react15 = __toESM(require_react());
+ var import_react14 = __toESM(require_react());
var FolderSVG = (e) => {
- return /* @__PURE__ */ import_react15.default.createElement(Spicetify.ReactComponent.IconComponent, {
+ return /* @__PURE__ */ import_react14.default.createElement(Spicetify.ReactComponent.IconComponent, {
semanticColor: "textSubdued",
viewBox: "0 0 24 24",
size: "xxlarge",
@@ -669,41 +656,44 @@ var library = (() => {
const { Cards, TextComponent, ArtistMenu, AlbumMenu, PodcastShowMenu, PlaylistMenu, ContextMenu } = Spicetify.ReactComponent;
const { FeatureCard: Card, CardImage } = Cards;
const { createHref, push } = Spicetify.Platform.History;
- const { type, header, uri, imageUrl, subheader, artistUri } = props;
- const backupImageUrl = type === "folder" || type === "collection" ? "https://raw.githubusercontent.com/harbassan/spicetify-apps/main/shared/placeholders/folder_placeholder.png" : "https://raw.githubusercontent.com/harbassan/spicetify-apps/main/shared/placeholders/def_placeholder.png";
+ const { type, header, uri, imageUrl, subheader, artistUri, badge, provider } = props;
const Menu = () => {
switch (type) {
case "artist":
- return /* @__PURE__ */ import_react16.default.createElement(ArtistMenu, {
+ return /* @__PURE__ */ import_react15.default.createElement(ArtistMenu, {
uri
});
case "album":
- return /* @__PURE__ */ import_react16.default.createElement(AlbumMenu, {
+ return /* @__PURE__ */ import_react15.default.createElement(AlbumMenu, {
uri,
artistUri,
canRemove: true
});
case "playlist":
- return /* @__PURE__ */ import_react16.default.createElement(PlaylistMenu, {
+ return /* @__PURE__ */ import_react15.default.createElement(PlaylistMenu, {
uri
});
case "show":
- return /* @__PURE__ */ import_react16.default.createElement(PodcastShowMenu, {
+ return /* @__PURE__ */ import_react15.default.createElement(PodcastShowMenu, {
uri
});
case "collection":
- return /* @__PURE__ */ import_react16.default.createElement(collection_menu_default, {
+ return /* @__PURE__ */ import_react15.default.createElement(collection_menu_default, {
id: uri
});
case "folder":
- return /* @__PURE__ */ import_react16.default.createElement(folder_menu_default, {
+ return /* @__PURE__ */ import_react15.default.createElement(folder_menu_default, {
uri
});
default:
- return /* @__PURE__ */ import_react16.default.createElement(import_react16.default.Fragment, null);
+ return /* @__PURE__ */ import_react15.default.createElement(import_react15.default.Fragment, null);
}
};
- const lastfmProps = type === "lastfm" ? { onClick: () => window.open(uri, "_blank"), isPlayable: false, delegateNavigation: true } : {};
+ const lastfmProps = provider === "lastfm" ? {
+ onClick: () => window.open(uri, "_blank"),
+ isPlayable: false,
+ delegateNavigation: true
+ } : {};
const folderProps = type === "folder" ? {
delegateNavigation: true,
onClick: () => {
@@ -718,13 +708,15 @@ var library = (() => {
push({ pathname: `/library/collection/${uri}` });
}
} : {};
- return /* @__PURE__ */ import_react16.default.createElement(ContextMenu, {
+ return /* @__PURE__ */ import_react15.default.createElement(ContextMenu, {
menu: Menu(),
trigger: "right-click"
- }, /* @__PURE__ */ import_react16.default.createElement(Card, {
+ }, /* @__PURE__ */ import_react15.default.createElement("div", {
+ style: { position: "relative" }
+ }, /* @__PURE__ */ import_react15.default.createElement(Card, {
featureIdentifier: type,
headerText: header,
- renderCardImage: () => /* @__PURE__ */ import_react16.default.createElement(CardImage, {
+ renderCardImage: () => /* @__PURE__ */ import_react15.default.createElement(CardImage, {
images: [
{
height: 640,
@@ -733,9 +725,9 @@ var library = (() => {
}
],
isCircular: type === "artist",
- FallbackComponent: folder_fallback_default
+ FallbackComponent: type === "folder" || type === "collection" ? folder_fallback_default : void 0
}),
- renderSubHeaderContent: () => /* @__PURE__ */ import_react16.default.createElement(TextComponent, {
+ renderSubHeaderContent: () => /* @__PURE__ */ import_react15.default.createElement(TextComponent, {
as: "div",
variant: "mesto",
semanticColor: "textSubdued",
@@ -745,48 +737,49 @@ var library = (() => {
...lastfmProps,
...folderProps,
...collectionProps
- }));
+ }), badge && /* @__PURE__ */ import_react15.default.createElement("div", {
+ className: "badge"
+ }, badge)));
}
var spotify_card_default = SpotifyCard;
// src/components/load_more_card.tsx
- var import_react17 = __toESM(require_react());
+ var import_react16 = __toESM(require_react());
var LoadMoreCard = (props) => {
const { callback } = props;
- return /* @__PURE__ */ import_react17.default.createElement("div", {
+ return /* @__PURE__ */ import_react16.default.createElement("div", {
onClick: callback,
className: "load-more-card main-card-card"
- }, /* @__PURE__ */ import_react17.default.createElement("div", {
+ }, /* @__PURE__ */ import_react16.default.createElement("div", {
className: "svg-placeholder"
- }, /* @__PURE__ */ import_react17.default.createElement("svg", {
+ }, /* @__PURE__ */ import_react16.default.createElement("svg", {
viewBox: "0 8 24 8",
xmlns: "http://www.w3.org/2000/svg"
- }, /* @__PURE__ */ import_react17.default.createElement("circle", {
+ }, /* @__PURE__ */ import_react16.default.createElement("circle", {
cx: "17.5",
cy: "12",
r: "1.5"
- }), /* @__PURE__ */ import_react17.default.createElement("circle", {
+ }), /* @__PURE__ */ import_react16.default.createElement("circle", {
cx: "12",
cy: "12",
r: "1.5"
- }), /* @__PURE__ */ import_react17.default.createElement("circle", {
+ }), /* @__PURE__ */ import_react16.default.createElement("circle", {
cx: "6.5",
cy: "12",
r: "1.5"
- }))), /* @__PURE__ */ import_react17.default.createElement(Spicetify.ReactComponent.TextComponent, {
+ }))), /* @__PURE__ */ import_react16.default.createElement(Spicetify.ReactComponent.TextComponent, {
as: "div",
variant: "violaBold",
semanticColor: "textBase",
- weight: "bold",
- children: "Load More"
- }));
+ weight: "bold"
+ }, "Load More"));
};
var load_more_card_default = LoadMoreCard;
// src/components/add_button.tsx
- var import_react18 = __toESM(require_react());
+ var import_react17 = __toESM(require_react());
function AddIcon() {
- return /* @__PURE__ */ import_react18.default.createElement(Spicetify.ReactComponent.IconComponent, {
+ return /* @__PURE__ */ import_react17.default.createElement(Spicetify.ReactComponent.IconComponent, {
semanticColor: "textSubdued",
dangerouslySetInnerHTML: {
__html: ''
@@ -798,13 +791,13 @@ var library = (() => {
const { ReactComponent } = Spicetify;
const { TooltipWrapper, ButtonTertiary, ContextMenu } = ReactComponent;
const { Menu } = props;
- return /* @__PURE__ */ import_react18.default.createElement(TooltipWrapper, {
+ return /* @__PURE__ */ import_react17.default.createElement(TooltipWrapper, {
label: "Add",
placement: "top"
- }, /* @__PURE__ */ import_react18.default.createElement("span", null, /* @__PURE__ */ import_react18.default.createElement(ContextMenu, {
+ }, /* @__PURE__ */ import_react17.default.createElement("span", null, /* @__PURE__ */ import_react17.default.createElement(ContextMenu, {
trigger: "click",
menu: Menu
- }, /* @__PURE__ */ import_react18.default.createElement(ButtonTertiary, {
+ }, /* @__PURE__ */ import_react17.default.createElement(ButtonTertiary, {
buttonSize: "sm",
"aria-label": "Add",
iconOnly: AddIcon
@@ -812,165 +805,204 @@ var library = (() => {
}
var add_button_default = AddButton;
+ // ../shared/status/useStatus.tsx
+ var import_react19 = __toESM(require_react());
+
+ // ../shared/status/status.tsx
+ var import_react18 = __toESM(require_react());
+ var ErrorIcon = () => {
+ return /* @__PURE__ */ import_react18.default.createElement("svg", {
+ "data-encore-id": "icon",
+ role: "img",
+ "aria-hidden": "true",
+ viewBox: "0 0 24 24",
+ className: "status-icon"
+ }, /* @__PURE__ */ import_react18.default.createElement("path", {
+ d: "M11 18v-2h2v2h-2zm0-4V6h2v8h-2z"
+ }), /* @__PURE__ */ import_react18.default.createElement("path", {
+ d: "M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18zM1 12C1 5.925 5.925 1 12 1s11 4.925 11 11-4.925 11-11 11S1 18.075 1 12z"
+ }));
+ };
+ var LibraryIcon = () => {
+ return /* @__PURE__ */ import_react18.default.createElement("svg", {
+ role: "img",
+ height: "46",
+ width: "46",
+ "aria-hidden": "true",
+ viewBox: "0 0 24 24",
+ "data-encore-id": "icon",
+ className: "status-icon"
+ }, /* @__PURE__ */ import_react18.default.createElement("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"
+ }));
+ };
+ var Status = (props) => {
+ const [isVisible, setIsVisible] = import_react18.default.useState(false);
+ import_react18.default.useEffect(() => {
+ const to = setTimeout(() => {
+ setIsVisible(true);
+ }, 500);
+ return () => clearTimeout(to);
+ }, []);
+ return isVisible ? /* @__PURE__ */ import_react18.default.createElement(import_react18.default.Fragment, null, /* @__PURE__ */ import_react18.default.createElement("div", {
+ className: "loadingWrapper"
+ }, props.icon === "error" ? /* @__PURE__ */ import_react18.default.createElement(ErrorIcon, null) : /* @__PURE__ */ import_react18.default.createElement(LibraryIcon, null), /* @__PURE__ */ import_react18.default.createElement("h1", null, props.heading), /* @__PURE__ */ import_react18.default.createElement("h3", null, props.subheading))) : /* @__PURE__ */ import_react18.default.createElement(import_react18.default.Fragment, null);
+ };
+ var status_default = Status;
+
+ // ../shared/status/useStatus.tsx
+ var useStatus = (status, error) => {
+ if (status === "pending") {
+ return /* @__PURE__ */ import_react19.default.createElement(status_default, {
+ icon: "library",
+ heading: "Loading",
+ subheading: "Please wait, this may take a moment"
+ });
+ }
+ if (status === "error") {
+ return /* @__PURE__ */ import_react19.default.createElement(status_default, {
+ icon: "error",
+ heading: "Error",
+ subheading: error?.message || "An unknown error occurred"
+ });
+ }
+ return null;
+ };
+ var useStatus_default = useStatus;
+
+ // ../shared/types/react_query.ts
+ var ReactQuery = Spicetify.ReactQuery;
+ var useQuery = ReactQuery.useQuery;
+ var QueryClient = ReactQuery.QueryClient;
+ var QueryClientProvider = ReactQuery.QueryClientProvider;
+ var useInfiniteQuery = ReactQuery.useInfiniteQuery;
+
+ // src/components/pin_icon.tsx
+ var import_react20 = __toESM(require_react());
+ var PinIcon = () => /* @__PURE__ */ import_react20.default.createElement(Spicetify.ReactComponent.IconComponent, {
+ semanticColor: "textBase",
+ viewBox: "0 0 16 16",
+ iconSize: 12
+ }, /* @__PURE__ */ import_react20.default.createElement("path", {
+ d: "M8.822.797a2.72 2.72 0 0 1 3.847 0l2.534 2.533a2.72 2.72 0 0 1 0 3.848l-3.678 3.678-1.337 4.988-4.486-4.486L1.28 15.78a.75.75 0 0 1-1.06-1.06l4.422-4.422L.156 5.812l4.987-1.337L8.822.797z"
+ }));
+ var pin_icon_default = PinIcon;
+
// src/pages/albums.tsx
- var sortOptions = [
- { id: "0", name: "Name" },
- { id: "1", name: "Date Added" },
- { id: "2", name: "Artist Name" },
- { id: "6", name: "Recents" }
- ];
- var AddMenu = ({ collection }) => {
+ var AddMenu = () => {
const { MenuItem: MenuItem2, Menu } = Spicetify.ReactComponent;
const { SVGIcons } = Spicetify;
- const createCollection = () => {
- const onSave = (value) => {
- SpicetifyLibrary.CollectionWrapper.createCollection(value, collection);
- };
- Spicetify.PopupModal.display({
- title: "Create Collection",
- content: /* @__PURE__ */ import_react19.default.createElement(text_input_dialog_default, {
- def: "New Collection",
- placeholder: "Collection Name",
- onSave
- })
- });
- };
const addAlbum = () => {
const onSave = (value) => {
- if (collection)
- SpicetifyLibrary.CollectionWrapper.addAlbumToCollection(collection, value);
Spicetify.Platform.LibraryAPI.add({ uris: [value] });
};
Spicetify.PopupModal.display({
title: "Add Album",
- content: /* @__PURE__ */ import_react19.default.createElement(text_input_dialog_default, {
+ content: /* @__PURE__ */ import_react21.default.createElement(text_input_dialog_default, {
def: "",
placeholder: "Album URI",
onSave
})
});
};
- return /* @__PURE__ */ import_react19.default.createElement(Menu, null, /* @__PURE__ */ import_react19.default.createElement(MenuItem2, {
- onClick: createCollection,
- leadingIcon: /* @__PURE__ */ import_react19.default.createElement(leading_icon_default, {
- path: SVGIcons["playlist-folder"]
- })
- }, "Create Collection"), /* @__PURE__ */ import_react19.default.createElement(MenuItem2, {
+ return /* @__PURE__ */ import_react21.default.createElement(Menu, null, /* @__PURE__ */ import_react21.default.createElement(MenuItem2, {
onClick: addAlbum,
- leadingIcon: /* @__PURE__ */ import_react19.default.createElement(leading_icon_default, {
- path: SVGIcons["album"]
+ leadingIcon: /* @__PURE__ */ import_react21.default.createElement(leading_icon_default, {
+ path: SVGIcons.album
})
}, "Add Album"));
};
- var AlbumsPage = ({ configWrapper, collection }) => {
+ var limit = 200;
+ var sortOptions = [
+ { id: "0", name: "Name" },
+ { id: "1", name: "Date Added" },
+ { id: "2", name: "Artist Name" },
+ { id: "6", name: "Recents" }
+ ];
+ var AlbumsPage = ({ configWrapper }) => {
const [dropdown, sortOption] = useDropdownMenu_default(sortOptions, "library:albums");
- const [textFilter, setTextFilter] = import_react19.default.useState("");
- const { useInfiniteQuery } = Spicetify.ReactQuery;
- const limit = 200;
- const fetchRootlist = async ({ pageParam }) => {
- const collections = await SpicetifyLibrary.CollectionWrapper.getCollectionItems({
- collectionUri: collection,
- textFilter,
+ const [textFilter, setTextFilter] = import_react21.default.useState("");
+ const fetchAlbums = async ({ pageParam }) => {
+ const res = await Spicetify.Platform.LibraryAPI.getContents({
+ filters: ["0"],
sortOrder: sortOption.id,
- limit,
+ textFilter,
offset: pageParam,
- rootlist: true
+ limit
});
- return collections;
+ if (!res.items?.length)
+ throw new Error("No albums found");
+ return res;
};
- const { data, status, hasNextPage, fetchNextPage, refetch } = useInfiniteQuery({
- queryKey: ["library:albums", sortOption.id, textFilter, collection],
- queryFn: fetchRootlist,
+ const { data, status, error, hasNextPage, fetchNextPage, refetch } = useInfiniteQuery({
+ queryKey: ["library:albums", sortOption.id, textFilter],
+ queryFn: fetchAlbums,
initialPageParam: 0,
- getNextPageParam: (lastPage, _allPages, lastPageParam) => {
- return lastPage.totalLength > lastPageParam + limit ? lastPageParam + limit : void 0;
- },
- structuralSharing: false
+ getNextPageParam: (lastPage) => {
+ const current = lastPage.offset + limit;
+ if (lastPage.totalLength > current)
+ return current;
+ }
});
- import_react19.default.useEffect(() => {
- const onUpdate = (e) => {
- refetch();
+ (0, import_react21.useEffect)(() => {
+ const update = (e) => {
+ if (e.data.list === "albums")
+ refetch();
};
- Spicetify.Platform.LibraryAPI.getEvents()._emitter.addListener("update", onUpdate);
- SpicetifyLibrary.CollectionWrapper.addEventListener("update", onUpdate);
+ Spicetify.Platform.LibraryAPI.getEvents()._emitter.addListener("update", update, {});
return () => {
- Spicetify.Platform.LibraryAPI.getEvents()._emitter.removeListener("update", onUpdate);
- SpicetifyLibrary.CollectionWrapper.removeEventListener("update", onUpdate);
+ Spicetify.Platform.LibraryAPI.getEvents()._emitter.removeListener("update", update);
};
- }, []);
+ }, [refetch]);
+ const Status2 = useStatus_default(status, error);
const props = {
- title: data?.pages[0].openedCollection || "Albums",
+ title: "Albums",
headerEls: [
- /* @__PURE__ */ import_react19.default.createElement(add_button_default, {
- Menu: /* @__PURE__ */ import_react19.default.createElement(AddMenu, {
- collection
- })
+ /* @__PURE__ */ import_react21.default.createElement(add_button_default, {
+ Menu: /* @__PURE__ */ import_react21.default.createElement(AddMenu, null)
}),
dropdown,
- /* @__PURE__ */ import_react19.default.createElement(searchbar_default, {
+ /* @__PURE__ */ import_react21.default.createElement(searchbar_default, {
setSearch: setTextFilter,
placeholder: "Albums"
}),
- /* @__PURE__ */ import_react19.default.createElement(settings_button_default, {
+ /* @__PURE__ */ import_react21.default.createElement(settings_button_default, {
configWrapper
})
]
};
- if (status === "pending") {
- return /* @__PURE__ */ import_react19.default.createElement(page_container_default, {
+ if (Status2)
+ return /* @__PURE__ */ import_react21.default.createElement(page_container_default, {
...props
- }, /* @__PURE__ */ import_react19.default.createElement(status_default, {
- icon: "library",
- heading: "Loading",
- subheading: "Fetching your albums"
- }));
- } else if (status === "error") {
- return /* @__PURE__ */ import_react19.default.createElement(page_container_default, {
- ...props
- }, /* @__PURE__ */ import_react19.default.createElement(status_default, {
- icon: "error",
- heading: "Error",
- subheading: "Failed to load your albums"
- }));
- } else if (!data.pages[0].items.length) {
- return /* @__PURE__ */ import_react19.default.createElement(page_container_default, {
- ...props
- }, /* @__PURE__ */ import_react19.default.createElement(status_default, {
- icon: "library",
- heading: "Nothing Here",
- subheading: "You don't have any albums saved"
- }));
- }
- const rootlistItems = data.pages.map((page) => page.items).flat();
- const rootlistCards = rootlistItems.map((item) => {
- const isAlbum = item.type === "album";
- return /* @__PURE__ */ import_react19.default.createElement(spotify_card_default, {
+ }, Status2);
+ const contents = data;
+ const albums = contents.pages.flatMap((page) => page.items);
+ const albumCards = albums.map((item) => {
+ return /* @__PURE__ */ import_react21.default.createElement(spotify_card_default, {
+ provider: "spotify",
type: item.type,
uri: item.uri,
header: item.name,
- subheader: isAlbum ? item.artists?.[0]?.name : "Collection",
- imageUrl: isAlbum ? item.images?.[0]?.url : item.imgUrl,
- artistUri: isAlbum ? item.artists?.[0]?.uri : void 0
+ subheader: item.artists[0].name,
+ imageUrl: item.images?.[0]?.url,
+ artistUri: item.artists[0].uri,
+ badge: item.pinned ? /* @__PURE__ */ import_react21.default.createElement(pin_icon_default, null) : void 0
});
});
if (hasNextPage)
- rootlistCards.push(/* @__PURE__ */ import_react19.default.createElement(load_more_card_default, {
+ albumCards.push(/* @__PURE__ */ import_react21.default.createElement(load_more_card_default, {
callback: fetchNextPage
}));
- return /* @__PURE__ */ import_react19.default.createElement(page_container_default, {
+ return /* @__PURE__ */ import_react21.default.createElement(page_container_default, {
...props
- }, /* @__PURE__ */ import_react19.default.createElement("div", {
- className: `main-gridContainer-gridContainer grid`
- }, rootlistCards));
+ }, /* @__PURE__ */ import_react21.default.createElement("div", {
+ className: "main-gridContainer-gridContainer grid"
+ }, albumCards));
};
var albums_default = AlbumsPage;
// src/pages/artists.tsx
- var import_react20 = __toESM(require_react());
- var sortOptions2 = [
- { id: "0", name: "Name" },
- { id: "1", name: "Date Added" }
- ];
+ var import_react22 = __toESM(require_react());
var AddMenu2 = () => {
const { MenuItem: MenuItem2, Menu } = Spicetify.ReactComponent;
const { SVGIcons } = Spicetify;
@@ -980,117 +1012,106 @@ var library = (() => {
};
Spicetify.PopupModal.display({
title: "Add Artist",
- content: /* @__PURE__ */ import_react20.default.createElement(text_input_dialog_default, {
+ content: /* @__PURE__ */ import_react22.default.createElement(text_input_dialog_default, {
def: "",
placeholder: "Artist URI",
onSave
})
});
};
- return /* @__PURE__ */ import_react20.default.createElement(Menu, null, /* @__PURE__ */ import_react20.default.createElement(MenuItem2, {
+ return /* @__PURE__ */ import_react22.default.createElement(Menu, null, /* @__PURE__ */ import_react22.default.createElement(MenuItem2, {
onClick: addAlbum,
- leadingIcon: /* @__PURE__ */ import_react20.default.createElement(leading_icon_default, {
- path: SVGIcons["artist"]
+ leadingIcon: /* @__PURE__ */ import_react22.default.createElement(leading_icon_default, {
+ path: SVGIcons.artist
})
}, "Add Artist"));
};
+ var limit2 = 200;
+ var sortOptions2 = [
+ { id: "0", name: "Name" },
+ { id: "1", name: "Date Added" }
+ ];
var ArtistsPage = ({ configWrapper }) => {
const [dropdown, sortOption] = useDropdownMenu_default(sortOptions2, "library:artists");
- const [textFilter, setTextFilter] = import_react20.default.useState("");
- const { useInfiniteQuery } = Spicetify.ReactQuery;
- const limit = 200;
+ const [textFilter, setTextFilter] = import_react22.default.useState("");
const fetchArtists = async ({ pageParam }) => {
const res = await Spicetify.Platform.LibraryAPI.getContents({
filters: ["1"],
sortOrder: sortOption.id,
textFilter,
offset: pageParam,
- limit
+ limit: limit2
});
+ if (!res.items?.length)
+ throw new Error("No artists found");
return res;
};
- const { data, status, hasNextPage, fetchNextPage, refetch } = useInfiniteQuery({
+ const { data, status, error, hasNextPage, fetchNextPage, refetch } = useInfiniteQuery({
queryKey: ["library:artists", sortOption.id, textFilter],
queryFn: fetchArtists,
initialPageParam: 0,
- getNextPageParam: (lastPage, _allPages, lastPageParam) => {
- return lastPage.totalLength > lastPageParam + limit ? lastPageParam + limit : void 0;
+ getNextPageParam: (lastPage) => {
+ const current = lastPage.offset + limit2;
+ if (lastPage.totalLength > current)
+ return current;
}
});
- import_react20.default.useEffect(() => {
- const onUpdate = (e) => refetch();
- Spicetify.Platform.LibraryAPI.getEvents()._emitter.addListener("update", onUpdate);
- return () => Spicetify.Platform.LibraryAPI.getEvents()._emitter.removeListener("update", onUpdate);
- }, []);
+ (0, import_react22.useEffect)(() => {
+ const update = (e) => {
+ if (e.data.list === "artists")
+ refetch();
+ };
+ Spicetify.Platform.LibraryAPI.getEvents()._emitter.addListener("update", update, {});
+ return () => {
+ Spicetify.Platform.LibraryAPI.getEvents()._emitter.removeListener("update", update);
+ };
+ }, [refetch]);
+ const Status2 = useStatus_default(status, error);
const props = {
title: "Artists",
headerEls: [
- /* @__PURE__ */ import_react20.default.createElement(add_button_default, {
- Menu: /* @__PURE__ */ import_react20.default.createElement(AddMenu2, null)
+ /* @__PURE__ */ import_react22.default.createElement(add_button_default, {
+ Menu: /* @__PURE__ */ import_react22.default.createElement(AddMenu2, null)
}),
dropdown,
- /* @__PURE__ */ import_react20.default.createElement(searchbar_default, {
+ /* @__PURE__ */ import_react22.default.createElement(searchbar_default, {
setSearch: setTextFilter,
placeholder: "Artists"
}),
- /* @__PURE__ */ import_react20.default.createElement(settings_button_default, {
+ /* @__PURE__ */ import_react22.default.createElement(settings_button_default, {
configWrapper
})
]
};
- if (status === "pending") {
- return /* @__PURE__ */ import_react20.default.createElement(page_container_default, {
+ if (Status2)
+ return /* @__PURE__ */ import_react22.default.createElement(page_container_default, {
...props
- }, /* @__PURE__ */ import_react20.default.createElement(status_default, {
- icon: "library",
- heading: "Loading",
- subheading: "Fetching your artists"
- }));
- } else if (status === "error") {
- return /* @__PURE__ */ import_react20.default.createElement(page_container_default, {
- ...props
- }, /* @__PURE__ */ import_react20.default.createElement(status_default, {
- icon: "error",
- heading: "Error",
- subheading: "Failed to load your artists"
- }));
- } else if (!data.pages[0].items.length) {
- return /* @__PURE__ */ import_react20.default.createElement(page_container_default, {
- ...props
- }, /* @__PURE__ */ import_react20.default.createElement(status_default, {
- icon: "library",
- heading: "Nothing Here",
- subheading: "You don't have any artists saved"
- }));
- }
- const artists = data.pages.map((page) => page.items).flat();
- const artistCards = artists.map((artist) => {
- return /* @__PURE__ */ import_react20.default.createElement(spotify_card_default, {
- type: "artist",
- uri: artist.uri,
- header: artist.name,
- subheader: "Artist",
- imageUrl: artist.images?.[0]?.url || ""
- });
- });
+ }, Status2);
+ const contents = data;
+ const artists = contents.pages.flatMap((page) => page.items);
+ const artistCards = artists.map((artist) => /* @__PURE__ */ import_react22.default.createElement(spotify_card_default, {
+ provider: "spotify",
+ type: "artist",
+ uri: artist.uri,
+ header: artist.name,
+ subheader: "",
+ imageUrl: artist.images?.at(0)?.url,
+ badge: artist.pinned ? /* @__PURE__ */ import_react22.default.createElement(pin_icon_default, null) : void 0
+ }));
if (hasNextPage)
- artistCards.push(/* @__PURE__ */ import_react20.default.createElement(load_more_card_default, {
+ artistCards.push(/* @__PURE__ */ import_react22.default.createElement(load_more_card_default, {
callback: fetchNextPage
}));
- return /* @__PURE__ */ import_react20.default.createElement(page_container_default, {
+ return /* @__PURE__ */ import_react22.default.createElement(page_container_default, {
...props
- }, /* @__PURE__ */ import_react20.default.createElement("div", {
- className: `main-gridContainer-gridContainer grid`
+ }, /* @__PURE__ */ import_react22.default.createElement("div", {
+ className: "main-gridContainer-gridContainer grid"
}, artistCards));
};
var artists_default = ArtistsPage;
// src/pages/shows.tsx
- var import_react21 = __toESM(require_react());
- var sortOptions3 = [
- { id: "0", name: "Name" },
- { id: "1", name: "Date Added" }
- ];
+ var import_react23 = __toESM(require_react());
var AddMenu3 = () => {
const { MenuItem: MenuItem2, Menu } = Spicetify.ReactComponent;
const { SVGIcons } = Spicetify;
@@ -1100,113 +1121,150 @@ var library = (() => {
};
Spicetify.PopupModal.display({
title: "Add Show",
- content: /* @__PURE__ */ import_react21.default.createElement(text_input_dialog_default, {
+ content: /* @__PURE__ */ import_react23.default.createElement(text_input_dialog_default, {
def: "",
placeholder: "Show URI",
onSave
})
});
};
- return /* @__PURE__ */ import_react21.default.createElement(Menu, null, /* @__PURE__ */ import_react21.default.createElement(MenuItem2, {
+ return /* @__PURE__ */ import_react23.default.createElement(Menu, null, /* @__PURE__ */ import_react23.default.createElement(MenuItem2, {
onClick: addAlbum,
- leadingIcon: /* @__PURE__ */ import_react21.default.createElement(leading_icon_default, {
- path: SVGIcons["podcasts"]
+ leadingIcon: /* @__PURE__ */ import_react23.default.createElement(leading_icon_default, {
+ path: SVGIcons.podcasts
})
}, "Add Show"));
};
+ var limit3 = 200;
+ var sortOptions3 = [
+ { id: "0", name: "Name" },
+ { id: "1", name: "Date Added" }
+ ];
var ShowsPage = ({ configWrapper }) => {
const [dropdown, sortOption] = useDropdownMenu_default(sortOptions3, "library:shows");
- const [textFilter, setTextFilter] = import_react21.default.useState("");
- const { useInfiniteQuery } = Spicetify.ReactQuery;
- const limit = 200;
+ const [textFilter, setTextFilter] = import_react23.default.useState("");
const fetchShows = async ({ pageParam }) => {
const res = await Spicetify.Platform.LibraryAPI.getContents({
filters: ["3"],
sortOrder: sortOption.id,
textFilter,
offset: pageParam,
- limit
+ limit: limit3
});
+ if (!res.items?.length)
+ throw new Error("No shows found");
return res;
};
- const { data, status, hasNextPage, fetchNextPage, refetch } = useInfiniteQuery({
+ const { data, status, error, hasNextPage, fetchNextPage, refetch } = useInfiniteQuery({
queryKey: ["library:shows", sortOption.id, textFilter],
queryFn: fetchShows,
initialPageParam: 0,
- getNextPageParam: (lastPage, _allPages, lastPageParam) => {
- return lastPage.totalLength > lastPageParam + limit ? lastPageParam + limit : void 0;
+ getNextPageParam: (lastPage) => {
+ const current = lastPage.offset + limit3;
+ if (lastPage.totalLength > current)
+ return current;
}
});
- import_react21.default.useEffect(() => {
- const onUpdate = (e) => refetch();
- Spicetify.Platform.LibraryAPI.getEvents()._emitter.addListener("update", onUpdate);
- return () => Spicetify.Platform.LibraryAPI.getEvents()._emitter.removeListener("update", onUpdate);
- }, []);
+ (0, import_react23.useEffect)(() => {
+ const update = (e) => {
+ if (e.data.list === "shows")
+ refetch();
+ };
+ Spicetify.Platform.LibraryAPI.getEvents()._emitter.addListener("update", update, {});
+ return () => {
+ Spicetify.Platform.LibraryAPI.getEvents()._emitter.removeListener("update", update);
+ };
+ }, [refetch]);
+ const Status2 = useStatus_default(status, error);
const props = {
title: "Shows",
headerEls: [
- /* @__PURE__ */ import_react21.default.createElement(add_button_default, {
- Menu: /* @__PURE__ */ import_react21.default.createElement(AddMenu3, null)
+ /* @__PURE__ */ import_react23.default.createElement(add_button_default, {
+ Menu: /* @__PURE__ */ import_react23.default.createElement(AddMenu3, null)
}),
dropdown,
- /* @__PURE__ */ import_react21.default.createElement(searchbar_default, {
+ /* @__PURE__ */ import_react23.default.createElement(searchbar_default, {
setSearch: setTextFilter,
placeholder: "Shows"
}),
- /* @__PURE__ */ import_react21.default.createElement(settings_button_default, {
+ /* @__PURE__ */ import_react23.default.createElement(settings_button_default, {
configWrapper
})
]
};
- if (status === "pending") {
- return /* @__PURE__ */ import_react21.default.createElement(page_container_default, {
+ if (Status2)
+ return /* @__PURE__ */ import_react23.default.createElement(page_container_default, {
...props
- }, /* @__PURE__ */ import_react21.default.createElement(status_default, {
- icon: "library",
- heading: "Loading",
- subheading: "Fetching your shows"
- }));
- } else if (status === "error") {
- return /* @__PURE__ */ import_react21.default.createElement(page_container_default, {
- ...props
- }, /* @__PURE__ */ import_react21.default.createElement(status_default, {
- icon: "error",
- heading: "Error",
- subheading: "Failed to load your shows"
- }));
- } else if (!data.pages[0].items.length) {
- return /* @__PURE__ */ import_react21.default.createElement(page_container_default, {
- ...props
- }, /* @__PURE__ */ import_react21.default.createElement(status_default, {
- icon: "library",
- heading: "Nothing Here",
- subheading: "You don't have any shows saved"
- }));
- }
- const shows = data.pages.map((page) => page.items).flat();
- const showCards = shows.map((show) => {
- return /* @__PURE__ */ import_react21.default.createElement(spotify_card_default, {
- type: "show",
- uri: show.uri,
- header: show.name,
- subheader: show.publisher,
- imageUrl: show.images?.[0]?.url || ""
- });
- });
+ }, Status2);
+ const contents = data;
+ const shows = contents.pages.flatMap((page) => page.items);
+ const showCards = shows.map((show) => /* @__PURE__ */ import_react23.default.createElement(spotify_card_default, {
+ provider: "spotify",
+ type: "show",
+ uri: show.uri,
+ header: show.name,
+ subheader: show.publisher,
+ imageUrl: show.images?.[0]?.url,
+ badge: show.pinned ? /* @__PURE__ */ import_react23.default.createElement(pin_icon_default, null) : void 0
+ }));
if (hasNextPage)
- showCards.push(/* @__PURE__ */ import_react21.default.createElement(load_more_card_default, {
+ showCards.push(/* @__PURE__ */ import_react23.default.createElement(load_more_card_default, {
callback: fetchNextPage
}));
- return /* @__PURE__ */ import_react21.default.createElement(page_container_default, {
+ return /* @__PURE__ */ import_react23.default.createElement(page_container_default, {
...props
- }, /* @__PURE__ */ import_react21.default.createElement("div", {
- className: `main-gridContainer-gridContainer grid`
+ }, /* @__PURE__ */ import_react23.default.createElement("div", {
+ className: "main-gridContainer-gridContainer grid"
}, showCards));
};
var shows_default = ShowsPage;
// src/pages/playlists.tsx
- var import_react22 = __toESM(require_react());
+ var import_react24 = __toESM(require_react());
+ var AddMenu4 = ({ folder }) => {
+ const { MenuItem: MenuItem2, Menu } = Spicetify.ReactComponent;
+ const { RootlistAPI } = Spicetify.Platform;
+ const { SVGIcons } = Spicetify;
+ const insertLocation = folder ? { uri: folder } : "start";
+ const createFolder = () => {
+ const onSave = (value) => {
+ RootlistAPI.createFolder(value || "New Folder", { after: insertLocation });
+ };
+ Spicetify.PopupModal.display({
+ title: "Create Folder",
+ content: /* @__PURE__ */ import_react24.default.createElement(text_input_dialog_default, {
+ def: "New Folder",
+ placeholder: "Folder Name",
+ onSave
+ })
+ });
+ };
+ const createPlaylist = () => {
+ const onSave = (value) => {
+ RootlistAPI.createPlaylist(value || "New Playlist", { after: insertLocation });
+ };
+ Spicetify.PopupModal.display({
+ title: "Create Playlist",
+ content: /* @__PURE__ */ import_react24.default.createElement(text_input_dialog_default, {
+ def: "New Playlist",
+ placeholder: "Playlist Name",
+ onSave
+ })
+ });
+ };
+ return /* @__PURE__ */ import_react24.default.createElement(Menu, null, /* @__PURE__ */ import_react24.default.createElement(MenuItem2, {
+ onClick: createFolder,
+ leadingIcon: /* @__PURE__ */ import_react24.default.createElement(leading_icon_default, {
+ path: SVGIcons["playlist-folder"]
+ })
+ }, "Create Folder"), /* @__PURE__ */ import_react24.default.createElement(MenuItem2, {
+ onClick: createPlaylist,
+ leadingIcon: /* @__PURE__ */ import_react24.default.createElement(leading_icon_default, {
+ path: SVGIcons.playlist
+ })
+ }, "Create Playlist"));
+ };
+ var limit4 = 200;
var dropdownOptions = [
{ id: "0", name: "Name" },
{ id: "1", name: "Date Added" },
@@ -1220,56 +1278,16 @@ var library = (() => {
{ id: "102", name: "By You" },
{ id: "103", name: "By Spotify" }
];
- var AddMenu4 = ({ folder }) => {
- const { MenuItem: MenuItem2, Menu } = Spicetify.ReactComponent;
- const { RootlistAPI } = Spicetify.Platform;
- const { SVGIcons } = Spicetify;
- const insertLocation = folder ? { uri: folder } : "start";
- const createFolder = () => {
- const onSave = (value) => {
- RootlistAPI.createFolder(value || "New Folder", { after: insertLocation });
- };
- Spicetify.PopupModal.display({
- title: "Create Folder",
- content: /* @__PURE__ */ import_react22.default.createElement(text_input_dialog_default, {
- def: "New Folder",
- placeholder: "Folder Name",
- onSave
- })
- });
- };
- const createPlaylist = () => {
- const onSave = (value) => {
- RootlistAPI.createPlaylist(value || "New Playlist", { after: insertLocation });
- };
- Spicetify.PopupModal.display({
- title: "Create Playlist",
- content: /* @__PURE__ */ import_react22.default.createElement(text_input_dialog_default, {
- def: "New Playlist",
- placeholder: "Playlist Name",
- onSave
- })
- });
- };
- return /* @__PURE__ */ import_react22.default.createElement(Menu, null, /* @__PURE__ */ import_react22.default.createElement(MenuItem2, {
- onClick: createFolder,
- leadingIcon: /* @__PURE__ */ import_react22.default.createElement(leading_icon_default, {
- path: SVGIcons["playlist-folder"]
- })
- }, "Create Folder"), /* @__PURE__ */ import_react22.default.createElement(MenuItem2, {
- onClick: createPlaylist,
- leadingIcon: /* @__PURE__ */ import_react22.default.createElement(leading_icon_default, {
- path: SVGIcons["playlist"]
- })
- }, "Create Playlist"));
- };
+ var flattenOptions = [
+ { id: "false", name: "Unflattened" },
+ { id: "true", name: "Flattened" }
+ ];
var PlaylistsPage = ({ folder, configWrapper }) => {
const [sortDropdown, sortOption] = useDropdownMenu_default(dropdownOptions, "library:playlists-sort");
- const [filterDropdown, filterOption, setFilterOption, setAvailableOptions] = useDropdownMenu_default(filterOptions);
- const [textFilter, setTextFilter] = import_react22.default.useState("");
- const [images, setImages] = import_react22.default.useState({ ...SpicetifyLibrary.FolderImageWrapper.getFolderImages() });
- const { useInfiniteQuery } = Spicetify.ReactQuery;
- const limit = 200;
+ const [filterDropdown, filterOption] = useDropdownMenu_default(filterOptions);
+ const [flattenDropdown, flattenOption] = useDropdownMenu_default(flattenOptions);
+ const [textFilter, setTextFilter] = import_react24.default.useState("");
+ const [images, setImages] = import_react24.default.useState({ ...FolderImageWrapper.getFolderImages() });
const fetchRootlist = async ({ pageParam }) => {
const filters = filterOption.id === "all" ? ["2"] : ["2", filterOption.id];
const res = await Spicetify.Platform.LibraryAPI.getContents({
@@ -1278,99 +1296,229 @@ var library = (() => {
folderUri: folder,
textFilter,
offset: pageParam,
- limit
+ limit: limit4,
+ flattenTree: JSON.parse(flattenOption.id)
});
+ if (!res.items?.length)
+ throw new Error("No playlists found");
return res;
};
- const { data, status, hasNextPage, fetchNextPage, refetch } = useInfiniteQuery({
- queryKey: ["library:playlists", sortOption.id, filterOption.id, textFilter, folder],
+ const { data, status, error, hasNextPage, fetchNextPage, refetch } = useInfiniteQuery({
+ queryKey: ["library:playlists", sortOption.id, filterOption.id, flattenOption.id, textFilter, folder],
queryFn: fetchRootlist,
initialPageParam: 0,
- getNextPageParam: (lastPage, _allPages, lastPageParam) => {
- return lastPage.totalLength > lastPageParam + limit ? lastPageParam + limit : void 0;
- }
+ getNextPageParam: (lastPage) => {
+ const current = lastPage.offset + limit4;
+ if (lastPage.totalLength > current)
+ return current;
+ },
+ retry: false
});
- import_react22.default.useEffect(() => {
- const onUpdate = (e) => refetch();
- const onImageUpdate = (e) => setImages({ ...e.detail });
- Spicetify.Platform.RootlistAPI.getEvents().addListener("update", onUpdate);
- SpicetifyLibrary.FolderImageWrapper.addEventListener("update", onImageUpdate);
+ (0, import_react24.useEffect)(() => {
+ const update = (e) => refetch();
+ const updateImages = (e) => "detail" in e && setImages({ ...e.detail });
+ FolderImageWrapper.addEventListener("update", updateImages);
+ Spicetify.Platform.RootlistAPI.getEvents()._emitter.addListener("update", update, {});
return () => {
- Spicetify.Platform.RootlistAPI.getEvents().removeListener("update", onUpdate);
- SpicetifyLibrary.FolderImageWrapper.removeEventListener("update", onImageUpdate);
+ FolderImageWrapper.removeEventListener("update", updateImages);
+ Spicetify.Platform.RootlistAPI.getEvents()._emitter.removeListener("update", update);
};
- }, []);
+ }, [refetch]);
+ const Status2 = useStatus_default(status, error);
const props = {
title: data?.pages[0].openedFolderName || "Playlists",
headerEls: [
- /* @__PURE__ */ import_react22.default.createElement(add_button_default, {
- Menu: /* @__PURE__ */ import_react22.default.createElement(AddMenu4, {
+ /* @__PURE__ */ import_react24.default.createElement(add_button_default, {
+ Menu: /* @__PURE__ */ import_react24.default.createElement(AddMenu4, {
folder
})
}),
sortDropdown,
filterDropdown,
- /* @__PURE__ */ import_react22.default.createElement(searchbar_default, {
+ flattenDropdown,
+ /* @__PURE__ */ import_react24.default.createElement(searchbar_default, {
setSearch: setTextFilter,
placeholder: "Playlists"
}),
- /* @__PURE__ */ import_react22.default.createElement(settings_button_default, {
+ /* @__PURE__ */ import_react24.default.createElement(settings_button_default, {
configWrapper
})
]
};
- if (status === "pending") {
- return /* @__PURE__ */ import_react22.default.createElement(page_container_default, {
+ if (Status2)
+ return /* @__PURE__ */ import_react24.default.createElement(page_container_default, {
...props
- }, /* @__PURE__ */ import_react22.default.createElement(status_default, {
- icon: "library",
- heading: "Loading",
- subheading: "Fetching your playlists"
- }));
- } else if (status === "error") {
- return /* @__PURE__ */ import_react22.default.createElement(page_container_default, {
- ...props
- }, /* @__PURE__ */ import_react22.default.createElement(status_default, {
- icon: "error",
- heading: "Error",
- subheading: "Failed to load your playlists"
- }));
- } else if (!data.pages[0].items.length) {
- return /* @__PURE__ */ import_react22.default.createElement(page_container_default, {
- ...props
- }, /* @__PURE__ */ import_react22.default.createElement(status_default, {
- icon: "library",
- heading: "Nothing Here",
- subheading: "You don't have any playlists saved"
- }));
- }
- const rootlistItems = data.pages.map((page) => page.items).flat();
- const rootlistCards = rootlistItems.map((playlist) => {
- return /* @__PURE__ */ import_react22.default.createElement(spotify_card_default, {
- type: playlist.type,
- uri: playlist.uri,
- header: playlist.name,
- subheader: playlist.owner?.name || "Folder",
- imageUrl: playlist.images?.[0]?.url || images[playlist.uri] || ""
- });
- });
+ }, Status2);
+ const contents = data;
+ const items = contents.pages.flatMap((page) => page.items);
+ const rootlistCards = items.map((item) => /* @__PURE__ */ import_react24.default.createElement(spotify_card_default, {
+ provider: "spotify",
+ type: item.type,
+ uri: item.uri,
+ header: item.name,
+ subheader: item.type === "playlist" ? item.owner.name : `${item.numberOfPlaylists} Playlists${item.numberOfFolders ? ` \u2022 ${item.numberOfFolders} Folders` : ""}`,
+ imageUrl: item.images?.[0]?.url || images[item.uri],
+ badge: item.pinned ? /* @__PURE__ */ import_react24.default.createElement(pin_icon_default, null) : void 0
+ }));
if (hasNextPage)
- rootlistCards.push(/* @__PURE__ */ import_react22.default.createElement(load_more_card_default, {
+ rootlistCards.push(/* @__PURE__ */ import_react24.default.createElement(load_more_card_default, {
callback: fetchNextPage
}));
- return /* @__PURE__ */ import_react22.default.createElement(page_container_default, {
+ return /* @__PURE__ */ import_react24.default.createElement(page_container_default, {
...props
- }, /* @__PURE__ */ import_react22.default.createElement("div", {
- className: `main-gridContainer-gridContainer grid`
+ }, /* @__PURE__ */ import_react24.default.createElement("div", {
+ className: "main-gridContainer-gridContainer grid"
}, rootlistCards));
};
var playlists_default = PlaylistsPage;
// package.json
- var version = "0.1.1";
+ var version = "1.0.0";
+
+ // src/pages/collections.tsx
+ var import_react25 = __toESM(require_react());
+ var AddMenu5 = ({ collection }) => {
+ const { MenuItem: MenuItem2, Menu } = Spicetify.ReactComponent;
+ const { RootlistAPI } = Spicetify.Platform;
+ const { SVGIcons } = Spicetify;
+ const createCollection = () => {
+ const onSave = (value) => {
+ CollectionsWrapper.createCollection(value || "New Collection", collection);
+ };
+ Spicetify.PopupModal.display({
+ title: "Create Collection",
+ content: /* @__PURE__ */ import_react25.default.createElement(text_input_dialog_default, {
+ def: "New Collection",
+ placeholder: "Collection Name",
+ onSave
+ })
+ });
+ };
+ const createDiscogCollection = () => {
+ const onSave = (value) => {
+ CollectionsWrapper.createCollectionFromDiscog(value);
+ };
+ Spicetify.PopupModal.display({
+ title: "Create Discog Collection",
+ content: /* @__PURE__ */ import_react25.default.createElement(text_input_dialog_default, {
+ def: "",
+ placeholder: "Artist URI",
+ onSave
+ })
+ });
+ };
+ const addAlbum = () => {
+ if (!collection)
+ return;
+ const onSave = (value) => {
+ CollectionsWrapper.addAlbumToCollection(collection, value);
+ };
+ Spicetify.PopupModal.display({
+ title: "Add Album",
+ content: /* @__PURE__ */ import_react25.default.createElement(text_input_dialog_default, {
+ def: "",
+ placeholder: "Album URI",
+ onSave
+ })
+ });
+ };
+ return /* @__PURE__ */ import_react25.default.createElement(Menu, null, /* @__PURE__ */ import_react25.default.createElement(MenuItem2, {
+ onClick: createCollection,
+ leadingIcon: /* @__PURE__ */ import_react25.default.createElement(leading_icon_default, {
+ path: SVGIcons["playlist-folder"]
+ })
+ }, "Create Collection"), /* @__PURE__ */ import_react25.default.createElement(MenuItem2, {
+ onClick: createDiscogCollection,
+ leadingIcon: /* @__PURE__ */ import_react25.default.createElement(leading_icon_default, {
+ path: SVGIcons.artist
+ })
+ }, "Create Discog Collection"), collection && /* @__PURE__ */ import_react25.default.createElement(MenuItem2, {
+ onClick: addAlbum,
+ leadingIcon: /* @__PURE__ */ import_react25.default.createElement(leading_icon_default, {
+ path: SVGIcons.album
+ })
+ }, "Add Album"));
+ };
+ var limit5 = 200;
+ var CollectionsPage = ({ collection, configWrapper }) => {
+ const [textFilter, setTextFilter] = import_react25.default.useState("");
+ const fetchRootlist = async ({ pageParam }) => {
+ const res = await CollectionsWrapper.getContents({
+ collectionUri: collection,
+ textFilter,
+ offset: pageParam,
+ limit: limit5
+ });
+ if (!res.items.length)
+ throw new Error("No collections found");
+ return res;
+ };
+ const { data, status, error, hasNextPage, fetchNextPage, refetch } = useInfiniteQuery({
+ queryKey: ["library:collections", textFilter, collection],
+ queryFn: fetchRootlist,
+ initialPageParam: 0,
+ getNextPageParam: (lastPage) => {
+ const current = lastPage.offset + limit5;
+ if (lastPage.totalLength > current)
+ return current;
+ },
+ retry: false,
+ structuralSharing: false
+ });
+ (0, import_react25.useEffect)(() => {
+ const update = (e) => {
+ refetch();
+ };
+ CollectionsWrapper.addEventListener("update", update);
+ return () => {
+ CollectionsWrapper.removeEventListener("update", update);
+ };
+ }, [refetch]);
+ const Status2 = useStatus_default(status, error);
+ const props = {
+ title: data?.pages[0].openedCollectionName || "Collections",
+ headerEls: [
+ /* @__PURE__ */ import_react25.default.createElement(add_button_default, {
+ Menu: /* @__PURE__ */ import_react25.default.createElement(AddMenu5, {
+ collection
+ })
+ }),
+ /* @__PURE__ */ import_react25.default.createElement(searchbar_default, {
+ setSearch: setTextFilter,
+ placeholder: "Collections"
+ }),
+ /* @__PURE__ */ import_react25.default.createElement(settings_button_default, {
+ configWrapper
+ })
+ ]
+ };
+ if (Status2)
+ return /* @__PURE__ */ import_react25.default.createElement(page_container_default, {
+ ...props
+ }, Status2);
+ const contents = data;
+ const items = contents.pages.flatMap((page) => page.items);
+ const rootlistCards = items.map((item) => /* @__PURE__ */ import_react25.default.createElement(spotify_card_default, {
+ provider: "spotify",
+ type: item.type,
+ uri: item.uri,
+ header: item.name,
+ subheader: item.type === "collection" ? `${item.items.length} Albums` : item.artists?.[0]?.name,
+ imageUrl: item.type === "collection" ? item.image : item.images?.[0]?.url
+ }));
+ if (hasNextPage)
+ rootlistCards.push(/* @__PURE__ */ import_react25.default.createElement(load_more_card_default, {
+ callback: fetchNextPage
+ }));
+ return /* @__PURE__ */ import_react25.default.createElement(page_container_default, {
+ ...props
+ }, /* @__PURE__ */ import_react25.default.createElement("div", {
+ className: "main-gridContainer-gridContainer grid"
+ }, rootlistCards));
+ };
+ var collections_default = CollectionsPage;
// src/app.tsx
- var tabPages = ["Playlists", "Albums", "Artists", "Shows"];
var checkForUpdates = (setNewUpdate) => {
fetch("https://api.github.com/repos/harbassan/spicetify-apps/releases").then((res) => res.json()).then(
(result) => {
@@ -1384,40 +1532,46 @@ var library = (() => {
};
var NavbarContainer = ({ configWrapper }) => {
const pages = {
- ["Artists"]: /* @__PURE__ */ import_react23.default.createElement(artists_default, {
+ ["Artists"]: /* @__PURE__ */ import_react26.default.createElement(artists_default, {
configWrapper
}),
- ["Albums"]: /* @__PURE__ */ import_react23.default.createElement(albums_default, {
+ ["Albums"]: /* @__PURE__ */ import_react26.default.createElement(albums_default, {
configWrapper
}),
- ["Shows"]: /* @__PURE__ */ import_react23.default.createElement(shows_default, {
+ ["Shows"]: /* @__PURE__ */ import_react26.default.createElement(shows_default, {
configWrapper
}),
- ["Playlists"]: /* @__PURE__ */ import_react23.default.createElement(playlists_default, {
+ ["Playlists"]: /* @__PURE__ */ import_react26.default.createElement(playlists_default, {
+ configWrapper
+ }),
+ ["Collections"]: /* @__PURE__ */ import_react26.default.createElement(collections_default, {
configWrapper
})
};
+ const tabPages = ["Playlists", "Albums", "Collections", "Artists", "Shows"].filter(
+ (page) => configWrapper.config[`show-${page.toLowerCase()}`]
+ );
const [navBar, activeLink, setActiveLink] = useNavigationBar_default(tabPages);
- const [firstUpdate, setFirstUpdate] = import_react23.default.useState(true);
- const [newUpdate, setNewUpdate] = import_react23.default.useState(false);
- import_react23.default.useEffect(() => {
+ const [firstUpdate, setFirstUpdate] = import_react26.default.useState(true);
+ const [newUpdate, setNewUpdate] = import_react26.default.useState(false);
+ import_react26.default.useEffect(() => {
setActiveLink(Spicetify.LocalStorage.get("library:active-link") || "Playlists");
checkForUpdates(setNewUpdate);
setFirstUpdate(false);
}, []);
- import_react23.default.useEffect(() => {
+ import_react26.default.useEffect(() => {
Spicetify.LocalStorage.set("library:active-link", activeLink);
}, [activeLink]);
if (firstUpdate)
- return /* @__PURE__ */ import_react23.default.createElement(import_react23.default.Fragment, null);
- return /* @__PURE__ */ import_react23.default.createElement(import_react23.default.Fragment, null, navBar, newUpdate && /* @__PURE__ */ import_react23.default.createElement("div", {
+ return /* @__PURE__ */ import_react26.default.createElement(import_react26.default.Fragment, null);
+ return /* @__PURE__ */ import_react26.default.createElement(import_react26.default.Fragment, null, navBar, newUpdate && /* @__PURE__ */ import_react26.default.createElement("div", {
className: "new-update"
- }, "New app update available! Visit", " ", /* @__PURE__ */ import_react23.default.createElement("a", {
+ }, "New app update available! Visit", " ", /* @__PURE__ */ import_react26.default.createElement("a", {
href: "https://github.com/harbassan/spicetify-apps/releases"
}, "harbassan/spicetify-apps"), " to install."), pages[activeLink]);
};
var App = () => {
- const [config, setConfig] = import_react23.default.useState({ ...SpicetifyLibrary.ConfigWrapper.Config });
+ const [config, setConfig] = import_react26.default.useState({ ...SpicetifyLibrary.ConfigWrapper.Config });
const launchModal = () => {
SpicetifyLibrary.ConfigWrapper.launchModal(setConfig);
};
@@ -1428,33 +1582,33 @@ var library = (() => {
const { pathname } = Spicetify.Platform.History.location;
const route = pathname.slice(8);
if (/^\/folder\/.+/.test(route)) {
- return /* @__PURE__ */ import_react23.default.createElement("div", {
+ return /* @__PURE__ */ import_react26.default.createElement("div", {
id: "library-app"
- }, /* @__PURE__ */ import_react23.default.createElement(playlists_default, {
+ }, /* @__PURE__ */ import_react26.default.createElement(playlists_default, {
folder: route.split("/").pop(),
configWrapper
}));
}
if (/^\/collection\/.+/.test(route)) {
- return /* @__PURE__ */ import_react23.default.createElement("div", {
+ return /* @__PURE__ */ import_react26.default.createElement("div", {
id: "library-app"
- }, /* @__PURE__ */ import_react23.default.createElement(albums_default, {
+ }, /* @__PURE__ */ import_react26.default.createElement(collections_default, {
collection: route.split("/").pop(),
configWrapper
}));
}
- return /* @__PURE__ */ import_react23.default.createElement("div", {
+ return /* @__PURE__ */ import_react26.default.createElement("div", {
id: "library-app"
- }, /* @__PURE__ */ import_react23.default.createElement(NavbarContainer, {
+ }, /* @__PURE__ */ import_react26.default.createElement(NavbarContainer, {
configWrapper
}));
};
var app_default = App;
// ../../../AppData/Local/Temp/spicetify-creator/index.jsx
- var import_react24 = __toESM(require_react());
+ var import_react27 = __toESM(require_react());
function render() {
- return /* @__PURE__ */ import_react24.default.createElement(app_default, null);
+ return /* @__PURE__ */ import_react27.default.createElement(app_default, null);
}
return __toCommonJS(spicetify_creator_exports);
})();
diff --git a/.config/spicetify/CustomApps/library/style.css b/.config/spicetify/CustomApps/library/style.css
index 9a15dd6c..9df462b7 100644
--- a/.config/spicetify/CustomApps/library/style.css
+++ b/.config/spicetify/CustomApps/library/style.css
@@ -1,4 +1,4 @@
-/* ../../../AppData/Local/Temp/tmp-5152-1Ex7Bvh4Tu6u/18f3cb8bc8b4/navBar.module.css */
+/* ../../../AppData/Local/Temp/tmp-4464-wbZ6O1BKhuot/19178f16f304/navBar.module.css */
.navBar-module__topBarHeaderItem___piw4C_library {
-webkit-app-region: no-drag;
display: inline-block;
@@ -46,7 +46,7 @@ div.navBar-module__topBarHeaderItemLink___xA4uv_library {
padding: 0;
}
-/* ../../../AppData/Local/Temp/tmp-5152-1Ex7Bvh4Tu6u/18f3cb8baff0/app.css */
+/* ../../../AppData/Local/Temp/tmp-4464-wbZ6O1BKhuot/19178f16dc80/app.css */
:root {
--library-card-size: 180px;
--library-searchbar-size: 200px;
@@ -57,6 +57,10 @@ div.navBar-module__topBarHeaderItemLink___xA4uv_library {
#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;
@@ -100,7 +104,7 @@ div.navBar-module__topBarHeaderItemLink___xA4uv_library {
align-self: end;
}
-/* ../../../AppData/Local/Temp/tmp-5152-1Ex7Bvh4Tu6u/18f3cb8bc361/external.css */
+/* ../../../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,
@@ -113,10 +117,12 @@ body:not(.show-ylx-filters) .main-yourLibraryX-filterArea:not(:has(> .main-yourL
.main-yourLibraryX-header {
margin-top: -8px;
}
-.main-yourLibraryX-librarySortWrapper button span:first-child {
+.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-librarySortWrapper {
+.main-yourLibraryX-libraryFilter .main-yourLibraryX-librarySortWrapper,
+.main-yourLibraryX-libraryFilter span[role=presentation] {
margin-left: auto;
}
.toggle-filters-button > button:after,
@@ -140,21 +146,24 @@ li.main-yourLibraryX-navItem[data-id="/library"] {
li.main-yourLibraryX-navItem[data-id="/library"] > a {
flex-grow: 1;
}
-.toggle-filters-button > button,
-.collapse-button > button,
-.main-yourLibraryX-librarySortWrapper > button {
+.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;
}
-.toggle-filters-button,
-.collapse-button,
-.main-yourLibraryX-librarySortWrapper {
+.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-librarySortWrapper > button > span:nth-child(2) {
+.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 {
@@ -227,7 +236,7 @@ li.main-yourLibraryX-navItem[data-id="/library"] > a {
transform: scale(1.04);
}
-/* ../../../AppData/Local/Temp/tmp-5152-1Ex7Bvh4Tu6u/18f3cb8bc5f2/config_modal.css */
+/* ../../../AppData/Local/Temp/tmp-4464-wbZ6O1BKhuot/19178f16f0a2/config_modal.css */
.config-container {
gap: 10px;
display: flex;
@@ -304,7 +313,7 @@ li.main-yourLibraryX-navItem[data-id="/library"] > a {
width: 200px;
}
-/* ../../../AppData/Local/Temp/tmp-5152-1Ex7Bvh4Tu6u/18f3cb8bc713/shared.css */
+/* ../../../AppData/Local/Temp/tmp-4464-wbZ6O1BKhuot/19178f16f163/shared.css */
.grid {
--grid-gap: 24px;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)) !important;
@@ -327,6 +336,20 @@ li.main-yourLibraryX-navItem[data-id="/library"] > a {
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;
diff --git a/.config/spicetify/config-xpui.ini b/.config/spicetify/config-xpui.ini
index 585d0df1..4032e9aa 100644
--- a/.config/spicetify/config-xpui.ini
+++ b/.config/spicetify/config-xpui.ini
@@ -29,4 +29,4 @@ custom_apps = library|lyrics-plus|marketplace|stats
; DO NOT CHANGE!
[Backup]
version = 1.2.42.290.g242057a2
-with = 2.37.1
+with = 2.38.3