♻️ refactor(spicetify): using marketplace for extensions and themes
This commit is contained in:
parent
972d458ac8
commit
1a6432a27b
38 changed files with 6100 additions and 8552 deletions
3
.config/spicetify/.github/README.md
vendored
3
.config/spicetify/.github/README.md
vendored
|
@ -1,3 +0,0 @@
|
||||||
<div align="center">
|
|
||||||
<a href=""><img src="./title.png"></a>
|
|
||||||
</div>
|
|
BIN
.config/spicetify/.github/title.png
vendored
BIN
.config/spicetify/.github/title.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 26 KiB |
84
.config/spicetify/CustomApps/better-local-files/README.md
Normal file
84
.config/spicetify/CustomApps/better-local-files/README.md
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
# Better local files
|
||||||
|
|
||||||
|
View your local songs, albums and artists.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
> There is a specific way to format artists so that the custom app recognizes them correctly: see [limitations](#limitations).
|
||||||
|
>
|
||||||
|
> If you have an issue with albums with the same name being merged, see [Albums with the same name from different artists](#albums-with-the-same-name-from-different-artists).
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Tracks, albums, and artists views
|
||||||
|
- Supports filtering and sorting
|
||||||
|
- Supports all languages available in Spotify.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Run `spicetify config-dir` to open the spicetify folder.
|
||||||
|
2. Go to the `CustomApps` folder.
|
||||||
|
3. Create a `better-local-files` folder.
|
||||||
|
4. Download the custom app files as a zip from [here](https://github.com/Pithaya/spicetify-apps-dist/archive/refs/heads/dist/better-local-files.zip).
|
||||||
|
5. Extract the zip and put the files inside the folder you created in step 3.
|
||||||
|
|
||||||
|
Then, run the following commands:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
spicetify config custom_apps better-local-files
|
||||||
|
spicetify apply
|
||||||
|
```
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
### Song artists
|
||||||
|
|
||||||
|
Spotify doesn't seem to recognize separate artist tags on songs as different artists, and will instead use the concatenation of the artist's names. So a track with artists 'A' and 'B' will be considered as having a single artist: 'A, B'.
|
||||||
|
The custom app parses these strings with the following delimiters: ', ' and '; '. You should use this format to get artists recognized properly.
|
||||||
|
**Note**: This means that artists with the same name will be **considered the same artist**.
|
||||||
|
|
||||||
|
### Album artists
|
||||||
|
|
||||||
|
Album artists tags are not recognized, so the custom app will use the list of all artists from the album's tracks as the album's artists.
|
||||||
|
|
||||||
|
### Artist images
|
||||||
|
|
||||||
|
As artist images are not available, the artist's first album's image will be used instead.
|
||||||
|
|
||||||
|
## Albums with the same name from different artists
|
||||||
|
|
||||||
|
To identify an album, Spotify uses the current track's artist(s) name and the album name. This means that albums with tracks from different artists will be considered separate albums.
|
||||||
|
To fix this, the custom app groups albums by name.
|
||||||
|
This means that albums with the same name will be **considered the same album**.
|
||||||
|
|
||||||
|
If you find that you have albums with the same name that are being merged when they shouldn't, you can use the **settings menu** to **rebuild the local album cache**. This will try to group tracks that share the same album name correctly by comparing the album covers.
|
||||||
|
|
||||||
|
Note that for performance reasons this behaviour is **not enabled by default**. If you add new albums that have the same issue later on, you will need to **rebuild the cache manually again**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The cache can take some time to be built depending on how many tracks you have and how large the cover images are.
|
||||||
|
|
||||||
|
The **clear album cache** menu item allows you to clear the cache and go back to the default behavior.
|
||||||
|
|
||||||
|
## Upcoming features
|
||||||
|
|
||||||
|
- Artist's album list
|
||||||
|
- Multi track selection
|
||||||
|
- Keep search, sort, and scroll information on navigation
|
||||||
|
|
||||||
|
## Uninstall
|
||||||
|
|
||||||
|
1. Run `spicetify config-dir` to open the spicetify folder
|
||||||
|
2. Go to the `CustomApps` folder
|
||||||
|
3. Delete the `better-local-files` folder
|
||||||
|
|
||||||
|
Then, run the following commands:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
spicetify config custom_apps better-local-files-
|
||||||
|
spicetify apply
|
||||||
|
```
|
File diff suppressed because one or more lines are too long
69
.config/spicetify/CustomApps/better-local-files/index.js
Normal file
69
.config/spicetify/CustomApps/better-local-files/index.js
Normal file
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"name": "Local Files",
|
||||||
|
"icon": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" style=\"stroke-width: 2px !important;\"><path d=\"M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2Z\"></path></svg>",
|
||||||
|
"active-icon": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" style=\"stroke-width: 2px !important;\"><path d=\"M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2Z\"></path></svg>",
|
||||||
|
"subfiles": [],
|
||||||
|
"subfiles_extension": [
|
||||||
|
"extension.js"
|
||||||
|
]
|
||||||
|
}
|
BIN
.config/spicetify/CustomApps/better-local-files/preview.png
Normal file
BIN
.config/spicetify/CustomApps/better-local-files/preview.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 248 KiB |
File diff suppressed because one or more lines are too long
156
.config/spicetify/CustomApps/library/collection_wrapper.js
Normal file
156
.config/spicetify/CustomApps/library/collection_wrapper.js
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
(async function() {
|
||||||
|
while (!Spicetify.React || !Spicetify.ReactDOM) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 10));
|
||||||
|
}
|
||||||
|
"use strict";
|
||||||
|
var library = (() => {
|
||||||
|
var __defProp = Object.defineProperty;
|
||||||
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||||
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||||
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||||
|
var __export = (target, all) => {
|
||||||
|
for (var name in all)
|
||||||
|
__defProp(target, name, { get: all[name], enumerable: true });
|
||||||
|
};
|
||||||
|
var __copyProps = (to, from, except, desc) => {
|
||||||
|
if (from && typeof from === "object" || typeof from === "function") {
|
||||||
|
for (let key of __getOwnPropNames(from))
|
||||||
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||||
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||||
|
}
|
||||||
|
return to;
|
||||||
|
};
|
||||||
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||||
|
|
||||||
|
// src/extensions/collection_wrapper.tsx
|
||||||
|
var collection_wrapper_exports = {};
|
||||||
|
__export(collection_wrapper_exports, {
|
||||||
|
default: () => collection_wrapper_default
|
||||||
|
});
|
||||||
|
|
||||||
|
// node_modules/uuid/dist/esm-browser/rng.js
|
||||||
|
var getRandomValues;
|
||||||
|
var rnds8 = new Uint8Array(16);
|
||||||
|
function rng() {
|
||||||
|
if (!getRandomValues) {
|
||||||
|
getRandomValues = typeof crypto !== "undefined" && crypto.getRandomValues && crypto.getRandomValues.bind(crypto);
|
||||||
|
if (!getRandomValues) {
|
||||||
|
throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getRandomValues(rnds8);
|
||||||
|
}
|
||||||
|
|
||||||
|
// node_modules/uuid/dist/esm-browser/stringify.js
|
||||||
|
var byteToHex = [];
|
||||||
|
for (let i = 0; i < 256; ++i) {
|
||||||
|
byteToHex.push((i + 256).toString(16).slice(1));
|
||||||
|
}
|
||||||
|
function unsafeStringify(arr, offset = 0) {
|
||||||
|
return byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]];
|
||||||
|
}
|
||||||
|
|
||||||
|
// node_modules/uuid/dist/esm-browser/native.js
|
||||||
|
var randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto);
|
||||||
|
var native_default = {
|
||||||
|
randomUUID
|
||||||
|
};
|
||||||
|
|
||||||
|
// node_modules/uuid/dist/esm-browser/v4.js
|
||||||
|
function v4(options, buf, offset) {
|
||||||
|
if (native_default.randomUUID && !buf && !options) {
|
||||||
|
return native_default.randomUUID();
|
||||||
|
}
|
||||||
|
options = options || {};
|
||||||
|
const rnds = options.random || (options.rng || rng)();
|
||||||
|
rnds[6] = rnds[6] & 15 | 64;
|
||||||
|
rnds[8] = rnds[8] & 63 | 128;
|
||||||
|
if (buf) {
|
||||||
|
offset = offset || 0;
|
||||||
|
for (let i = 0; i < 16; ++i) {
|
||||||
|
buf[offset + i] = rnds[i];
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
return unsafeStringify(rnds);
|
||||||
|
}
|
||||||
|
var v4_default = v4;
|
||||||
|
|
||||||
|
// src/extensions/collection_wrapper.tsx
|
||||||
|
var CollectionWrapper = class {
|
||||||
|
constructor() {
|
||||||
|
this.getCollections = () => {
|
||||||
|
return this._collections;
|
||||||
|
};
|
||||||
|
this.createCollection = (name) => {
|
||||||
|
const collection = {
|
||||||
|
id: v4_default(),
|
||||||
|
name,
|
||||||
|
items: []
|
||||||
|
};
|
||||||
|
this._collections.push(collection);
|
||||||
|
this.saveCollections();
|
||||||
|
Spicetify.showNotification("Collection Created");
|
||||||
|
return collection;
|
||||||
|
};
|
||||||
|
this.deleteCollection = (collectionID) => {
|
||||||
|
this._collections = this._collections.filter((collection) => collection.id !== collectionID);
|
||||||
|
this.saveCollections();
|
||||||
|
Spicetify.showNotification("Collection Deleted");
|
||||||
|
};
|
||||||
|
this.getCollection = (collectionID) => {
|
||||||
|
return this._collections.find((collection) => collection.id === collectionID);
|
||||||
|
};
|
||||||
|
this.renameCollection = (collectionID, name) => {
|
||||||
|
const collection = this.getCollection(collectionID);
|
||||||
|
if (!collection)
|
||||||
|
throw new Error("Collection is not defined");
|
||||||
|
collection.name = name;
|
||||||
|
this.saveCollections();
|
||||||
|
Spicetify.showNotification("Collection Renamed");
|
||||||
|
};
|
||||||
|
this.addToCollection = (collectionID, albumURI) => {
|
||||||
|
const collection = this.getCollection(collectionID);
|
||||||
|
if (!collection)
|
||||||
|
throw new Error("Collection is not defined");
|
||||||
|
Spicetify.GraphQL.Request(Spicetify.GraphQL.Definitions.getAlbum, {
|
||||||
|
uri: albumURI,
|
||||||
|
locale: "en",
|
||||||
|
offset: 0,
|
||||||
|
limit: 1
|
||||||
|
}).then((res) => {
|
||||||
|
var _a, _b, _c, _d, _e, _f, _g;
|
||||||
|
const data = res.data.albumUnion;
|
||||||
|
const albumItem = {
|
||||||
|
uri: data.uri,
|
||||||
|
name: data.name,
|
||||||
|
artist: (_d = (_c = (_b = (_a = data.artists) == null ? void 0 : _a.items) == null ? void 0 : _b[0]) == null ? void 0 : _c.profile) == null ? void 0 : _d.name,
|
||||||
|
image: ((_g = (_f = (_e = data.coverArt) == null ? void 0 : _e.sources) == null ? void 0 : _f[0]) == null ? void 0 : _g.url) || ""
|
||||||
|
};
|
||||||
|
collection.items.push(albumItem);
|
||||||
|
this.saveCollections();
|
||||||
|
});
|
||||||
|
Spicetify.showNotification("Item Added to Collection");
|
||||||
|
};
|
||||||
|
this.removeFromCollection = (collectionID, albumURI) => {
|
||||||
|
const collection = this.getCollection(collectionID);
|
||||||
|
if (!collection)
|
||||||
|
throw new Error("Collection is not defined");
|
||||||
|
collection.items = collection.items.filter((album) => album.uri !== albumURI);
|
||||||
|
this.saveCollections();
|
||||||
|
Spicetify.showNotification("Item Removed from Collection");
|
||||||
|
};
|
||||||
|
this.getCollectionForItem = (albumURI) => {
|
||||||
|
return this._collections.filter((collection) => collection.items.some((item) => item.uri === albumURI));
|
||||||
|
};
|
||||||
|
this.saveCollections = () => {
|
||||||
|
localStorage.setItem("library:collections", JSON.stringify(this._collections));
|
||||||
|
};
|
||||||
|
this._collections = JSON.parse(localStorage.getItem("library:collections") || "[]");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var collection_wrapper_default = CollectionWrapper;
|
||||||
|
return __toCommonJS(collection_wrapper_exports);
|
||||||
|
})();
|
||||||
|
|
||||||
|
})();
|
218
.config/spicetify/CustomApps/library/collections_wrapper.js
Normal file
218
.config/spicetify/CustomApps/library/collections_wrapper.js
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
(async function() {
|
||||||
|
while (!Spicetify.React || !Spicetify.ReactDOM) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 10));
|
||||||
|
}
|
||||||
|
"use strict";
|
||||||
|
var library = (() => {
|
||||||
|
var __defProp = Object.defineProperty;
|
||||||
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||||
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||||
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||||
|
var __export = (target, all) => {
|
||||||
|
for (var name in all)
|
||||||
|
__defProp(target, name, { get: all[name], enumerable: true });
|
||||||
|
};
|
||||||
|
var __copyProps = (to, from, except, desc) => {
|
||||||
|
if (from && typeof from === "object" || typeof from === "function") {
|
||||||
|
for (let key of __getOwnPropNames(from))
|
||||||
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||||
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||||
|
}
|
||||||
|
return to;
|
||||||
|
};
|
||||||
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||||
|
|
||||||
|
// src/extensions/collections_wrapper.ts
|
||||||
|
var collections_wrapper_exports = {};
|
||||||
|
__export(collections_wrapper_exports, {
|
||||||
|
default: () => collections_wrapper_default
|
||||||
|
});
|
||||||
|
|
||||||
|
// ../node_modules/uuid/dist/esm-browser/rng.js
|
||||||
|
var getRandomValues;
|
||||||
|
var rnds8 = new Uint8Array(16);
|
||||||
|
function rng() {
|
||||||
|
if (!getRandomValues) {
|
||||||
|
getRandomValues = typeof crypto !== "undefined" && crypto.getRandomValues && crypto.getRandomValues.bind(crypto);
|
||||||
|
if (!getRandomValues) {
|
||||||
|
throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getRandomValues(rnds8);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ../node_modules/uuid/dist/esm-browser/stringify.js
|
||||||
|
var byteToHex = [];
|
||||||
|
for (let i = 0; i < 256; ++i) {
|
||||||
|
byteToHex.push((i + 256).toString(16).slice(1));
|
||||||
|
}
|
||||||
|
function unsafeStringify(arr, offset = 0) {
|
||||||
|
return byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ../node_modules/uuid/dist/esm-browser/native.js
|
||||||
|
var randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto);
|
||||||
|
var native_default = {
|
||||||
|
randomUUID
|
||||||
|
};
|
||||||
|
|
||||||
|
// ../node_modules/uuid/dist/esm-browser/v4.js
|
||||||
|
function v4(options, buf, offset) {
|
||||||
|
if (native_default.randomUUID && !buf && !options) {
|
||||||
|
return native_default.randomUUID();
|
||||||
|
}
|
||||||
|
options = options || {};
|
||||||
|
const rnds = options.random || (options.rng || rng)();
|
||||||
|
rnds[6] = rnds[6] & 15 | 64;
|
||||||
|
rnds[8] = rnds[8] & 63 | 128;
|
||||||
|
if (buf) {
|
||||||
|
offset = offset || 0;
|
||||||
|
for (let i = 0; i < 16; ++i) {
|
||||||
|
buf[offset + i] = rnds[i];
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
return unsafeStringify(rnds);
|
||||||
|
}
|
||||||
|
var v4_default = v4;
|
||||||
|
|
||||||
|
// src/extensions/collections_wrapper.ts
|
||||||
|
var CollectionWrapper = class extends EventTarget {
|
||||||
|
_collections;
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this._collections = JSON.parse(localStorage.getItem("library:collections") || "[]");
|
||||||
|
}
|
||||||
|
saveCollections() {
|
||||||
|
localStorage.setItem("library:collections", JSON.stringify(this._collections));
|
||||||
|
this.dispatchEvent(new CustomEvent("update", { detail: this._collections }));
|
||||||
|
}
|
||||||
|
getCollection(uri) {
|
||||||
|
return this._collections.find((collection) => collection.uri === uri);
|
||||||
|
}
|
||||||
|
async requestAlbums({ sortOrder, textFilter }) {
|
||||||
|
const albums = await Spicetify.Platform.LibraryAPI.getContents({
|
||||||
|
filters: ["0"],
|
||||||
|
sortOrder,
|
||||||
|
textFilter,
|
||||||
|
offset: 0,
|
||||||
|
limit: 9999
|
||||||
|
});
|
||||||
|
return albums;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
if (textFilter) {
|
||||||
|
let regex = new RegExp("\\b" + textFilter, "i");
|
||||||
|
collectionItems = collectionItems.filter((item) => {
|
||||||
|
return regex.test(item.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (offset > 0)
|
||||||
|
collectionItems = [];
|
||||||
|
return {
|
||||||
|
openedCollection,
|
||||||
|
items: [...collectionItems, ...albumItems.slice(offset, offset + limit)],
|
||||||
|
totalLength: albumItems.length + collectionItems.length,
|
||||||
|
unfilteredLength
|
||||||
|
};
|
||||||
|
}
|
||||||
|
createCollection(name, parentCollection = "") {
|
||||||
|
const uri = v4_default();
|
||||||
|
const collection = {
|
||||||
|
type: "collection",
|
||||||
|
uri,
|
||||||
|
name,
|
||||||
|
items: [],
|
||||||
|
totalLength: 0,
|
||||||
|
imgUrl: "",
|
||||||
|
parentCollection
|
||||||
|
};
|
||||||
|
this._collections.push(collection);
|
||||||
|
this.saveCollections();
|
||||||
|
Spicetify.showNotification("Collection created");
|
||||||
|
}
|
||||||
|
deleteCollection(uri) {
|
||||||
|
this._collections = this._collections.filter((collection) => collection.uri !== uri);
|
||||||
|
this.saveCollections();
|
||||||
|
Spicetify.showNotification("Collection deleted");
|
||||||
|
}
|
||||||
|
async addAlbumToCollection(collectionUri, albumUri) {
|
||||||
|
const collection = this.getCollection(collectionUri);
|
||||||
|
if (!collection)
|
||||||
|
return;
|
||||||
|
collection.items.push(albumUri);
|
||||||
|
collection.totalLength++;
|
||||||
|
this.saveCollections();
|
||||||
|
Spicetify.showNotification("Album added to collection");
|
||||||
|
}
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
getCollectionsWithAlbum(albumUri) {
|
||||||
|
return this._collections.filter((collection) => {
|
||||||
|
return collection.items.some((item) => item === albumUri);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
renameCollection(uri, newName) {
|
||||||
|
const collection = this.getCollection(uri);
|
||||||
|
if (!collection)
|
||||||
|
return;
|
||||||
|
collection.name = newName;
|
||||||
|
this.saveCollections();
|
||||||
|
Spicetify.showNotification("Collection renamed");
|
||||||
|
}
|
||||||
|
setCollectionImage(uri, imgUrl) {
|
||||||
|
const collection = this.getCollection(uri);
|
||||||
|
if (!collection)
|
||||||
|
return;
|
||||||
|
collection.imgUrl = imgUrl;
|
||||||
|
this.saveCollections();
|
||||||
|
Spicetify.showNotification("Collection image set");
|
||||||
|
}
|
||||||
|
removeCollectionImage(uri) {
|
||||||
|
const collection = this.getCollection(uri);
|
||||||
|
if (!collection)
|
||||||
|
return;
|
||||||
|
collection.imgUrl = "";
|
||||||
|
this.saveCollections();
|
||||||
|
Spicetify.showNotification("Collection image removed");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var collections_wrapper_default = CollectionWrapper;
|
||||||
|
return __toCommonJS(collections_wrapper_exports);
|
||||||
|
})();
|
||||||
|
|
||||||
|
})();
|
280
.config/spicetify/CustomApps/library/config_loader.js
Normal file
280
.config/spicetify/CustomApps/library/config_loader.js
Normal file
|
@ -0,0 +1,280 @@
|
||||||
|
(async function() {
|
||||||
|
while (!Spicetify.React || !Spicetify.ReactDOM) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 10));
|
||||||
|
}
|
||||||
|
"use strict";
|
||||||
|
var library = (() => {
|
||||||
|
var __create = Object.create;
|
||||||
|
var __defProp = Object.defineProperty;
|
||||||
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||||
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||||
|
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
||||||
|
var __getProtoOf = Object.getPrototypeOf;
|
||||||
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||||
|
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
||||||
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
||||||
|
var __spreadValues = (a, b) => {
|
||||||
|
for (var prop in b || (b = {}))
|
||||||
|
if (__hasOwnProp.call(b, prop))
|
||||||
|
__defNormalProp(a, prop, b[prop]);
|
||||||
|
if (__getOwnPropSymbols)
|
||||||
|
for (var prop of __getOwnPropSymbols(b)) {
|
||||||
|
if (__propIsEnum.call(b, prop))
|
||||||
|
__defNormalProp(a, prop, b[prop]);
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
};
|
||||||
|
var __commonJS = (cb, mod) => function __require() {
|
||||||
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
||||||
|
};
|
||||||
|
var __export = (target, all) => {
|
||||||
|
for (var name in all)
|
||||||
|
__defProp(target, name, { get: all[name], enumerable: true });
|
||||||
|
};
|
||||||
|
var __copyProps = (to, from, except, desc) => {
|
||||||
|
if (from && typeof from === "object" || typeof from === "function") {
|
||||||
|
for (let key of __getOwnPropNames(from))
|
||||||
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||||
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||||
|
}
|
||||||
|
return to;
|
||||||
|
};
|
||||||
|
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||||
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||||
|
mod
|
||||||
|
));
|
||||||
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||||
|
|
||||||
|
// external-global-plugin:react
|
||||||
|
var require_react = __commonJS({
|
||||||
|
"external-global-plugin:react"(exports, module) {
|
||||||
|
module.exports = Spicetify.React;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// src/extensions/config_loader.tsx
|
||||||
|
var config_loader_exports = {};
|
||||||
|
__export(config_loader_exports, {
|
||||||
|
default: () => config_loader_default
|
||||||
|
});
|
||||||
|
|
||||||
|
// src/components/settings_modal.tsx
|
||||||
|
var import_react = __toESM(require_react());
|
||||||
|
var TextInput = (props) => {
|
||||||
|
const textId = `text-input:${props.storageKey}`;
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement("label", {
|
||||||
|
className: "text-input-wrapper"
|
||||||
|
}, /* @__PURE__ */ import_react.default.createElement("input", {
|
||||||
|
className: "text-input",
|
||||||
|
type: "text",
|
||||||
|
value: props.value || "",
|
||||||
|
"data-storage-key": props.storageKey,
|
||||||
|
placeholder: props.placeholder,
|
||||||
|
id: textId,
|
||||||
|
title: `Text input for ${props.storageKey}`,
|
||||||
|
onChange: props.onChange
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
var Dropdown = (props) => {
|
||||||
|
const dropdownId = `dropdown:${props.storageKey}`;
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement("label", {
|
||||||
|
className: "dropdown-wrapper"
|
||||||
|
}, /* @__PURE__ */ import_react.default.createElement("select", {
|
||||||
|
className: "dropdown-input",
|
||||||
|
value: props.value,
|
||||||
|
"data-storage-key": props.storageKey,
|
||||||
|
id: dropdownId,
|
||||||
|
title: `Dropdown for ${props.storageKey}`,
|
||||||
|
onChange: props.onChange
|
||||||
|
}, props.options.map((option, index) => /* @__PURE__ */ import_react.default.createElement("option", {
|
||||||
|
key: index,
|
||||||
|
value: option
|
||||||
|
}, option))));
|
||||||
|
};
|
||||||
|
var TooltipIcon = () => {
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement("svg", {
|
||||||
|
role: "img",
|
||||||
|
height: "16",
|
||||||
|
width: "16",
|
||||||
|
className: "Svg-sc-ytk21e-0 uPxdw nW1RKQOkzcJcX6aDCZB4",
|
||||||
|
viewBox: "0 0 16 16"
|
||||||
|
}, /* @__PURE__ */ import_react.default.createElement("path", {
|
||||||
|
d: "M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8z"
|
||||||
|
}), /* @__PURE__ */ import_react.default.createElement("path", {
|
||||||
|
d: "M7.25 12.026v-1.5h1.5v1.5h-1.5zm.884-7.096A1.125 1.125 0 007.06 6.39l-1.431.448a2.625 2.625 0 115.13-.784c0 .54-.156 1.015-.503 1.488-.3.408-.7.652-.973.818l-.112.068c-.185.116-.26.203-.302.283-.046.087-.097.245-.097.57h-1.5c0-.47.072-.898.274-1.277.206-.385.507-.645.827-.846l.147-.092c.285-.177.413-.257.526-.41.169-.23.213-.397.213-.602 0-.622-.503-1.125-1.125-1.125z"
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
var ConfigRow = (props) => {
|
||||||
|
console.log(props);
|
||||||
|
const enabled = !!props.modalConfig[props.storageKey];
|
||||||
|
const value = props.modalConfig[props.storageKey];
|
||||||
|
const updateItem = (storageKey, state) => {
|
||||||
|
props.modalConfig[storageKey] = state;
|
||||||
|
console.debug(`toggling ${storageKey} to ${state}`);
|
||||||
|
localStorage.setItem(`library:config:${storageKey}`, String(state));
|
||||||
|
props.updateConfig(props.modalConfig);
|
||||||
|
};
|
||||||
|
const settingsToggleChange = (newValue, storageKey) => {
|
||||||
|
updateItem(storageKey, newValue);
|
||||||
|
if (props.callback)
|
||||||
|
props.callback(newValue);
|
||||||
|
};
|
||||||
|
const settingsTextChange = (event) => {
|
||||||
|
console.log("yoohoo");
|
||||||
|
updateItem(event.target.dataset.storageKey, event.target.value);
|
||||||
|
console.log(props.callback);
|
||||||
|
if (props.callback)
|
||||||
|
props.callback(event.target.value);
|
||||||
|
};
|
||||||
|
const settingsDropdownChange = (event) => {
|
||||||
|
updateItem(event.target.dataset.storageKey, event.target.value);
|
||||||
|
if (props.callback)
|
||||||
|
props.callback(event.target.value);
|
||||||
|
};
|
||||||
|
const element = () => {
|
||||||
|
switch (props.type) {
|
||||||
|
case "dropdown":
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement(Dropdown, {
|
||||||
|
name: props.name,
|
||||||
|
storageKey: props.storageKey,
|
||||||
|
value,
|
||||||
|
options: props.options || [],
|
||||||
|
onChange: settingsDropdownChange
|
||||||
|
});
|
||||||
|
case "text":
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement(TextInput, {
|
||||||
|
name: props.name,
|
||||||
|
storageKey: props.storageKey,
|
||||||
|
value,
|
||||||
|
placeholder: props.placeholder,
|
||||||
|
onChange: settingsTextChange
|
||||||
|
});
|
||||||
|
default:
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement(Spicetify.ReactComponent.Toggle, {
|
||||||
|
id: `toggle:${props.storageKey}`,
|
||||||
|
value: enabled,
|
||||||
|
onSelected: (newValue) => {
|
||||||
|
settingsToggleChange(newValue, props.storageKey);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement("div", {
|
||||||
|
className: "setting-row"
|
||||||
|
}, /* @__PURE__ */ import_react.default.createElement("label", {
|
||||||
|
className: "col description"
|
||||||
|
}, props.name, props.desc && /* @__PURE__ */ import_react.default.createElement(Spicetify.ReactComponent.TooltipWrapper, {
|
||||||
|
label: /* @__PURE__ */ import_react.default.createElement("div", {
|
||||||
|
dangerouslySetInnerHTML: { __html: props.desc }
|
||||||
|
}),
|
||||||
|
renderInline: true,
|
||||||
|
showDelay: 10,
|
||||||
|
placement: "top",
|
||||||
|
labelClassName: "tooltip",
|
||||||
|
disabled: false
|
||||||
|
}, /* @__PURE__ */ import_react.default.createElement("div", {
|
||||||
|
className: "tooltip-icon"
|
||||||
|
}, /* @__PURE__ */ import_react.default.createElement(TooltipIcon, null)))), /* @__PURE__ */ import_react.default.createElement("div", {
|
||||||
|
className: "col action"
|
||||||
|
}, element()));
|
||||||
|
};
|
||||||
|
var SettingsModal = ({ CONFIG, settings, updateAppConfig }) => {
|
||||||
|
const [modalConfig, setModalConfig] = import_react.default.useState(__spreadValues({}, CONFIG));
|
||||||
|
const updateConfig = (CONFIG2) => {
|
||||||
|
updateAppConfig(__spreadValues({}, CONFIG2));
|
||||||
|
setModalConfig(__spreadValues({}, CONFIG2));
|
||||||
|
};
|
||||||
|
const configRows = settings.map((setting, index) => {
|
||||||
|
console.log(setting);
|
||||||
|
if (setting.sectionHeader) {
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, index != 0 ? /* @__PURE__ */ import_react.default.createElement("br", null) : /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null), /* @__PURE__ */ import_react.default.createElement("h2", {
|
||||||
|
className: "section-header"
|
||||||
|
}, setting.sectionHeader), /* @__PURE__ */ import_react.default.createElement(ConfigRow, {
|
||||||
|
name: setting.name,
|
||||||
|
storageKey: setting.key,
|
||||||
|
type: setting.type,
|
||||||
|
options: setting.options,
|
||||||
|
placeholder: setting.placeholder,
|
||||||
|
desc: setting.desc,
|
||||||
|
modalConfig,
|
||||||
|
updateConfig,
|
||||||
|
callback: setting.callback
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement(ConfigRow, {
|
||||||
|
name: setting.name,
|
||||||
|
storageKey: setting.key,
|
||||||
|
type: setting.type,
|
||||||
|
options: setting.options,
|
||||||
|
placeholder: setting.placeholder,
|
||||||
|
desc: setting.desc,
|
||||||
|
modalConfig,
|
||||||
|
updateConfig,
|
||||||
|
callback: setting.callback
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement("div", {
|
||||||
|
id: "stats-config-container"
|
||||||
|
}, configRows);
|
||||||
|
};
|
||||||
|
var settings_modal_default = SettingsModal;
|
||||||
|
|
||||||
|
// src/extensions/config_loader.tsx
|
||||||
|
var import_react2 = __toESM(require_react());
|
||||||
|
var getLocalStorageDataFromKey = (key, fallback) => {
|
||||||
|
const data = localStorage.getItem(key);
|
||||||
|
if (data) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(data);
|
||||||
|
} catch (err) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
(function wait() {
|
||||||
|
const { LocalStorageAPI } = Spicetify == null ? void 0 : Spicetify.Platform;
|
||||||
|
if (!LocalStorageAPI) {
|
||||||
|
setTimeout(wait, 100);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
async function loadConfig(configSettings) {
|
||||||
|
const { PopupModal } = Spicetify;
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
(function checkPopupModal() {
|
||||||
|
if (PopupModal) {
|
||||||
|
resolve(void 0);
|
||||||
|
} else {
|
||||||
|
setTimeout(checkPopupModal, 100);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
const settingsArray = configSettings.map((setting) => {
|
||||||
|
return { [setting.key]: getLocalStorageDataFromKey(`library:config:${setting.key}`, setting.def) };
|
||||||
|
});
|
||||||
|
let CONFIG = window.CONFIG = Object.assign({}, ...settingsArray);
|
||||||
|
const updateConfig = (config) => {
|
||||||
|
window.CONFIG = __spreadValues({}, config);
|
||||||
|
console.log("updated config", config);
|
||||||
|
};
|
||||||
|
const launchModal = window.launchModal = () => {
|
||||||
|
console.log(settingsArray);
|
||||||
|
PopupModal.display({
|
||||||
|
title: "Library Settings",
|
||||||
|
content: /* @__PURE__ */ import_react2.default.createElement(settings_modal_default, {
|
||||||
|
CONFIG,
|
||||||
|
settings: configSettings,
|
||||||
|
updateAppConfig: updateConfig
|
||||||
|
}),
|
||||||
|
isLarge: true
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return { CONFIG, launchModal };
|
||||||
|
}
|
||||||
|
var config_loader_default = loadConfig;
|
||||||
|
return __toCommonJS(config_loader_exports);
|
||||||
|
})();
|
||||||
|
|
||||||
|
})();
|
269
.config/spicetify/CustomApps/library/config_wrapper.js
Normal file
269
.config/spicetify/CustomApps/library/config_wrapper.js
Normal file
|
@ -0,0 +1,269 @@
|
||||||
|
(async function() {
|
||||||
|
while (!Spicetify.React || !Spicetify.ReactDOM) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 10));
|
||||||
|
}
|
||||||
|
"use strict";
|
||||||
|
var library = (() => {
|
||||||
|
var __create = Object.create;
|
||||||
|
var __defProp = Object.defineProperty;
|
||||||
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||||
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||||
|
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
||||||
|
var __getProtoOf = Object.getPrototypeOf;
|
||||||
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||||
|
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
||||||
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
||||||
|
var __spreadValues = (a, b) => {
|
||||||
|
for (var prop in b || (b = {}))
|
||||||
|
if (__hasOwnProp.call(b, prop))
|
||||||
|
__defNormalProp(a, prop, b[prop]);
|
||||||
|
if (__getOwnPropSymbols)
|
||||||
|
for (var prop of __getOwnPropSymbols(b)) {
|
||||||
|
if (__propIsEnum.call(b, prop))
|
||||||
|
__defNormalProp(a, prop, b[prop]);
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
};
|
||||||
|
var __commonJS = (cb, mod) => function __require() {
|
||||||
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
||||||
|
};
|
||||||
|
var __export = (target, all) => {
|
||||||
|
for (var name in all)
|
||||||
|
__defProp(target, name, { get: all[name], enumerable: true });
|
||||||
|
};
|
||||||
|
var __copyProps = (to, from, except, desc) => {
|
||||||
|
if (from && typeof from === "object" || typeof from === "function") {
|
||||||
|
for (let key of __getOwnPropNames(from))
|
||||||
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||||
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||||
|
}
|
||||||
|
return to;
|
||||||
|
};
|
||||||
|
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||||
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||||
|
mod
|
||||||
|
));
|
||||||
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||||
|
|
||||||
|
// external-global-plugin:react
|
||||||
|
var require_react = __commonJS({
|
||||||
|
"external-global-plugin:react"(exports, module) {
|
||||||
|
module.exports = Spicetify.React;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// src/extensions/config_wrapper.tsx
|
||||||
|
var config_wrapper_exports = {};
|
||||||
|
__export(config_wrapper_exports, {
|
||||||
|
default: () => config_wrapper_default
|
||||||
|
});
|
||||||
|
var import_react2 = __toESM(require_react());
|
||||||
|
|
||||||
|
// src/components/config/config_modal.tsx
|
||||||
|
var import_react = __toESM(require_react());
|
||||||
|
var TextInput = (props) => {
|
||||||
|
const handleTextChange = (event) => {
|
||||||
|
props.callback(event.target.value);
|
||||||
|
};
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement("label", {
|
||||||
|
className: "text-input-wrapper"
|
||||||
|
}, /* @__PURE__ */ import_react.default.createElement("input", {
|
||||||
|
className: "text-input",
|
||||||
|
type: "text",
|
||||||
|
value: props.value || "",
|
||||||
|
"data-storage-key": props.storageKey,
|
||||||
|
placeholder: props.placeholder,
|
||||||
|
id: `text-input:${props.storageKey}`,
|
||||||
|
title: `Text input for ${props.storageKey}`,
|
||||||
|
onChange: handleTextChange
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
var Dropdown = (props) => {
|
||||||
|
const handleDropdownChange = (event) => {
|
||||||
|
props.callback(event.target.value);
|
||||||
|
};
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement("label", {
|
||||||
|
className: "dropdown-wrapper"
|
||||||
|
}, /* @__PURE__ */ import_react.default.createElement("select", {
|
||||||
|
className: "dropdown-input",
|
||||||
|
value: props.value,
|
||||||
|
"data-storage-key": props.storageKey,
|
||||||
|
id: `dropdown:${props.storageKey}`,
|
||||||
|
title: `Dropdown for ${props.storageKey}`,
|
||||||
|
onChange: handleDropdownChange
|
||||||
|
}, props.options.map((option, index) => /* @__PURE__ */ import_react.default.createElement("option", {
|
||||||
|
key: index,
|
||||||
|
value: option
|
||||||
|
}, option))));
|
||||||
|
};
|
||||||
|
var ToggleInput = (props) => {
|
||||||
|
const { Toggle } = Spicetify.ReactComponent;
|
||||||
|
const handleToggleChange = (newValue) => {
|
||||||
|
props.callback(newValue);
|
||||||
|
};
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement(Toggle, {
|
||||||
|
id: `toggle:${props.storageKey}`,
|
||||||
|
value: props.value,
|
||||||
|
onSelected: (newValue) => handleToggleChange(newValue)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
var SliderInput = (props) => {
|
||||||
|
const { Slider } = Spicetify.ReactComponent;
|
||||||
|
const handleSliderChange = (newValue) => {
|
||||||
|
const calculatedValue = props.min + newValue * (props.max - props.min);
|
||||||
|
props.callback(calculatedValue);
|
||||||
|
};
|
||||||
|
const value = (props.value - props.min) / (props.max - props.min);
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement(Slider, {
|
||||||
|
id: `slider:${props.storageKey}`,
|
||||||
|
value,
|
||||||
|
min: 0,
|
||||||
|
max: 1,
|
||||||
|
step: 0.1,
|
||||||
|
onDragMove: (newValue) => handleSliderChange(newValue),
|
||||||
|
onDragStart: () => {
|
||||||
|
},
|
||||||
|
onDragEnd: () => {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
var TooltipIcon = () => {
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement("svg", {
|
||||||
|
role: "img",
|
||||||
|
height: "16",
|
||||||
|
width: "16",
|
||||||
|
className: "Svg-sc-ytk21e-0 uPxdw nW1RKQOkzcJcX6aDCZB4",
|
||||||
|
viewBox: "0 0 16 16"
|
||||||
|
}, /* @__PURE__ */ import_react.default.createElement("path", {
|
||||||
|
d: "M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8z"
|
||||||
|
}), /* @__PURE__ */ import_react.default.createElement("path", {
|
||||||
|
d: "M7.25 12.026v-1.5h1.5v1.5h-1.5zm.884-7.096A1.125 1.125 0 007.06 6.39l-1.431.448a2.625 2.625 0 115.13-.784c0 .54-.156 1.015-.503 1.488-.3.408-.7.652-.973.818l-.112.068c-.185.116-.26.203-.302.283-.046.087-.097.245-.097.57h-1.5c0-.47.072-.898.274-1.277.206-.385.507-.645.827-.846l.147-.092c.285-.177.413-.257.526-.41.169-.23.213-.397.213-.602 0-.622-.503-1.125-1.125-1.125z"
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
var ConfigRow = (props) => {
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement("div", {
|
||||||
|
className: "setting-row"
|
||||||
|
}, /* @__PURE__ */ import_react.default.createElement("label", {
|
||||||
|
className: "col description"
|
||||||
|
}, props.name, props.desc && /* @__PURE__ */ import_react.default.createElement(Spicetify.ReactComponent.TooltipWrapper, {
|
||||||
|
label: /* @__PURE__ */ import_react.default.createElement("div", {
|
||||||
|
dangerouslySetInnerHTML: { __html: props.desc }
|
||||||
|
}),
|
||||||
|
renderInline: true,
|
||||||
|
showDelay: 10,
|
||||||
|
placement: "top",
|
||||||
|
labelClassName: "tooltip",
|
||||||
|
disabled: false
|
||||||
|
}, /* @__PURE__ */ import_react.default.createElement("div", {
|
||||||
|
className: "tooltip-icon"
|
||||||
|
}, /* @__PURE__ */ import_react.default.createElement(TooltipIcon, null)))), /* @__PURE__ */ import_react.default.createElement("div", {
|
||||||
|
className: "col action"
|
||||||
|
}, props.children));
|
||||||
|
};
|
||||||
|
var ConfigModal = (props) => {
|
||||||
|
const { config, structure, updateAppConfig } = props;
|
||||||
|
const [modalConfig, setModalConfig] = import_react.default.useState(__spreadValues({}, config));
|
||||||
|
const modalRows = structure.map((modalRow, index) => {
|
||||||
|
const key = modalRow.key;
|
||||||
|
const currentValue = modalConfig[key];
|
||||||
|
const updateItem = (state) => {
|
||||||
|
console.debug(`toggling ${key} to ${state}`);
|
||||||
|
localStorage.setItem(`library:config:${key}`, String(state));
|
||||||
|
if (modalRow.callback)
|
||||||
|
modalRow.callback(state);
|
||||||
|
const newConfig = __spreadValues({}, modalConfig);
|
||||||
|
newConfig[key] = state;
|
||||||
|
updateAppConfig(newConfig);
|
||||||
|
setModalConfig(newConfig);
|
||||||
|
};
|
||||||
|
const header = modalRow.sectionHeader;
|
||||||
|
const element = () => {
|
||||||
|
switch (modalRow.type) {
|
||||||
|
case "toggle":
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement(ToggleInput, {
|
||||||
|
storageKey: key,
|
||||||
|
value: currentValue,
|
||||||
|
callback: updateItem
|
||||||
|
});
|
||||||
|
case "text":
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement(TextInput, {
|
||||||
|
storageKey: key,
|
||||||
|
value: currentValue,
|
||||||
|
callback: updateItem
|
||||||
|
});
|
||||||
|
case "dropdown":
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement(Dropdown, {
|
||||||
|
storageKey: key,
|
||||||
|
value: currentValue,
|
||||||
|
options: modalRow.options,
|
||||||
|
callback: updateItem
|
||||||
|
});
|
||||||
|
case "slider":
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement(SliderInput, {
|
||||||
|
storageKey: key,
|
||||||
|
value: currentValue,
|
||||||
|
min: modalRow.min,
|
||||||
|
max: modalRow.max,
|
||||||
|
step: modalRow.step,
|
||||||
|
callback: updateItem
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, header && index !== 0 && /* @__PURE__ */ import_react.default.createElement("br", null), header && /* @__PURE__ */ import_react.default.createElement("h2", {
|
||||||
|
className: "section-header"
|
||||||
|
}, modalRow.sectionHeader), /* @__PURE__ */ import_react.default.createElement(ConfigRow, {
|
||||||
|
name: modalRow.name,
|
||||||
|
desc: modalRow.desc
|
||||||
|
}, element()));
|
||||||
|
});
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement("div", {
|
||||||
|
id: "library-config-container"
|
||||||
|
}, modalRows);
|
||||||
|
};
|
||||||
|
var config_modal_default = ConfigModal;
|
||||||
|
|
||||||
|
// src/extensions/config_wrapper.tsx
|
||||||
|
var _ConfigWrapper = class {
|
||||||
|
constructor(modalStructure) {
|
||||||
|
const config = modalStructure.map((modalStructureRow) => {
|
||||||
|
var _a;
|
||||||
|
const value = _ConfigWrapper.getLocalStorageDataFromKey(`library:config:${modalStructureRow.key}`, modalStructureRow.def);
|
||||||
|
(_a = modalStructureRow.callback) == null ? void 0 : _a.call(modalStructureRow, value);
|
||||||
|
return { [modalStructureRow.key]: value };
|
||||||
|
});
|
||||||
|
this.Config = Object.assign({}, ...config);
|
||||||
|
this.launchModal = (callback) => {
|
||||||
|
const updateConfig = (config2) => {
|
||||||
|
this.Config = __spreadValues({}, config2);
|
||||||
|
callback == null ? void 0 : callback(config2);
|
||||||
|
};
|
||||||
|
Spicetify.PopupModal.display({
|
||||||
|
title: "Library Settings",
|
||||||
|
content: /* @__PURE__ */ import_react2.default.createElement(config_modal_default, {
|
||||||
|
config: this.Config,
|
||||||
|
structure: modalStructure,
|
||||||
|
updateAppConfig: updateConfig
|
||||||
|
}),
|
||||||
|
isLarge: true
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var ConfigWrapper = _ConfigWrapper;
|
||||||
|
ConfigWrapper.getLocalStorageDataFromKey = (key, fallback) => {
|
||||||
|
const data = localStorage.getItem(key);
|
||||||
|
if (data) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(data);
|
||||||
|
} catch (err) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var config_wrapper_default = ConfigWrapper;
|
||||||
|
return __toCommonJS(config_wrapper_exports);
|
||||||
|
})();
|
||||||
|
|
||||||
|
})();
|
19
.config/spicetify/CustomApps/library/context_menu_handler.js
Normal file
19
.config/spicetify/CustomApps/library/context_menu_handler.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
(async function() {
|
||||||
|
while (!Spicetify.React || !Spicetify.ReactDOM) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 10));
|
||||||
|
}
|
||||||
|
"use strict";
|
||||||
|
var library = (() => {
|
||||||
|
// src/extensions/context_menu_handler.tsx
|
||||||
|
var observer = new MutationObserver((mutations) => {
|
||||||
|
mutations.forEach((mutation) => {
|
||||||
|
if (mutation.addedNodes.length) {
|
||||||
|
const node = mutation.addedNodes[0];
|
||||||
|
console.log(node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
observer.observe(document.body, { childList: true, subtree: false });
|
||||||
|
})();
|
||||||
|
|
||||||
|
})();
|
926
.config/spicetify/CustomApps/library/extension.js
Normal file
926
.config/spicetify/CustomApps/library/extension.js
Normal file
|
@ -0,0 +1,926 @@
|
||||||
|
(async function() {
|
||||||
|
while (!Spicetify.React || !Spicetify.ReactDOM) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 10));
|
||||||
|
}
|
||||||
|
"use strict";
|
||||||
|
var library = (() => {
|
||||||
|
var __create = Object.create;
|
||||||
|
var __defProp = Object.defineProperty;
|
||||||
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||||
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||||
|
var __getProtoOf = Object.getPrototypeOf;
|
||||||
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||||
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
||||||
|
var __commonJS = (cb, mod) => function __require() {
|
||||||
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
||||||
|
};
|
||||||
|
var __copyProps = (to, from, except, desc) => {
|
||||||
|
if (from && typeof from === "object" || typeof from === "function") {
|
||||||
|
for (let key of __getOwnPropNames(from))
|
||||||
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||||
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||||
|
}
|
||||||
|
return to;
|
||||||
|
};
|
||||||
|
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||||
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||||
|
mod
|
||||||
|
));
|
||||||
|
var __publicField = (obj, key, value) => {
|
||||||
|
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
// external-global-plugin:react
|
||||||
|
var require_react = __commonJS({
|
||||||
|
"external-global-plugin:react"(exports, module) {
|
||||||
|
module.exports = Spicetify.React;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// external-global-plugin:react-dom
|
||||||
|
var require_react_dom = __commonJS({
|
||||||
|
"external-global-plugin:react-dom"(exports, module) {
|
||||||
|
module.exports = Spicetify.ReactDOM;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ../shared/config/config_wrapper.tsx
|
||||||
|
var import_react2 = __toESM(require_react());
|
||||||
|
|
||||||
|
// ../shared/config/config_modal.tsx
|
||||||
|
var import_react = __toESM(require_react());
|
||||||
|
var TextInput = (props) => {
|
||||||
|
const handleTextChange = (event) => {
|
||||||
|
props.callback(event.target.value);
|
||||||
|
};
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement("label", {
|
||||||
|
className: "text-input-wrapper"
|
||||||
|
}, /* @__PURE__ */ import_react.default.createElement("input", {
|
||||||
|
className: "text-input",
|
||||||
|
type: "text",
|
||||||
|
value: props.value || "",
|
||||||
|
"data-storage-key": props.storageKey,
|
||||||
|
placeholder: props.placeholder,
|
||||||
|
id: `text-input:${props.storageKey}`,
|
||||||
|
title: `Text input for ${props.storageKey}`,
|
||||||
|
onChange: handleTextChange
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
var Dropdown = (props) => {
|
||||||
|
const handleDropdownChange = (event) => {
|
||||||
|
props.callback(event.target.value);
|
||||||
|
};
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement("label", {
|
||||||
|
className: "dropdown-wrapper"
|
||||||
|
}, /* @__PURE__ */ import_react.default.createElement("select", {
|
||||||
|
className: "dropdown-input",
|
||||||
|
value: props.value,
|
||||||
|
"data-storage-key": props.storageKey,
|
||||||
|
id: `dropdown:${props.storageKey}`,
|
||||||
|
title: `Dropdown for ${props.storageKey}`,
|
||||||
|
onChange: handleDropdownChange
|
||||||
|
}, props.options.map((option, index) => /* @__PURE__ */ import_react.default.createElement("option", {
|
||||||
|
key: index,
|
||||||
|
value: option
|
||||||
|
}, option))));
|
||||||
|
};
|
||||||
|
var ToggleInput = (props) => {
|
||||||
|
const { Toggle } = Spicetify.ReactComponent;
|
||||||
|
const handleToggleChange = (newValue) => {
|
||||||
|
props.callback(newValue);
|
||||||
|
};
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement(Toggle, {
|
||||||
|
id: `toggle:${props.storageKey}`,
|
||||||
|
value: props.value,
|
||||||
|
onSelected: (newValue) => handleToggleChange(newValue)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
var SliderInput = (props) => {
|
||||||
|
const { Slider } = Spicetify.ReactComponent;
|
||||||
|
const handleSliderChange = (newValue) => {
|
||||||
|
const calculatedValue = props.min + newValue * (props.max - props.min);
|
||||||
|
props.callback(calculatedValue);
|
||||||
|
};
|
||||||
|
const value = (props.value - props.min) / (props.max - props.min);
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement(Slider, {
|
||||||
|
id: `slider:${props.storageKey}`,
|
||||||
|
value,
|
||||||
|
min: 0,
|
||||||
|
max: 1,
|
||||||
|
step: 0.1,
|
||||||
|
onDragMove: (newValue) => handleSliderChange(newValue),
|
||||||
|
onDragStart: () => {
|
||||||
|
},
|
||||||
|
onDragEnd: () => {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
var TooltipIcon = () => {
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement("svg", {
|
||||||
|
role: "img",
|
||||||
|
height: "16",
|
||||||
|
width: "16",
|
||||||
|
className: "Svg-sc-ytk21e-0 uPxdw nW1RKQOkzcJcX6aDCZB4",
|
||||||
|
viewBox: "0 0 16 16"
|
||||||
|
}, /* @__PURE__ */ import_react.default.createElement("path", {
|
||||||
|
d: "M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8z"
|
||||||
|
}), /* @__PURE__ */ import_react.default.createElement("path", {
|
||||||
|
d: "M7.25 12.026v-1.5h1.5v1.5h-1.5zm.884-7.096A1.125 1.125 0 007.06 6.39l-1.431.448a2.625 2.625 0 115.13-.784c0 .54-.156 1.015-.503 1.488-.3.408-.7.652-.973.818l-.112.068c-.185.116-.26.203-.302.283-.046.087-.097.245-.097.57h-1.5c0-.47.072-.898.274-1.277.206-.385.507-.645.827-.846l.147-.092c.285-.177.413-.257.526-.41.169-.23.213-.397.213-.602 0-.622-.503-1.125-1.125-1.125z"
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
var ConfigRow = (props) => {
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement("div", {
|
||||||
|
className: "setting-row"
|
||||||
|
}, /* @__PURE__ */ import_react.default.createElement("label", {
|
||||||
|
className: "col description"
|
||||||
|
}, props.name, props.desc && /* @__PURE__ */ import_react.default.createElement(Spicetify.ReactComponent.TooltipWrapper, {
|
||||||
|
label: /* @__PURE__ */ import_react.default.createElement("div", {
|
||||||
|
dangerouslySetInnerHTML: { __html: props.desc }
|
||||||
|
}),
|
||||||
|
renderInline: true,
|
||||||
|
showDelay: 10,
|
||||||
|
placement: "top",
|
||||||
|
labelClassName: "tooltip",
|
||||||
|
disabled: false
|
||||||
|
}, /* @__PURE__ */ import_react.default.createElement("div", {
|
||||||
|
className: "tooltip-icon"
|
||||||
|
}, /* @__PURE__ */ import_react.default.createElement(TooltipIcon, null)))), /* @__PURE__ */ import_react.default.createElement("div", {
|
||||||
|
className: "col action"
|
||||||
|
}, props.children));
|
||||||
|
};
|
||||||
|
var ConfigModal = (props) => {
|
||||||
|
const { config, structure, appKey, updateAppConfig } = props;
|
||||||
|
const [modalConfig, setModalConfig] = import_react.default.useState({ ...config });
|
||||||
|
const modalRows = structure.map((modalRow, index) => {
|
||||||
|
const key = modalRow.key;
|
||||||
|
const currentValue = modalConfig[key];
|
||||||
|
const updateItem = (state) => {
|
||||||
|
console.debug(`toggling ${key} to ${state}`);
|
||||||
|
localStorage.setItem(`${appKey}:config:${key}`, String(state));
|
||||||
|
if (modalRow.callback)
|
||||||
|
modalRow.callback(state);
|
||||||
|
const newConfig = { ...modalConfig };
|
||||||
|
newConfig[key] = state;
|
||||||
|
updateAppConfig(newConfig);
|
||||||
|
setModalConfig(newConfig);
|
||||||
|
};
|
||||||
|
const header = modalRow.sectionHeader;
|
||||||
|
const element = () => {
|
||||||
|
switch (modalRow.type) {
|
||||||
|
case "toggle":
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement(ToggleInput, {
|
||||||
|
storageKey: key,
|
||||||
|
value: currentValue,
|
||||||
|
callback: updateItem
|
||||||
|
});
|
||||||
|
case "text":
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement(TextInput, {
|
||||||
|
storageKey: key,
|
||||||
|
value: currentValue,
|
||||||
|
callback: updateItem
|
||||||
|
});
|
||||||
|
case "dropdown":
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement(Dropdown, {
|
||||||
|
storageKey: key,
|
||||||
|
value: currentValue,
|
||||||
|
options: modalRow.options,
|
||||||
|
callback: updateItem
|
||||||
|
});
|
||||||
|
case "slider":
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement(SliderInput, {
|
||||||
|
storageKey: key,
|
||||||
|
value: currentValue,
|
||||||
|
min: modalRow.min,
|
||||||
|
max: modalRow.max,
|
||||||
|
step: modalRow.step,
|
||||||
|
callback: updateItem
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, header && index !== 0 && /* @__PURE__ */ import_react.default.createElement("br", null), header && /* @__PURE__ */ import_react.default.createElement("h2", {
|
||||||
|
className: "section-header"
|
||||||
|
}, modalRow.sectionHeader), /* @__PURE__ */ import_react.default.createElement(ConfigRow, {
|
||||||
|
name: modalRow.name,
|
||||||
|
desc: modalRow.desc
|
||||||
|
}, element()));
|
||||||
|
});
|
||||||
|
return /* @__PURE__ */ import_react.default.createElement("div", {
|
||||||
|
className: "config-container"
|
||||||
|
}, modalRows);
|
||||||
|
};
|
||||||
|
var config_modal_default = ConfigModal;
|
||||||
|
|
||||||
|
// ../shared/config/config_wrapper.tsx
|
||||||
|
var _ConfigWrapper = class {
|
||||||
|
Config;
|
||||||
|
launchModal;
|
||||||
|
constructor(modalStructure, key) {
|
||||||
|
const config = modalStructure.map((modalStructureRow) => {
|
||||||
|
const value = _ConfigWrapper.getLocalStorageDataFromKey(
|
||||||
|
`${key}:config:${modalStructureRow.key}`,
|
||||||
|
modalStructureRow.def
|
||||||
|
);
|
||||||
|
modalStructureRow.callback?.(value);
|
||||||
|
return { [modalStructureRow.key]: value };
|
||||||
|
});
|
||||||
|
this.Config = Object.assign({}, ...config);
|
||||||
|
this.launchModal = (callback) => {
|
||||||
|
const updateConfig = (config2) => {
|
||||||
|
this.Config = { ...config2 };
|
||||||
|
callback?.(config2);
|
||||||
|
};
|
||||||
|
Spicetify.PopupModal.display({
|
||||||
|
title: `${key.charAt(0).toUpperCase() + key.slice(1)} Settings`,
|
||||||
|
content: /* @__PURE__ */ import_react2.default.createElement(config_modal_default, {
|
||||||
|
config: this.Config,
|
||||||
|
structure: modalStructure,
|
||||||
|
appKey: key,
|
||||||
|
updateAppConfig: updateConfig
|
||||||
|
}),
|
||||||
|
isLarge: true
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var ConfigWrapper = _ConfigWrapper;
|
||||||
|
__publicField(ConfigWrapper, "getLocalStorageDataFromKey", (key, fallback) => {
|
||||||
|
const data = localStorage.getItem(key);
|
||||||
|
if (data) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(data);
|
||||||
|
} catch (err) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var config_wrapper_default = ConfigWrapper;
|
||||||
|
|
||||||
|
// src/extensions/extension.tsx
|
||||||
|
var import_react10 = __toESM(require_react());
|
||||||
|
var import_react_dom = __toESM(require_react_dom());
|
||||||
|
|
||||||
|
// src/components/toggle_filters.tsx
|
||||||
|
var import_react3 = __toESM(require_react());
|
||||||
|
var UpIcon = () => {
|
||||||
|
const { IconComponent } = Spicetify.ReactComponent;
|
||||||
|
return /* @__PURE__ */ import_react3.default.createElement(IconComponent, {
|
||||||
|
semanticColor: "textSubdued",
|
||||||
|
dangerouslySetInnerHTML: {
|
||||||
|
__html: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M.998 8.81A.749.749 0 0 1 .47 7.53L7.99 0l7.522 7.53a.75.75 0 1 1-1.06 1.06L8.74 2.87v12.38a.75.75 0 1 1-1.498 0V2.87L1.528 8.59a.751.751 0 0 1-.53.22z"></path></svg>'
|
||||||
|
},
|
||||||
|
iconSize: 16
|
||||||
|
});
|
||||||
|
};
|
||||||
|
var DownIcon = () => {
|
||||||
|
const { IconComponent } = Spicetify.ReactComponent;
|
||||||
|
return /* @__PURE__ */ import_react3.default.createElement(IconComponent, {
|
||||||
|
semanticColor: "textSubdued",
|
||||||
|
dangerouslySetInnerHTML: {
|
||||||
|
__html: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M.998 7.19A.749.749 0 0 0 .47 8.47L7.99 16l7.522-7.53a.75.75 0 1 0-1.06-1.06L8.74 13.13V.75a.75.75 0 1 0-1.498 0v12.38L1.528 7.41a.749.749 0 0 0-.53-.22z"></path></svg>'
|
||||||
|
},
|
||||||
|
iconSize: 16
|
||||||
|
});
|
||||||
|
};
|
||||||
|
var ToggleFiltersButton = () => {
|
||||||
|
const [direction, setDirection] = import_react3.default.useState(document.body.classList.contains("show-ylx-filters") ? "up" : "down");
|
||||||
|
const { ButtonTertiary } = Spicetify.ReactComponent;
|
||||||
|
const toggleDirection = () => {
|
||||||
|
if (direction === "down") {
|
||||||
|
document.body.classList.add("show-ylx-filters");
|
||||||
|
setDirection("up");
|
||||||
|
} else {
|
||||||
|
setDirection("down");
|
||||||
|
document.body.classList.remove("show-ylx-filters");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const Icon = direction === "down" ? DownIcon : UpIcon;
|
||||||
|
return /* @__PURE__ */ import_react3.default.createElement(ButtonTertiary, {
|
||||||
|
buttonSize: "sm",
|
||||||
|
"aria-label": "Show Filters",
|
||||||
|
iconOnly: Icon,
|
||||||
|
onClick: toggleDirection
|
||||||
|
});
|
||||||
|
};
|
||||||
|
var toggle_filters_default = ToggleFiltersButton;
|
||||||
|
|
||||||
|
// src/components/collapse_button.tsx
|
||||||
|
var import_react4 = __toESM(require_react());
|
||||||
|
var collapseLibrary = () => {
|
||||||
|
Spicetify.Platform.LocalStorageAPI.setItem("ylx-sidebar-state", 1);
|
||||||
|
};
|
||||||
|
var CollapseIcon = () => {
|
||||||
|
const { IconComponent } = Spicetify.ReactComponent;
|
||||||
|
return /* @__PURE__ */ import_react4.default.createElement(IconComponent, {
|
||||||
|
semanticColor: "textSubdued",
|
||||||
|
dangerouslySetInnerHTML: {
|
||||||
|
__html: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M8.81 1A.749.749 0 0 0 7.53.47L0 7.99l7.53 7.521a.75.75 0 0 0 1.234-.815.75.75 0 0 0-.174-.243L2.87 8.74h12.38a.75.75 0 1 0 0-1.498H2.87l5.72-5.713c.14-.14.22-.331.22-.53z"></path></svg>'
|
||||||
|
},
|
||||||
|
iconSize: 16
|
||||||
|
});
|
||||||
|
};
|
||||||
|
var CollapseButton = () => {
|
||||||
|
const { ButtonTertiary } = Spicetify.ReactComponent;
|
||||||
|
return /* @__PURE__ */ import_react4.default.createElement(ButtonTertiary, {
|
||||||
|
buttonSize: "sm",
|
||||||
|
"aria-label": "Show Filters",
|
||||||
|
iconOnly: CollapseIcon,
|
||||||
|
onClick: collapseLibrary
|
||||||
|
});
|
||||||
|
};
|
||||||
|
var collapse_button_default = CollapseButton;
|
||||||
|
|
||||||
|
// src/components/expand_button.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, {
|
||||||
|
semanticColor: "textSubdued",
|
||||||
|
dangerouslySetInnerHTML: {
|
||||||
|
__html: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" ><path d="M7.19 1A.749.749 0 0 1 8.47.47L16 7.99l-7.53 7.521a.75.75 0 0 1-1.234-.815.75.75 0 0 1 .174-.243l5.72-5.714H.75a.75.75 0 1 1 0-1.498h12.38L7.41 1.529a.749.749 0 0 1-.22-.53z"></path></svg>'
|
||||||
|
},
|
||||||
|
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 expand_button_default = ExpandButton;
|
||||||
|
|
||||||
|
// ../node_modules/uuid/dist/esm-browser/rng.js
|
||||||
|
var getRandomValues;
|
||||||
|
var rnds8 = new Uint8Array(16);
|
||||||
|
function rng() {
|
||||||
|
if (!getRandomValues) {
|
||||||
|
getRandomValues = typeof crypto !== "undefined" && crypto.getRandomValues && crypto.getRandomValues.bind(crypto);
|
||||||
|
if (!getRandomValues) {
|
||||||
|
throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getRandomValues(rnds8);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ../node_modules/uuid/dist/esm-browser/stringify.js
|
||||||
|
var byteToHex = [];
|
||||||
|
for (let i = 0; i < 256; ++i) {
|
||||||
|
byteToHex.push((i + 256).toString(16).slice(1));
|
||||||
|
}
|
||||||
|
function unsafeStringify(arr, offset = 0) {
|
||||||
|
return byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ../node_modules/uuid/dist/esm-browser/native.js
|
||||||
|
var randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto);
|
||||||
|
var native_default = {
|
||||||
|
randomUUID
|
||||||
|
};
|
||||||
|
|
||||||
|
// ../node_modules/uuid/dist/esm-browser/v4.js
|
||||||
|
function v4(options, buf, offset) {
|
||||||
|
if (native_default.randomUUID && !buf && !options) {
|
||||||
|
return native_default.randomUUID();
|
||||||
|
}
|
||||||
|
options = options || {};
|
||||||
|
const rnds = options.random || (options.rng || rng)();
|
||||||
|
rnds[6] = rnds[6] & 15 | 64;
|
||||||
|
rnds[8] = rnds[8] & 63 | 128;
|
||||||
|
if (buf) {
|
||||||
|
offset = offset || 0;
|
||||||
|
for (let i = 0; i < 16; ++i) {
|
||||||
|
buf[offset + i] = rnds[i];
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
return unsafeStringify(rnds);
|
||||||
|
}
|
||||||
|
var v4_default = v4;
|
||||||
|
|
||||||
|
// src/extensions/collections_wrapper.ts
|
||||||
|
var CollectionWrapper = class extends EventTarget {
|
||||||
|
_collections;
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this._collections = JSON.parse(localStorage.getItem("library:collections") || "[]");
|
||||||
|
}
|
||||||
|
saveCollections() {
|
||||||
|
localStorage.setItem("library:collections", JSON.stringify(this._collections));
|
||||||
|
this.dispatchEvent(new CustomEvent("update", { detail: this._collections }));
|
||||||
|
}
|
||||||
|
getCollection(uri) {
|
||||||
|
return this._collections.find((collection) => collection.uri === uri);
|
||||||
|
}
|
||||||
|
async requestAlbums({ sortOrder, textFilter }) {
|
||||||
|
const albums = await Spicetify.Platform.LibraryAPI.getContents({
|
||||||
|
filters: ["0"],
|
||||||
|
sortOrder,
|
||||||
|
textFilter,
|
||||||
|
offset: 0,
|
||||||
|
limit: 9999
|
||||||
|
});
|
||||||
|
return albums;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
if (textFilter) {
|
||||||
|
let regex = new RegExp("\\b" + textFilter, "i");
|
||||||
|
collectionItems = collectionItems.filter((item) => {
|
||||||
|
return regex.test(item.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (offset > 0)
|
||||||
|
collectionItems = [];
|
||||||
|
return {
|
||||||
|
openedCollection,
|
||||||
|
items: [...collectionItems, ...albumItems.slice(offset, offset + limit)],
|
||||||
|
totalLength: albumItems.length + collectionItems.length,
|
||||||
|
unfilteredLength
|
||||||
|
};
|
||||||
|
}
|
||||||
|
createCollection(name, parentCollection = "") {
|
||||||
|
const uri = v4_default();
|
||||||
|
const collection = {
|
||||||
|
type: "collection",
|
||||||
|
uri,
|
||||||
|
name,
|
||||||
|
items: [],
|
||||||
|
totalLength: 0,
|
||||||
|
imgUrl: "",
|
||||||
|
parentCollection
|
||||||
|
};
|
||||||
|
this._collections.push(collection);
|
||||||
|
this.saveCollections();
|
||||||
|
Spicetify.showNotification("Collection created");
|
||||||
|
}
|
||||||
|
deleteCollection(uri) {
|
||||||
|
this._collections = this._collections.filter((collection) => collection.uri !== uri);
|
||||||
|
this.saveCollections();
|
||||||
|
Spicetify.showNotification("Collection deleted");
|
||||||
|
}
|
||||||
|
async addAlbumToCollection(collectionUri, albumUri) {
|
||||||
|
const collection = this.getCollection(collectionUri);
|
||||||
|
if (!collection)
|
||||||
|
return;
|
||||||
|
collection.items.push(albumUri);
|
||||||
|
collection.totalLength++;
|
||||||
|
this.saveCollections();
|
||||||
|
Spicetify.showNotification("Album added to collection");
|
||||||
|
}
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
getCollectionsWithAlbum(albumUri) {
|
||||||
|
return this._collections.filter((collection) => {
|
||||||
|
return collection.items.some((item) => item === albumUri);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
renameCollection(uri, newName) {
|
||||||
|
const collection = this.getCollection(uri);
|
||||||
|
if (!collection)
|
||||||
|
return;
|
||||||
|
collection.name = newName;
|
||||||
|
this.saveCollections();
|
||||||
|
Spicetify.showNotification("Collection renamed");
|
||||||
|
}
|
||||||
|
setCollectionImage(uri, imgUrl) {
|
||||||
|
const collection = this.getCollection(uri);
|
||||||
|
if (!collection)
|
||||||
|
return;
|
||||||
|
collection.imgUrl = imgUrl;
|
||||||
|
this.saveCollections();
|
||||||
|
Spicetify.showNotification("Collection image set");
|
||||||
|
}
|
||||||
|
removeCollectionImage(uri) {
|
||||||
|
const collection = this.getCollection(uri);
|
||||||
|
if (!collection)
|
||||||
|
return;
|
||||||
|
collection.imgUrl = "";
|
||||||
|
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: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">${path}</svg>`
|
||||||
|
},
|
||||||
|
iconSize: 16
|
||||||
|
});
|
||||||
|
};
|
||||||
|
var leading_icon_default = LeadingIcon;
|
||||||
|
|
||||||
|
// src/components/text_input_dialog.tsx
|
||||||
|
var import_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;
|
||||||
|
|
||||||
|
// src/extensions/folder_image_wrapper.ts
|
||||||
|
var FolderImageWrapper = class extends EventTarget {
|
||||||
|
_folderImages;
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this._folderImages = JSON.parse(localStorage.getItem("library:folderImages") || "{}");
|
||||||
|
}
|
||||||
|
getFolderImage(uri) {
|
||||||
|
return this._folderImages[uri];
|
||||||
|
}
|
||||||
|
getFolderImages() {
|
||||||
|
return this._folderImages;
|
||||||
|
}
|
||||||
|
setFolderImage({ uri, url }) {
|
||||||
|
this._folderImages[uri] = url;
|
||||||
|
this.saveFolderImages();
|
||||||
|
Spicetify.showNotification("Folder image updated");
|
||||||
|
}
|
||||||
|
removeFolderImage(uri) {
|
||||||
|
delete this._folderImages[uri];
|
||||||
|
this.saveFolderImages();
|
||||||
|
Spicetify.showNotification("Folder image removed");
|
||||||
|
}
|
||||||
|
saveFolderImages() {
|
||||||
|
this.dispatchEvent(new CustomEvent("update", { detail: this._folderImages }));
|
||||||
|
localStorage.setItem("library:folderImages", JSON.stringify(this._folderImages));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var folder_image_wrapper_default = FolderImageWrapper;
|
||||||
|
|
||||||
|
// src/extensions/extension.tsx
|
||||||
|
var styleLink = document.createElement("link");
|
||||||
|
styleLink.rel = "stylesheet";
|
||||||
|
styleLink.href = "/spicetify-routes-library.css";
|
||||||
|
document.head.appendChild(styleLink);
|
||||||
|
var setCardSize = (size) => {
|
||||||
|
document.documentElement.style.setProperty("--library-card-size", `${size}px`);
|
||||||
|
};
|
||||||
|
var setSearchBarSize = (enlarged) => {
|
||||||
|
const size = enlarged ? 300 : 200;
|
||||||
|
document.documentElement.style.setProperty("--library-searchbar-size", `${size}px`);
|
||||||
|
};
|
||||||
|
var FolderImage = ({ url }) => {
|
||||||
|
return /* @__PURE__ */ import_react10.default.createElement("img", {
|
||||||
|
"aria-hidden": "true",
|
||||||
|
draggable: "false",
|
||||||
|
loading: "eager",
|
||||||
|
src: url,
|
||||||
|
className: "main-image-image x-entityImage-image main-image-loading main-image-loaded"
|
||||||
|
});
|
||||||
|
};
|
||||||
|
var FolderPlaceholder = () => {
|
||||||
|
return /* @__PURE__ */ import_react10.default.createElement("div", {
|
||||||
|
className: "x-entityImage-imagePlaceholder"
|
||||||
|
}, /* @__PURE__ */ import_react10.default.createElement("svg", {
|
||||||
|
"data-encore-id": "icon",
|
||||||
|
role: "img",
|
||||||
|
"aria-hidden": "true",
|
||||||
|
className: "Svg-sc-ytk21e-0 Svg-img-icon-medium",
|
||||||
|
viewBox: "0 0 24 24"
|
||||||
|
}, /* @__PURE__ */ import_react10.default.createElement("path", {
|
||||||
|
d: "M1 4a2 2 0 0 1 2-2h5.155a3 3 0 0 1 2.598 1.5l.866 1.5H21a2 2 0 0 1 2 2v13a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V4zm7.155 0H3v16h18V7H10.464L9.021 4.5a1 1 0 0 0-.866-.5z"
|
||||||
|
})));
|
||||||
|
};
|
||||||
|
var SpicetifyLibrary2 = class {
|
||||||
|
ConfigWrapper = new config_wrapper_default(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: "Card Size",
|
||||||
|
key: "cardSize",
|
||||||
|
type: "slider",
|
||||||
|
min: 100,
|
||||||
|
max: 200,
|
||||||
|
step: 0.05,
|
||||||
|
def: 180,
|
||||||
|
callback: setCardSize
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Extend Search Bar",
|
||||||
|
key: "extendSearchBar",
|
||||||
|
type: "toggle",
|
||||||
|
def: false,
|
||||||
|
callback: setSearchBarSize
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"library"
|
||||||
|
);
|
||||||
|
CollectionWrapper = new collections_wrapper_default();
|
||||||
|
FolderImageWrapper = new folder_image_wrapper_default();
|
||||||
|
};
|
||||||
|
window.SpicetifyLibrary = new SpicetifyLibrary2();
|
||||||
|
(function wait() {
|
||||||
|
const { LocalStorageAPI } = Spicetify?.Platform;
|
||||||
|
if (!LocalStorageAPI) {
|
||||||
|
setTimeout(wait, 100);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
main(LocalStorageAPI);
|
||||||
|
})();
|
||||||
|
function main(LocalStorageAPI) {
|
||||||
|
const isAlbum = (props) => {
|
||||||
|
return props.uri?.includes("album");
|
||||||
|
};
|
||||||
|
Spicetify.ContextMenuV2.registerItem(/* @__PURE__ */ import_react10.default.createElement(album_menu_item_default, null), isAlbum);
|
||||||
|
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);
|
||||||
|
if (uri?.includes("folder")) {
|
||||||
|
const imageBox = el.querySelector(".x-entityImage-imageContainer");
|
||||||
|
if (!imageBox)
|
||||||
|
return;
|
||||||
|
const imageUrl = window.SpicetifyLibrary.FolderImageWrapper.getFolderImage(uri);
|
||||||
|
if (!imageUrl)
|
||||||
|
import_react_dom.default.render(/* @__PURE__ */ import_react10.default.createElement(FolderPlaceholder, null), imageBox);
|
||||||
|
else
|
||||||
|
import_react_dom.default.render(/* @__PURE__ */ import_react10.default.createElement(FolderImage, {
|
||||||
|
url: imageUrl
|
||||||
|
}), imageBox);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
injectFolderImages();
|
||||||
|
window.SpicetifyLibrary.FolderImageWrapper.addEventListener("update", () => {
|
||||||
|
injectFolderImages();
|
||||||
|
});
|
||||||
|
function injectYLXButtons() {
|
||||||
|
const ylx_filter = document.querySelector(
|
||||||
|
".main-yourLibraryX-libraryRootlist > .main-yourLibraryX-libraryFilter"
|
||||||
|
);
|
||||||
|
if (!ylx_filter) {
|
||||||
|
return setTimeout(injectYLXButtons, 100);
|
||||||
|
}
|
||||||
|
injectFiltersButton(ylx_filter);
|
||||||
|
injectCollapseButton(ylx_filter);
|
||||||
|
}
|
||||||
|
function injectFiltersButton(ylx_filter) {
|
||||||
|
const toggleFiltersButton = document.createElement("span");
|
||||||
|
toggleFiltersButton.classList.add("toggle-filters-button");
|
||||||
|
ylx_filter.appendChild(toggleFiltersButton);
|
||||||
|
import_react_dom.default.render(/* @__PURE__ */ import_react10.default.createElement(toggle_filters_default, null), toggleFiltersButton);
|
||||||
|
}
|
||||||
|
function injectCollapseButton(ylx_filter) {
|
||||||
|
const collapseButton = document.createElement("span");
|
||||||
|
collapseButton.classList.add("collapse-button");
|
||||||
|
ylx_filter.appendChild(collapseButton);
|
||||||
|
import_react_dom.default.render(
|
||||||
|
/* @__PURE__ */ import_react10.default.createElement(Spicetify.ReactComponent.TooltipWrapper, {
|
||||||
|
label: "Collapse Sidebar",
|
||||||
|
placement: "top"
|
||||||
|
}, /* @__PURE__ */ import_react10.default.createElement(collapse_button_default, null)),
|
||||||
|
collapseButton
|
||||||
|
);
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
})();
|
61
.config/spicetify/CustomApps/library/folder_image_wrapper.js
Normal file
61
.config/spicetify/CustomApps/library/folder_image_wrapper.js
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
(async function() {
|
||||||
|
while (!Spicetify.React || !Spicetify.ReactDOM) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 10));
|
||||||
|
}
|
||||||
|
"use strict";
|
||||||
|
var library = (() => {
|
||||||
|
var __defProp = Object.defineProperty;
|
||||||
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||||
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||||
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||||
|
var __export = (target, all) => {
|
||||||
|
for (var name in all)
|
||||||
|
__defProp(target, name, { get: all[name], enumerable: true });
|
||||||
|
};
|
||||||
|
var __copyProps = (to, from, except, desc) => {
|
||||||
|
if (from && typeof from === "object" || typeof from === "function") {
|
||||||
|
for (let key of __getOwnPropNames(from))
|
||||||
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||||
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||||
|
}
|
||||||
|
return to;
|
||||||
|
};
|
||||||
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||||
|
|
||||||
|
// src/extensions/folder_image_wrapper.ts
|
||||||
|
var folder_image_wrapper_exports = {};
|
||||||
|
__export(folder_image_wrapper_exports, {
|
||||||
|
default: () => folder_image_wrapper_default
|
||||||
|
});
|
||||||
|
var FolderImageWrapper = class extends EventTarget {
|
||||||
|
_folderImages;
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this._folderImages = JSON.parse(localStorage.getItem("library:folderImages") || "{}");
|
||||||
|
}
|
||||||
|
getFolderImage(uri) {
|
||||||
|
return this._folderImages[uri];
|
||||||
|
}
|
||||||
|
getFolderImages() {
|
||||||
|
return this._folderImages;
|
||||||
|
}
|
||||||
|
setFolderImage({ uri, url }) {
|
||||||
|
this._folderImages[uri] = url;
|
||||||
|
this.saveFolderImages();
|
||||||
|
Spicetify.showNotification("Folder image updated");
|
||||||
|
}
|
||||||
|
removeFolderImage(uri) {
|
||||||
|
delete this._folderImages[uri];
|
||||||
|
this.saveFolderImages();
|
||||||
|
Spicetify.showNotification("Folder image removed");
|
||||||
|
}
|
||||||
|
saveFolderImages() {
|
||||||
|
this.dispatchEvent(new CustomEvent("update", { detail: this._folderImages }));
|
||||||
|
localStorage.setItem("library:folderImages", JSON.stringify(this._folderImages));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var folder_image_wrapper_default = FolderImageWrapper;
|
||||||
|
return __toCommonJS(folder_image_wrapper_exports);
|
||||||
|
})();
|
||||||
|
|
||||||
|
})();
|
1461
.config/spicetify/CustomApps/library/index.js
Normal file
1461
.config/spicetify/CustomApps/library/index.js
Normal file
File diff suppressed because it is too large
Load diff
11
.config/spicetify/CustomApps/library/manifest.json
Normal file
11
.config/spicetify/CustomApps/library/manifest.json
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"name": "Your Library",
|
||||||
|
"icon": "<svg height=\"24\" width=\"24\" viewBox=\"0 0 24 24\">\r\n<path d=\"M14.5 2.134a1 1 0 0 1 1 0l6 3.464a1 1 0 0 1 .5.866V21a1 1 0 0 1-1 1h-6a1 1 0 0 1-1-1V3a1 1 0 0 1 .5-.866zM16 4.732V20h4V7.041l-4-2.309zM3 22a1 1 0 0 1-1-1V3a1 1 0 0 1 2 0v18a1 1 0 0 1-1 1zm6 0a1 1 0 0 1-1-1V3a1 1 0 0 1 2 0v18a1 1 0 0 1-1 1z\"></path>\r\n</svg>\r\n",
|
||||||
|
"active-icon": "<svg height=\"24\" width=\"24\" viewBox=\"0 0 24 24\">\r\n<path d=\"M3 22a1 1 0 0 1-1-1V3a1 1 0 0 1 2 0v18a1 1 0 0 1-1 1zM15.5 2.134A1 1 0 0 0 14 3v18a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V6.464a1 1 0 0 0-.5-.866l-6-3.464zM9 2a1 1 0 0 0-1 1v18a1 1 0 1 0 2 0V3a1 1 0 0 0-1-1z\"></path>\r\n</svg>",
|
||||||
|
"subfiles": [],
|
||||||
|
"subfiles_extension": [
|
||||||
|
"collections_wrapper.js",
|
||||||
|
"extension.js",
|
||||||
|
"folder_image_wrapper.js"
|
||||||
|
]
|
||||||
|
}
|
356
.config/spicetify/CustomApps/library/style.css
Normal file
356
.config/spicetify/CustomApps/library/style.css
Normal file
|
@ -0,0 +1,356 @@
|
||||||
|
/* ../../../AppData/Local/Temp/tmp-5152-1Ex7Bvh4Tu6u/18f3cb8bc8b4/navBar.module.css */
|
||||||
|
.navBar-module__topBarHeaderItem___piw4C_library {
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
display: inline-block;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
.navBar-module__topBarHeaderItemLink___xA4uv_library {
|
||||||
|
margin: 0 8px 0 0;
|
||||||
|
}
|
||||||
|
.navBar-module__topBarActive___XhWpm_library {
|
||||||
|
background-color: var(--spice-tab-active);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.navBar-module__topBarHeaderItemLink___xA4uv_library {
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--spice-text);
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 8px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
position: relative;
|
||||||
|
text-decoration: none !important;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.navBar-module__topBarNav___qWGeZ_library {
|
||||||
|
-webkit-app-region: drag;
|
||||||
|
pointer-events: none;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.navBar-module__topBarHeaderItem___piw4C_library .navBar-module__optionsMenuDropBox___pzfNI_library {
|
||||||
|
color: var(--spice-text);
|
||||||
|
border: 0;
|
||||||
|
max-width: 150px;
|
||||||
|
height: 42px;
|
||||||
|
padding: 0 30px 0 12px;
|
||||||
|
background-color: initial;
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
}
|
||||||
|
.navBar-module__topBarHeaderItem___piw4C_library .navBar-module__optionsMenuDropBox___pzfNI_library svg {
|
||||||
|
position: absolute;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
div.navBar-module__topBarHeaderItemLink___xA4uv_library {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ../../../AppData/Local/Temp/tmp-5152-1Ex7Bvh4Tu6u/18f3cb8baff0/app.css */
|
||||||
|
:root {
|
||||||
|
--library-card-size: 180px;
|
||||||
|
--library-searchbar-size: 200px;
|
||||||
|
}
|
||||||
|
#library-app .header-right .x-filterBox-expandedOrHasFilter .x-filterBox-filterInput {
|
||||||
|
width: var(--library-searchbar-size);
|
||||||
|
}
|
||||||
|
#library-app .grid {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(var(--library-card-size), 1fr)) !important;
|
||||||
|
}
|
||||||
|
#library-app .load-more-card {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
#library-app .load-more-card div:nth-child(2) {
|
||||||
|
text-align: center;
|
||||||
|
font-size: var(--encore-text-size-base);
|
||||||
|
}
|
||||||
|
#library-app .load-more-card div:first-child {
|
||||||
|
fill: var(--text-subdued);
|
||||||
|
width: 80%;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
#library-app .load-more-card:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.text-input-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 18px;
|
||||||
|
}
|
||||||
|
.text-input-form .text-input {
|
||||||
|
background: rgba(var(--spice-rgb-selected-row), 0.1);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--spice-text);
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 14px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0 12px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.text-input-form .text-input:focus {
|
||||||
|
background-color: var(--spice-tab-active);
|
||||||
|
border: 1px solid var(--spice-button-disabled);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.text-input-form button {
|
||||||
|
align-self: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ../../../AppData/Local/Temp/tmp-5152-1Ex7Bvh4Tu6u/18f3cb8bc361/external.css */
|
||||||
|
body:not(.show-ylx-filters) .main-yourLibraryX-filterArea:not(:has(> .main-yourLibraryX-libraryFilter)),
|
||||||
|
.main-yourLibraryX-header:not(:has(> .main-yourLibraryX-headerContent > .main-yourLibraryX-collapseButton > button:nth-child(2))),
|
||||||
|
.main-yourLibraryX-collapseButton > button:first-child,
|
||||||
|
.main-yourLibraryX-headerContent > button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.main-yourLibraryX-library {
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
|
.main-yourLibraryX-header {
|
||||||
|
margin-top: -8px;
|
||||||
|
}
|
||||||
|
.main-yourLibraryX-librarySortWrapper button span:first-child {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.main-yourLibraryX-librarySortWrapper {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
.toggle-filters-button > button:after,
|
||||||
|
.collapse-button > button:after,
|
||||||
|
.expand-button > button:after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.expand-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 1;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
.expand-button > button {
|
||||||
|
visibility: hidden;
|
||||||
|
margin: 0 5px;
|
||||||
|
}
|
||||||
|
li.main-yourLibraryX-navItem[data-id="/library"] {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
li.main-yourLibraryX-navItem[data-id="/library"] > a {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
.toggle-filters-button > button,
|
||||||
|
.collapse-button > button,
|
||||||
|
.main-yourLibraryX-librarySortWrapper > button {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.toggle-filters-button,
|
||||||
|
.collapse-button,
|
||||||
|
.main-yourLibraryX-librarySortWrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-basis: 32px;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 24px;
|
||||||
|
flex-shrink: 10;
|
||||||
|
}
|
||||||
|
.main-yourLibraryX-librarySortWrapper > button > span:nth-child(2) {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.LayoutResizer__resize-bar {
|
||||||
|
opacity: 0 !important;
|
||||||
|
}
|
||||||
|
.Root__nav-bar .x-filterBox-expandedOrHasFilter .x-filterBox-filterInput {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.Root__nav-bar .x-filterBox-expandedOrHasFilter {
|
||||||
|
flex-grow: 1;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
.Root__nav-bar:has(> .LayoutResizer__resize-bar:hover) .expand-button > button,
|
||||||
|
.Root__nav-bar .expand-button:hover > button {
|
||||||
|
visibility: visible;
|
||||||
|
height: 32px;
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
.text-input-form .Button-small-buttonPrimary {
|
||||||
|
box-sizing: border-box;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 700;
|
||||||
|
font-family: var(--font-family, CircularSp, CircularSp-Arab, CircularSp-Hebr, CircularSp-Cyrl, CircularSp-Grek, CircularSp-Deva, var(--fallback-fonts, sans-serif));
|
||||||
|
background-color: transparent;
|
||||||
|
border: 0px;
|
||||||
|
border-radius: 9999px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
text-transform: none;
|
||||||
|
touch-action: manipulation;
|
||||||
|
transition-duration: 33ms;
|
||||||
|
transition-property:
|
||||||
|
background-color,
|
||||||
|
border-color,
|
||||||
|
color,
|
||||||
|
box-shadow,
|
||||||
|
filter,
|
||||||
|
transform;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
vertical-align: middle;
|
||||||
|
transform: translate3d(0px, 0px, 0px);
|
||||||
|
padding: 0px;
|
||||||
|
min-inline-size: 0px;
|
||||||
|
}
|
||||||
|
.text-input-form .ButtonInner-small {
|
||||||
|
box-sizing: border-box;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
position: relative;
|
||||||
|
background-color: var(--background-base, #1ed760);
|
||||||
|
color: var(--text-base, #000000);
|
||||||
|
display: flex;
|
||||||
|
border-radius: 9999px;
|
||||||
|
font-size: inherit;
|
||||||
|
min-block-size: var(--encore-control-size-smaller, 32px);
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding-block-start: var(--encore-spacing-tighter-4, 4px);
|
||||||
|
padding-block-end: var(--encore-spacing-tighter-4, 4px);
|
||||||
|
padding-inline-start: var(--encore-spacing-base, 16px);
|
||||||
|
padding-inline-end: var(--encore-spacing-base, 16px);
|
||||||
|
}
|
||||||
|
.text-input-form .Button-small-buttonPrimary:hover .ButtonInner-sc-14ud5tc-0,
|
||||||
|
.text-input-form .Button-small-buttonPrimary:hover .ButtonFocus-sc-2hq6ey-0 {
|
||||||
|
transform: scale(1.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ../../../AppData/Local/Temp/tmp-5152-1Ex7Bvh4Tu6u/18f3cb8bc5f2/config_modal.css */
|
||||||
|
.config-container {
|
||||||
|
gap: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.config-container .section-header {
|
||||||
|
box-sizing: border-box;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
margin-block: 0px;
|
||||||
|
font-size: 1.125rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--spice-text);
|
||||||
|
}
|
||||||
|
.config-container .col.description {
|
||||||
|
box-sizing: border-box;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
margin-block: 0px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--spice-subtext);
|
||||||
|
}
|
||||||
|
.config-container .disabled {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.config-container .text-input {
|
||||||
|
background: rgba(var(--spice-rgb-selected-row), 0.1);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--spice-text);
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 14px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0 12px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.config-container .text-input:focus {
|
||||||
|
background-color: var(--spice-tab-active);
|
||||||
|
border: 1px solid var(--spice-button-disabled);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.config-container .dropdown-input {
|
||||||
|
background-color: var(--spice-tab-active);
|
||||||
|
border: 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: rgba(var(--spice-rgb-selected-row), 0.7);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
height: 32px;
|
||||||
|
letter-spacing: 0.24px;
|
||||||
|
line-height: 20px;
|
||||||
|
padding: 0 32px 0 12px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.config-container .tooltip-icon {
|
||||||
|
float: right;
|
||||||
|
margin-left: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 22px;
|
||||||
|
fill: var(--spice-subtext);
|
||||||
|
}
|
||||||
|
.config-container .tooltip-icon:hover {
|
||||||
|
fill: var(--spice-text);
|
||||||
|
}
|
||||||
|
.config-container .tooltip {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.config-container .setting-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.config-container .playback-progressbar {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ../../../AppData/Local/Temp/tmp-5152-1Ex7Bvh4Tu6u/18f3cb8bc713/shared.css */
|
||||||
|
.grid {
|
||||||
|
--grid-gap: 24px;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)) !important;
|
||||||
|
}
|
||||||
|
.loadingWrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 60vh;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
.loadingWrapper .status-icon {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
.page-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
.page-header {
|
||||||
|
align-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
|
.page-header .header-right,
|
||||||
|
.page-header .header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.page-header .header-right {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
.page-header .header-left {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
.new-update {
|
||||||
|
background-color: var(--spice-player);
|
||||||
|
color: var(--spice-text);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 2px 12px;
|
||||||
|
margin: 0 24px;
|
||||||
|
border: 0px;
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
76
.config/spicetify/CustomApps/stats/extension.css
Normal file
76
.config/spicetify/CustomApps/stats/extension.css
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
/* ../../../AppData/Local/Temp/tmp-15744-866d0PjWRxih/18d53b11b111/config_modal.css */
|
||||||
|
#config-container {
|
||||||
|
gap: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
#config-container .section-header {
|
||||||
|
box-sizing: border-box;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
margin-block: 0px;
|
||||||
|
font-size: 1.125rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--spice-text);
|
||||||
|
}
|
||||||
|
#config-container .col.description {
|
||||||
|
box-sizing: border-box;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
margin-block: 0px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--spice-subtext);
|
||||||
|
}
|
||||||
|
#config-container .disabled {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
#config-container .text-input {
|
||||||
|
background: rgba(var(--spice-rgb-selected-row), 0.1);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--spice-text);
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 14px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0 12px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
#config-container .text-input:focus {
|
||||||
|
background-color: var(--spice-tab-active);
|
||||||
|
border: 1px solid var(--spice-button-disabled);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
#config-container .dropdown-input {
|
||||||
|
background-color: var(--spice-tab-active);
|
||||||
|
border: 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: rgba(var(--spice-rgb-selected-row), 0.7);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
height: 32px;
|
||||||
|
letter-spacing: 0.24px;
|
||||||
|
line-height: 20px;
|
||||||
|
padding: 0 32px 0 12px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
#config-container .tooltip-icon {
|
||||||
|
float: right;
|
||||||
|
margin-left: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 22px;
|
||||||
|
fill: var(--spice-subtext);
|
||||||
|
}
|
||||||
|
#config-container .tooltip-icon:hover {
|
||||||
|
fill: var(--spice-text);
|
||||||
|
}
|
||||||
|
#config-container .tooltip {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#config-container .setting-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
#config-container .playback-progressbar {
|
||||||
|
width: 200px;
|
||||||
|
}
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,17 +1,17 @@
|
||||||
/* ../../AppData/Local/Temp/tmp-6912-LESOepgMxajG/18ca59b28c22/navBar.module.css */
|
/* ../../../AppData/Local/Temp/tmp-17120-n3iWRzissBjg/18f3cbbb4bc3/navBar.module.css */
|
||||||
.navBar-module__topBarHeaderItem___v29bR_stats {
|
.navBar-module__topBarHeaderItem___piw4C_stats {
|
||||||
-webkit-app-region: no-drag;
|
-webkit-app-region: no-drag;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
.navBar-module__topBarHeaderItemLink___VeyBY_stats {
|
.navBar-module__topBarHeaderItemLink___xA4uv_stats {
|
||||||
margin: 0 8px 0 0;
|
margin: 0 8px 0 0;
|
||||||
}
|
}
|
||||||
.navBar-module__topBarActive___-qYPu_stats {
|
.navBar-module__topBarActive___XhWpm_stats {
|
||||||
background-color: var(--spice-tab-active);
|
background-color: var(--spice-tab-active);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
.navBar-module__topBarHeaderItemLink___VeyBY_stats {
|
.navBar-module__topBarHeaderItemLink___xA4uv_stats {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
color: var(--spice-text);
|
color: var(--spice-text);
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -21,12 +21,12 @@
|
||||||
text-decoration: none !important;
|
text-decoration: none !important;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.navBar-module__topBarNav___1OtdR_stats {
|
.navBar-module__topBarNav___qWGeZ_stats {
|
||||||
-webkit-app-region: drag;
|
-webkit-app-region: drag;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.navBar-module__topBarHeaderItem___v29bR_stats .navBar-module__optionsMenuDropBox___tD9mA_stats {
|
.navBar-module__topBarHeaderItem___piw4C_stats .navBar-module__optionsMenuDropBox___pzfNI_stats {
|
||||||
color: var(--spice-text);
|
color: var(--spice-text);
|
||||||
border: 0;
|
border: 0;
|
||||||
max-width: 150px;
|
max-width: 150px;
|
||||||
|
@ -38,74 +38,45 @@
|
||||||
-moz-appearance: none;
|
-moz-appearance: none;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
}
|
}
|
||||||
.navBar-module__topBarHeaderItem___v29bR_stats .navBar-module__optionsMenuDropBox___tD9mA_stats svg {
|
.navBar-module__topBarHeaderItem___piw4C_stats .navBar-module__optionsMenuDropBox___pzfNI_stats svg {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
div.navBar-module__topBarHeaderItemLink___VeyBY_stats {
|
div.navBar-module__topBarHeaderItemLink___xA4uv_stats {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ../../AppData/Local/Temp/tmp-6912-LESOepgMxajG/18ca59b275b0/app.css */
|
/* ../../../AppData/Local/Temp/tmp-17120-n3iWRzissBjg/18f3cbbb33e0/app.css */
|
||||||
.stats-grid {
|
#stats-app .stats-gridInline {
|
||||||
--grid-gap: 24px;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)) !important;
|
|
||||||
}
|
|
||||||
.stats-refreshButton {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
}
|
|
||||||
.collection-searchBar-searchBar {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
.stats-specialGrid {
|
|
||||||
grid-template-columns: repeat(11, 180px) !important;
|
|
||||||
}
|
|
||||||
.stats-gridInline {
|
|
||||||
--grid-gap: 24px;
|
--grid-gap: 24px;
|
||||||
grid-template-columns: repeat(10, 180px) !important;
|
grid-template-columns: repeat(10, 180px) !important;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
[data-scroll=both] {
|
#stats-app [data-scroll=both] {
|
||||||
-webkit-mask-image: linear-gradient(to right, transparent, black 10%, black 90%, transparent);
|
-webkit-mask-image: linear-gradient(to right, transparent, black 10%, black 90%, transparent);
|
||||||
mask-image: linear-gradient(to right, transparent, black 10%, black 90%, transparent);
|
mask-image: linear-gradient(to right, transparent, black 10%, black 90%, transparent);
|
||||||
}
|
}
|
||||||
[data-scroll=end] {
|
#stats-app [data-scroll=end] {
|
||||||
-webkit-mask-image: linear-gradient(to right, transparent, black 10%);
|
-webkit-mask-image: linear-gradient(to right, transparent, black 10%);
|
||||||
mask-image: linear-gradient(to right, transparent, black 10%);
|
mask-image: linear-gradient(to right, transparent, black 10%);
|
||||||
}
|
}
|
||||||
[data-scroll=start] {
|
#stats-app [data-scroll=start] {
|
||||||
-webkit-mask-image: linear-gradient(to right, black 90%, transparent);
|
-webkit-mask-image: linear-gradient(to right, black 90%, transparent);
|
||||||
mask-image: linear-gradient(to right, black 90%, transparent);
|
mask-image: linear-gradient(to right, black 90%, transparent);
|
||||||
}
|
}
|
||||||
.stats-loadingWrapper {
|
#stats-app .stats-libraryOverview {
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 60vh;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
.stats-libraryOverview {
|
|
||||||
display: flex;
|
|
||||||
gap: 24px;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
.stats-page {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 24px;
|
|
||||||
}
|
|
||||||
.stats-trackPageTitle {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 24px;
|
gap: 24px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.stats-scrollButton {
|
#stats-app .stats-trackPageTitle {
|
||||||
|
display: flex;
|
||||||
|
gap: 24px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
#stats-app .stats-scrollButton {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: none;
|
border: none;
|
||||||
|
@ -114,69 +85,20 @@ div.navBar-module__topBarHeaderItemLink___VeyBY_stats {
|
||||||
background-color: var(--spice-player);
|
background-color: var(--spice-player);
|
||||||
color: var(--spice-subtext);
|
color: var(--spice-subtext);
|
||||||
}
|
}
|
||||||
.stats-scrollButton:hover {
|
#stats-app .stats-scrollButton:hover {
|
||||||
background-color: var(--spice-card);
|
background-color: var(--spice-card);
|
||||||
}
|
|
||||||
.stats-createPlaylistButton {
|
|
||||||
margin-left: 24px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
-webkit-tap-highlight-color: transparent;
|
|
||||||
font-size: 0.8125rem;
|
|
||||||
font-weight: 700;
|
|
||||||
font-family: var(--font-family, CircularSp, CircularSp-Arab, CircularSp-Hebr, CircularSp-Cyrl, CircularSp-Grek, CircularSp-Deva, var(--fallback-fonts, sans-serif));
|
|
||||||
background-color: transparent;
|
|
||||||
border-radius: 500px;
|
|
||||||
cursor: pointer;
|
|
||||||
position: relative;
|
|
||||||
text-align: center;
|
|
||||||
text-decoration: none;
|
|
||||||
text-transform: none;
|
|
||||||
touch-action: manipulation;
|
|
||||||
transition-duration: 33ms;
|
|
||||||
transition-property:
|
|
||||||
background-color,
|
|
||||||
border-color,
|
|
||||||
color,
|
|
||||||
box-shadow,
|
|
||||||
filter,
|
|
||||||
transform;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
vertical-align: middle;
|
|
||||||
transform: translate3d(0px, 0px, 0px);
|
|
||||||
padding-block: 3px;
|
|
||||||
padding-inline: 15px;
|
|
||||||
border: 1px solid var(--essential-subdued, #878787);
|
|
||||||
color: var(--text-base, #000000);
|
|
||||||
min-block-size: 32px;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
.stats-createPlaylistButton:hover {
|
|
||||||
transform: scale(1.04);
|
|
||||||
border-color: var(--essential-base, #000000);
|
|
||||||
}
|
|
||||||
.stats-header {
|
|
||||||
-webkit-box-pack: justify;
|
|
||||||
-ms-flex-pack: justify;
|
|
||||||
-webkit-box-align: center;
|
|
||||||
-ms-flex-align: center;
|
|
||||||
align-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
color: var(--spice-text);
|
color: var(--spice-text);
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin: 16px 0;
|
|
||||||
}
|
}
|
||||||
.stats-tracklistHeader > div {
|
#stats-app .stats-tracklistHeader > div {
|
||||||
display: flex;
|
display: flex;
|
||||||
-webkit-app-region: no-drag;
|
-webkit-app-region: no-drag;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.stats-genreCard {
|
#stats-app .stats-make-playlist-button {
|
||||||
|
margin-inline-start: 12px;
|
||||||
|
}
|
||||||
|
#stats-app .stats-genreCard {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
@ -185,44 +107,36 @@ div.navBar-module__topBarHeaderItemLink___VeyBY_stats {
|
||||||
background: var(--spice-player);
|
background: var(--spice-player);
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
.stats-genreRow {
|
#stats-app .stats-genreRow {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
.stats-genreRowFill {
|
#stats-app .stats-genreRowFill {
|
||||||
background: var(--spice-button);
|
background: var(--spice-button);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.stats-genreText {
|
#stats-app .stats-genreText {
|
||||||
color: var(--spice-player);
|
color: var(--spice-player);
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
margin-left: 7px;
|
margin-left: 7px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
.stats-genreValue {
|
#stats-app .stats-genreValue {
|
||||||
color: var(--spice-text);
|
color: var(--spice-text);
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
}
|
}
|
||||||
.stats-cardValue {
|
#stats-app .stats-genreCard + .stats-gridInlineSection {
|
||||||
font-size: 2rem;
|
margin-top: 3px;
|
||||||
font-weight: bold;
|
|
||||||
color: var(--spice-text);
|
|
||||||
}
|
}
|
||||||
.stats-cardText {
|
#stats-app .main-trackList-rowHeartButton,
|
||||||
color: var(--spice-text);
|
#stats-app .main-trackList-rowMoreButton {
|
||||||
}
|
background-color: transparent;
|
||||||
.new-update {
|
border: none;
|
||||||
background-color: var(--spice-player);
|
|
||||||
color: var(--spice-text);
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 2px 12px;
|
|
||||||
margin: 0 24px;
|
|
||||||
border: 0px;
|
|
||||||
}
|
}
|
||||||
.GenericModal[aria-label="Playlist Stats"] .main-embedWidgetGenerator-container {
|
.GenericModal[aria-label="Playlist Stats"] .main-embedWidgetGenerator-container {
|
||||||
width: 80vw;
|
width: 80vw;
|
||||||
|
@ -232,25 +146,14 @@ div.navBar-module__topBarHeaderItemLink___VeyBY_stats {
|
||||||
.GenericModal[aria-label="Playlist Stats"] .main-shelf-title {
|
.GenericModal[aria-label="Playlist Stats"] .main-shelf-title {
|
||||||
color: var(--spice-text);
|
color: var(--spice-text);
|
||||||
}
|
}
|
||||||
.status-icon {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
fill: currentColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ../../AppData/Local/Temp/tmp-6912-LESOepgMxajG/18ca59b28921/settings_modal.css */
|
/* ../../../AppData/Local/Temp/tmp-17120-n3iWRzissBjg/18f3cbbb48b1/config_modal.css */
|
||||||
#stats-config-container {
|
.config-container {
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
#stats-config-container .toggle-wrapper {
|
.config-container .section-header {
|
||||||
display: inline-flex;
|
|
||||||
position: relative;
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
#stats-config-container .section-header {
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
margin-block: 0px;
|
margin-block: 0px;
|
||||||
|
@ -258,7 +161,7 @@ div.navBar-module__topBarHeaderItemLink___VeyBY_stats {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--spice-text);
|
color: var(--spice-text);
|
||||||
}
|
}
|
||||||
#stats-config-container .col.description {
|
.config-container .col.description {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
margin-block: 0px;
|
margin-block: 0px;
|
||||||
|
@ -266,60 +169,11 @@ div.navBar-module__topBarHeaderItemLink___VeyBY_stats {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
color: var(--spice-subtext);
|
color: var(--spice-subtext);
|
||||||
}
|
}
|
||||||
#stats-config-container .disabled {
|
.config-container .disabled {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
#stats-config-container .toggle-input {
|
.config-container .text-input {
|
||||||
opacity: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
#stats-config-container .toggle-input:checked ~ .toggle-indicator-wrapper {
|
|
||||||
background-color: var(--spice-text);
|
|
||||||
}
|
|
||||||
#stats-config-container .toggle-input:checked ~ .toggle-indicator-wrapper .toggle-indicator {
|
|
||||||
background-color: #fff;
|
|
||||||
left: auto;
|
|
||||||
right: 2px;
|
|
||||||
right: 3px;
|
|
||||||
}
|
|
||||||
#stats-config-container .toggle-input:hover ~ .toggle-indicator-wrapper {
|
|
||||||
filter: brightness(1.3);
|
|
||||||
}
|
|
||||||
#stats-config-container .toggle-input:hover:checked ~ .toggle-indicator-wrapper {
|
|
||||||
filter: brightness(1.15);
|
|
||||||
}
|
|
||||||
#stats-config-container .toggle-input:active:not([disabled]) ~ .toggle-indicator-wrapper .toggle-indicator {
|
|
||||||
width: 20px;
|
|
||||||
}
|
|
||||||
#stats-config-container .toggle-indicator-wrapper {
|
|
||||||
background-color: #535353;
|
|
||||||
border-radius: 24px;
|
|
||||||
height: 24px;
|
|
||||||
position: relative;
|
|
||||||
width: 42px;
|
|
||||||
}
|
|
||||||
#stats-config-container .toggle-indicator {
|
|
||||||
background: #fff;
|
|
||||||
border-radius: inherit;
|
|
||||||
height: 20px;
|
|
||||||
left: 2px;
|
|
||||||
position: absolute;
|
|
||||||
top: 2px;
|
|
||||||
transition:
|
|
||||||
background-color,
|
|
||||||
left,
|
|
||||||
right,
|
|
||||||
width 0.1s ease;
|
|
||||||
width: 20px;
|
|
||||||
height: 18px;
|
|
||||||
width: 18px;
|
|
||||||
top: 3px;
|
|
||||||
left: 3px;
|
|
||||||
background: var(--spice-shadow) !important;
|
|
||||||
}
|
|
||||||
#stats-config-container .text-input {
|
|
||||||
background: rgba(var(--spice-rgb-selected-row), 0.1);
|
background: rgba(var(--spice-rgb-selected-row), 0.1);
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
@ -330,12 +184,12 @@ div.navBar-module__topBarHeaderItemLink___VeyBY_stats {
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
#stats-config-container .text-input:focus {
|
.config-container .text-input:focus {
|
||||||
background-color: var(--spice-tab-active);
|
background-color: var(--spice-tab-active);
|
||||||
border: 1px solid var(--spice-button-disabled);
|
border: 1px solid var(--spice-button-disabled);
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
#stats-config-container .dropdown-input {
|
.config-container .dropdown-input {
|
||||||
background-color: var(--spice-tab-active);
|
background-color: var(--spice-tab-active);
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
@ -348,7 +202,7 @@ div.navBar-module__topBarHeaderItemLink___VeyBY_stats {
|
||||||
padding: 0 32px 0 12px;
|
padding: 0 32px 0 12px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
#stats-config-container .tooltip-icon {
|
.config-container .tooltip-icon {
|
||||||
float: right;
|
float: right;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -356,13 +210,67 @@ div.navBar-module__topBarHeaderItemLink___VeyBY_stats {
|
||||||
height: 22px;
|
height: 22px;
|
||||||
fill: var(--spice-subtext);
|
fill: var(--spice-subtext);
|
||||||
}
|
}
|
||||||
#stats-config-container .tooltip-icon:hover {
|
.config-container .tooltip-icon:hover {
|
||||||
fill: var(--spice-text);
|
fill: var(--spice-text);
|
||||||
}
|
}
|
||||||
#stats-config-container .tooltip {
|
.config-container .tooltip {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
#stats-config-container .setting-row {
|
.config-container .setting-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
.config-container .playback-progressbar {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ../../../AppData/Local/Temp/tmp-17120-n3iWRzissBjg/18f3cbbb49f2/shared.css */
|
||||||
|
.grid {
|
||||||
|
--grid-gap: 24px;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)) !important;
|
||||||
|
}
|
||||||
|
.loadingWrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 60vh;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
.loadingWrapper .status-icon {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
.page-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
.page-header {
|
||||||
|
align-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
|
.page-header .header-right,
|
||||||
|
.page-header .header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.page-header .header-right {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
.page-header .header-left {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
.new-update {
|
||||||
|
background-color: var(--spice-player);
|
||||||
|
color: var(--spice-text);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 2px 12px;
|
||||||
|
margin: 0 24px;
|
||||||
|
border: 0px;
|
||||||
|
}
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
//@ts-check
|
|
||||||
|
|
||||||
// NAME: adblock
|
|
||||||
// AUTHOR: CharlieS1103
|
|
||||||
// DESCRIPTION: Block all audio and UI ads on Spotify
|
|
||||||
|
|
||||||
/// <reference path="../../spicetify-cli/globals.d.ts" />
|
|
||||||
|
|
||||||
(function adblock() {
|
|
||||||
const { Platform} = Spicetify;
|
|
||||||
if (!(Platform)) {
|
|
||||||
setTimeout(adblock, 300)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var styleSheet = document.createElement("style")
|
|
||||||
|
|
||||||
styleSheet.innerHTML =
|
|
||||||
`
|
|
||||||
.MnW5SczTcbdFHxLZ_Z8j, .WiPggcPDzbwGxoxwLWFf, .ReyA3uE3K7oEz7PTTnAn, .main-leaderboardComponent-container, .sponsor-container, a.link-subtle.main-navBar-navBarLink.GKnnhbExo0U9l7Jz2rdc{
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
`
|
|
||||||
document.body.appendChild(styleSheet)
|
|
||||||
delayAds()
|
|
||||||
var billboard = Spicetify.Platform.AdManagers.billboard.displayBillboard;
|
|
||||||
Spicetify.Platform.AdManagers.billboard.displayBillboard = function (arguments) {
|
|
||||||
Spicetify.Platform.AdManagers.billboard.finish()
|
|
||||||
// hook before call
|
|
||||||
var ret = billboard.apply(this, arguments);
|
|
||||||
// hook after call
|
|
||||||
console.log("Adblock.js: Billboard blocked! Leave a star!")
|
|
||||||
Spicetify.Platform.AdManagers.billboard.finish()
|
|
||||||
const observer = new MutationObserver((mutations, obs) => {
|
|
||||||
const billboardAd = document.getElementById('view-billboard-ad');
|
|
||||||
if (billboardAd) {
|
|
||||||
Spicetify.Platform.AdManagers.billboard.finish()
|
|
||||||
obs.disconnect();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
observer.observe(document, {
|
|
||||||
childList: true,
|
|
||||||
subtree: true
|
|
||||||
});
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
function delayAds() {
|
|
||||||
console.log("Ads delayed: Adblock.js")
|
|
||||||
Spicetify.Platform.AdManagers.audio.audioApi.cosmosConnector.increaseStreamTime(-100000000000)
|
|
||||||
Spicetify.Platform.AdManagers.billboard.billboardApi.cosmosConnector.increaseStreamTime(-100000000000)
|
|
||||||
}
|
|
||||||
setInterval(delayAds, 720 *10000);
|
|
||||||
|
|
||||||
|
|
||||||
})()
|
|
||||||
|
|
||||||
|
|
|
@ -1,654 +0,0 @@
|
||||||
// NAME: Copy Playlists
|
|
||||||
// AUTHOR: einzigartigerName
|
|
||||||
// DESCRIPTION: copy/combine playlist/queue directly in Spotify
|
|
||||||
|
|
||||||
(function CopyPlaylist() {
|
|
||||||
|
|
||||||
const { CosmosAPI, BridgeAPI, LocalStorage, PlaybackControl, ContextMenu, URI } = Spicetify
|
|
||||||
if (!(CosmosAPI || BridgeAPI)) {
|
|
||||||
setTimeout(CopyPlaylist, 1000);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const STORAGE_KEY = "combine_buffer_spicetify"
|
|
||||||
const TOP_BTN_TOOLTIP = "Combine Playlists"
|
|
||||||
const MENU_BTN_CREATE_NEW = "Create Playlist"
|
|
||||||
const MENU_BTN_INSERT_BUFFER = "Copy to Buffer"
|
|
||||||
|
|
||||||
class PlaylistCollection {
|
|
||||||
constructor() {
|
|
||||||
const menu = createMenu()
|
|
||||||
this.container = menu.container
|
|
||||||
this.items = menu.menu
|
|
||||||
this.lastScroll = 0
|
|
||||||
this.container.onclick = () => {
|
|
||||||
this.storeScroll()
|
|
||||||
this.container.remove()
|
|
||||||
}
|
|
||||||
this.pattern
|
|
||||||
this.apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
apply() {
|
|
||||||
this.items.textContent = '' // Remove all childs
|
|
||||||
this.items.append(createMenuItem("Create Playlist", () => highjackCreateDialog(mergePlaylists(this.pattern))))
|
|
||||||
this.items.append(createMenuItem("Clear Buffer", () => LIST.clearStorage()))
|
|
||||||
|
|
||||||
const select = createPatternSelect(this.filter);
|
|
||||||
select.onchange = (event) => {
|
|
||||||
this.pattern = event.srcElement.selectedIndex;
|
|
||||||
}
|
|
||||||
this.items.append(select);
|
|
||||||
|
|
||||||
const collection = this.getStorage();
|
|
||||||
collection.forEach((item) => this.items.append(new CardContainer(item)))
|
|
||||||
}
|
|
||||||
|
|
||||||
getStorage() {
|
|
||||||
const storageRaw = LocalStorage.get(STORAGE_KEY);
|
|
||||||
let storage = [];
|
|
||||||
|
|
||||||
if (storageRaw) {
|
|
||||||
storage = JSON.parse(storageRaw);
|
|
||||||
} else {
|
|
||||||
LocalStorage.set(STORAGE_KEY, "[]")
|
|
||||||
}
|
|
||||||
|
|
||||||
return storage;
|
|
||||||
}
|
|
||||||
|
|
||||||
addToStorage(data) {
|
|
||||||
|
|
||||||
/** @type {Object[]} */
|
|
||||||
const storage = this.getStorage();
|
|
||||||
storage.push(data);
|
|
||||||
|
|
||||||
LocalStorage.set(STORAGE_KEY, JSON.stringify(storage));
|
|
||||||
this.apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
removeFromStorage(id) {
|
|
||||||
const storage = this.getStorage()
|
|
||||||
.filter(item => item.id !== id)
|
|
||||||
|
|
||||||
LocalStorage.set(STORAGE_KEY, JSON.stringify(storage));
|
|
||||||
this.apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
clearStorage() {
|
|
||||||
LocalStorage.set(STORAGE_KEY, "[]");
|
|
||||||
this.apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
moveItem(uri, direction) {
|
|
||||||
var storage = this.getStorage()
|
|
||||||
|
|
||||||
var from;
|
|
||||||
|
|
||||||
for (var i = 0; i < storage.length; i++) {
|
|
||||||
if (storage[i].uri === uri) {
|
|
||||||
from = i
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!from) { return }
|
|
||||||
|
|
||||||
var to = from + direction
|
|
||||||
if (to < 0 || to >= storage.length) { return }
|
|
||||||
|
|
||||||
var tmp = storage[from]
|
|
||||||
storage[from] = storage[to]
|
|
||||||
storage[to] = tmp
|
|
||||||
|
|
||||||
LocalStorage.set(STORAGE_KEY, JSON.stringify(storage));
|
|
||||||
this.apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
changePosition(x, y) {
|
|
||||||
this.items.style.left = x + "px"
|
|
||||||
this.items.style.top = y + 10 + "px"
|
|
||||||
}
|
|
||||||
|
|
||||||
storeScroll() {
|
|
||||||
this.lastScroll = this.items.scrollTop
|
|
||||||
}
|
|
||||||
|
|
||||||
setScroll() {
|
|
||||||
this.items.scrollTop = this.lastScroll
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Displays Stored Playlist
|
|
||||||
* {id, uri, name, tracks, imgUri, owner}
|
|
||||||
*/
|
|
||||||
class CardContainer extends HTMLElement {
|
|
||||||
constructor(info) {
|
|
||||||
super()
|
|
||||||
|
|
||||||
this.innerHTML = `
|
|
||||||
<div class="card card-horizontal card-type-album ${info.imgUri ? "" : "card-hidden-image"}" data-uri="${info.uri}" data-contextmenu="">
|
|
||||||
<div class="card-attention-highlight-box"></div>
|
|
||||||
<div class="card-horizontal-interior-wrapper">
|
|
||||||
${info.imgUri ? `
|
|
||||||
<div class="card-image-wrapper">
|
|
||||||
<div class="card-image-hit-area">
|
|
||||||
<a class="card-image-link" link="${info.uri}">
|
|
||||||
<div class="card-hit-area-counter-scale-left"></div>
|
|
||||||
<div class="card-image-content-wrapper">
|
|
||||||
<div class="card-image" style="background-image: url('${info.imgUri}')"></div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<div class="card-overlay"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
` : ""}
|
|
||||||
<div class="card-info-wrapper">
|
|
||||||
<div class="order-controls">
|
|
||||||
<div class="order-controls-up">
|
|
||||||
<button class="button button-green button-icon-only spoticon-chevron-up-16" data-tooltip="Move Up"></button>
|
|
||||||
</div>
|
|
||||||
<div class="order-controls-remove">
|
|
||||||
<button class="button button-green button-icon-only spoticon-x-16" data-tooltip="Remove"></button>
|
|
||||||
</div>
|
|
||||||
<div class="order-controls-down">
|
|
||||||
<button class="button button-green button-icon-only spoticon-chevron-down-16" data-tooltip="Move Down"></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<a class="card-info-link" ${info.uri}>
|
|
||||||
<div class="card-info-content-wrapper">
|
|
||||||
<div class="card-info-title"><span class="card-info-title-text">${info.name}</span></div>
|
|
||||||
<div class="card-info-subtitle-owner"><span>${info.owner}</span></div>
|
|
||||||
<div class="card-info-subtitle-tracks"><span>${info.tracks.length === 1 ? "1 Track" : `${info.tracks.length} Tracks`}</span></div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>`
|
|
||||||
|
|
||||||
const up = this.querySelector(".order-controls-up")
|
|
||||||
up.onclick = (event) => {
|
|
||||||
LIST.moveItem(info.uri, -1)
|
|
||||||
event.stopPropagation()
|
|
||||||
}
|
|
||||||
|
|
||||||
const remove = this.querySelector(".order-controls-remove")
|
|
||||||
remove.onclick = (event) => {
|
|
||||||
LIST.removeFromStorage(info.id)
|
|
||||||
event.stopPropagation()
|
|
||||||
}
|
|
||||||
|
|
||||||
const down = this.querySelector(".order-controls-down")
|
|
||||||
down.onclick = (event) => {
|
|
||||||
LIST.moveItem(info.uri, +1)
|
|
||||||
event.stopPropagation()
|
|
||||||
}
|
|
||||||
|
|
||||||
const imageLink = this.querySelector(".card-image-link");
|
|
||||||
const infoLink = this.querySelector(".card-info-link");
|
|
||||||
|
|
||||||
if (imageLink) imageLink.addEventListener("click", ((e) => showPlaylist(e)));
|
|
||||||
|
|
||||||
if (infoLink) infoLink.addEventListener("click", ((e) => showPlaylist(e)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("combine-buffer-card-container", CardContainer)
|
|
||||||
|
|
||||||
const LIST = new PlaylistCollection()
|
|
||||||
|
|
||||||
// New Playlist Button
|
|
||||||
const playlistDialogButton = document.querySelector("#new-playlist-button-mount-point > div > button")
|
|
||||||
if (!playlistDialogButton) return;
|
|
||||||
|
|
||||||
document.querySelector("#view-browser-navigation-top-bar")
|
|
||||||
.append(createTopBarButton())
|
|
||||||
|
|
||||||
createPlaylistContextMenu().register()
|
|
||||||
|
|
||||||
|
|
||||||
/**************************************************************************
|
|
||||||
UI Building
|
|
||||||
**************************************************************************/
|
|
||||||
// If Queue Page add Buttons
|
|
||||||
const iframeInterval = setInterval(() => {
|
|
||||||
/** @type {HTMLIFrameElement} */
|
|
||||||
const currentIframe = document.querySelector("iframe.active");
|
|
||||||
if (!currentIframe ||
|
|
||||||
currentIframe.id !== "app-queue"
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const headers = currentIframe.contentDocument.querySelectorAll(
|
|
||||||
".glue-page-header__buttons"
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const e of headers) {
|
|
||||||
e.append(createQueueButton(
|
|
||||||
"Save as Playlist",
|
|
||||||
"Save the current Queue as a new Playlist",
|
|
||||||
() => {
|
|
||||||
let tracks = getQueueTracks();
|
|
||||||
highjackCreateDialog(tracks);
|
|
||||||
},
|
|
||||||
));
|
|
||||||
|
|
||||||
e.append(createQueueButton(
|
|
||||||
"Copy into Buffer",
|
|
||||||
"Insert the current Queue into the Buffer",
|
|
||||||
() => { queueToBuffer() },
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (headers.length > 0) clearInterval(iframeInterval);
|
|
||||||
}, 500)
|
|
||||||
|
|
||||||
// Creates the Main Menu
|
|
||||||
function createMenu() {
|
|
||||||
const container = document.createElement("div")
|
|
||||||
container.id = "combine-playlist-spicetify"
|
|
||||||
container.className = "context-menu-container"
|
|
||||||
container.style.zIndex = "1029"
|
|
||||||
|
|
||||||
const style = document.createElement("style")
|
|
||||||
style.textContent = `
|
|
||||||
#combine-menu {
|
|
||||||
display: inline-block;
|
|
||||||
width: 33%;
|
|
||||||
max-height: 70%;
|
|
||||||
overflow: hidden auto;
|
|
||||||
padding: 10px
|
|
||||||
}
|
|
||||||
.combine-pattern {
|
|
||||||
margin-top: 7px;
|
|
||||||
}
|
|
||||||
.order-controls {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
padding: 0 5px 5px 0;
|
|
||||||
z-index: 3
|
|
||||||
}
|
|
||||||
.button.button-icon-only::before {
|
|
||||||
color: var(--modspotify_main_fg);
|
|
||||||
}
|
|
||||||
.order-controls-up {
|
|
||||||
position: relative;
|
|
||||||
top: 100%;
|
|
||||||
}
|
|
||||||
.order-controls-remove {
|
|
||||||
position: relative;
|
|
||||||
top: 50%;
|
|
||||||
}
|
|
||||||
.order-controls-down {
|
|
||||||
position: relative;
|
|
||||||
bottom: 100%;
|
|
||||||
}
|
|
||||||
.card-info-subtitle-owner {
|
|
||||||
color: var(--modspotify_secondary_fg);
|
|
||||||
}
|
|
||||||
.card-info-subtitle-tracks {
|
|
||||||
font-weight: lighter;
|
|
||||||
color: var(--modspotify_secondary_fg);
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const menu = document.createElement("ul")
|
|
||||||
menu.id = "combine-menu"
|
|
||||||
menu.className = "context-menu"
|
|
||||||
|
|
||||||
container.append(style, menu)
|
|
||||||
|
|
||||||
return { container, menu }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates a Button in the Combine Menu
|
|
||||||
function createMenuItem(name, callback) {
|
|
||||||
const item = document.createElement("div");
|
|
||||||
item.classList.add("item");
|
|
||||||
item.onclick = callback;
|
|
||||||
item.onmouseover = () => item.classList.add("hover");
|
|
||||||
item.onmouseleave = () => item.classList.remove("hover");
|
|
||||||
|
|
||||||
const text = document.createElement("span");
|
|
||||||
text.classList.add("text");
|
|
||||||
text.innerText = name;
|
|
||||||
|
|
||||||
item.append(text);
|
|
||||||
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates the SubMenu in Playlist Context
|
|
||||||
function createPlaylistContextMenu() {
|
|
||||||
var createFromCurrent = new Spicetify.ContextMenu.Item(
|
|
||||||
MENU_BTN_CREATE_NEW,
|
|
||||||
(uris) => {
|
|
||||||
if (uris.length === 1) {
|
|
||||||
fetchPlaylist(uris[0])
|
|
||||||
.then((buffer) => highjackCreateDialog(buffer.tracks))
|
|
||||||
.catch((err) => Spicetify.showNotification(`${err}`));
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
Spicetify.showNotification("Unable to find Playlist URI")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(_) => true
|
|
||||||
)
|
|
||||||
|
|
||||||
var insertIntoBuffer = new Spicetify.ContextMenu.Item(
|
|
||||||
MENU_BTN_INSERT_BUFFER,
|
|
||||||
(uris) => {
|
|
||||||
|
|
||||||
if (uris.length === 1) {
|
|
||||||
fetchPlaylist(uris[0])
|
|
||||||
.then((buffer) => {LIST.addToStorage(buffer)})
|
|
||||||
.catch((err) => Spicetify.showNotification(`${err}`));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(_) => true
|
|
||||||
)
|
|
||||||
|
|
||||||
return new Spicetify.ContextMenu.SubMenu(
|
|
||||||
"Copy Playlist",
|
|
||||||
[ createFromCurrent, insertIntoBuffer],
|
|
||||||
(uris) => {
|
|
||||||
if (uris.length === 1) {
|
|
||||||
const uriObj = Spicetify.URI.fromString(uris[0]);
|
|
||||||
switch (uriObj.type) {
|
|
||||||
case Spicetify.URI.Type.PLAYLIST:
|
|
||||||
case Spicetify.URI.Type.PLAYLIST_V2:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Multiple Items selected.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates the Button to View Merge Buffer
|
|
||||||
function createTopBarButton() {
|
|
||||||
const button = document.createElement("button")
|
|
||||||
button.classList.add("button", "spoticon-copy-16", "merge-button")
|
|
||||||
button.setAttribute("data-tooltip", TOP_BTN_TOOLTIP)
|
|
||||||
button.setAttribute("data-contextmenu", "")
|
|
||||||
button.setAttribute("data-uri", "spotify:special:copy")
|
|
||||||
button.onclick = () => {
|
|
||||||
const bound = button.getBoundingClientRect()
|
|
||||||
LIST.changePosition(bound.left, bound.top)
|
|
||||||
document.body.append(LIST.container)
|
|
||||||
LIST.setScroll()
|
|
||||||
}
|
|
||||||
return button
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates the Dropdown Menu for Merge Pattern
|
|
||||||
function createPatternSelect(defaultOpt = 0) {
|
|
||||||
const select = document.createElement("select");
|
|
||||||
select.className = "GlueDropdown combine-pattern";
|
|
||||||
|
|
||||||
const appendOpt = document.createElement("option");
|
|
||||||
appendOpt.text = "Append";
|
|
||||||
|
|
||||||
const shuffleOpt = document.createElement("option");
|
|
||||||
shuffleOpt.text = "Shuffle";
|
|
||||||
|
|
||||||
const alternateOpt = document.createElement("option");
|
|
||||||
alternateOpt.text = "Alternate";
|
|
||||||
|
|
||||||
select.onclick = (ev) => ev.stopPropagation();
|
|
||||||
select.append(appendOpt, shuffleOpt, alternateOpt);
|
|
||||||
select.options[defaultOpt].selected = true;
|
|
||||||
|
|
||||||
return select;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Queue button
|
|
||||||
function createQueueButton(name, tooltip, callback) {
|
|
||||||
const b = document.createElement("button");
|
|
||||||
b.classList.add("button", "button-green");
|
|
||||||
b.innerText = name;
|
|
||||||
b.setAttribute("data-tooltip", tooltip);
|
|
||||||
b.onclick = callback;
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Highjack Spotifies 'New Playlist' Dialog
|
|
||||||
function highjackCreateDialog(tracks) {
|
|
||||||
playlistDialogButton.click()
|
|
||||||
|
|
||||||
var createButton = document.querySelector("body > div.Modal__portal > div > div > div > div.PlaylistAnnotationModal__submit-button-container > button")
|
|
||||||
var buttonContainer = document.querySelector("body > div.Modal__portal > div > div > div > div.PlaylistAnnotationModal__submit-button-container")
|
|
||||||
|
|
||||||
var highjackedButton = createButton.cloneNode(true)
|
|
||||||
highjackedButton.addEventListener("click", () => onCreateNewPlaylist(tracks))
|
|
||||||
|
|
||||||
window.addEventListener("keypress", (event) => {
|
|
||||||
if (event.code === `Enter`) {
|
|
||||||
// Cancel the default action, if needed
|
|
||||||
event.preventDefault();
|
|
||||||
// Trigger the button element with a click
|
|
||||||
createButton.click();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
createButton.remove()
|
|
||||||
buttonContainer.insertAdjacentElement("afterbegin", highjackedButton)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**************************************************************************
|
|
||||||
OnCLick Functions
|
|
||||||
**************************************************************************/
|
|
||||||
// Create a new Playlist from Inputs
|
|
||||||
function onCreateNewPlaylist(tracks) {
|
|
||||||
var exitButton = document.querySelector("body > div.Modal__portal > div > div > div > div.PlaylistAnnotationModal__close-button > button");
|
|
||||||
var nameInput = document.querySelector("body > div.Modal__portal > div > div > div > div.PlaylistAnnotationModal__content > div.PlaylistAnnotationModal__playlist-name > input")
|
|
||||||
var descInput = document.querySelector("body > div.Modal__portal > div > div > div > div.PlaylistAnnotationModal__content > div.PlaylistAnnotationModal__playlist-description > textarea")
|
|
||||||
var imageInput = document.querySelector("body > div.Modal__portal > div > div > div > div.PlaylistAnnotationModal__content > div.PlaylistAnnotationModal__img-container > div > div.PlaylistAnnotationModal__img-holder > img")
|
|
||||||
|
|
||||||
var name = nameInput.value
|
|
||||||
if (!name) {
|
|
||||||
name = nameInput.getAttribute("placeholder")
|
|
||||||
}
|
|
||||||
|
|
||||||
var desc = descInput.value
|
|
||||||
|
|
||||||
var img;
|
|
||||||
if (imageInput) {
|
|
||||||
img = imageInput.getAttribute("src")
|
|
||||||
}
|
|
||||||
|
|
||||||
createPlaylist(name)
|
|
||||||
.then(res => addTracks(res.uri, tracks))
|
|
||||||
.then((_) => Spicetify.showNotification(`Created Playlist: "${name}"`))
|
|
||||||
.catch((err) => Spicetify.showNotification(`${err}`));
|
|
||||||
|
|
||||||
exitButton.click()
|
|
||||||
|
|
||||||
if (exitButton) {
|
|
||||||
exitButton.click()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get All Tracks in Queue and remove delimiter
|
|
||||||
function getQueueTracks() {
|
|
||||||
return Spicetify.Queue.next_tracks
|
|
||||||
.map((t) => t.uri)
|
|
||||||
.filter((t) => { return t != "spotify:delimiter"; })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy the Queue to the Combine Buffer
|
|
||||||
function queueToBuffer() {
|
|
||||||
let tracks = getQueueTracks();
|
|
||||||
|
|
||||||
var date = new Date()
|
|
||||||
var year = new Intl.DateTimeFormat('en', { year: 'numeric' }).format(date);
|
|
||||||
var month = new Intl.DateTimeFormat('en', { month: 'short' }).format(date);
|
|
||||||
var day = new Intl.DateTimeFormat('en', { day: '2-digit' }).format(date);
|
|
||||||
|
|
||||||
const timeOptions = { hour: 'numeric', minute: 'numeric', hour12: false};
|
|
||||||
var time = new Intl.DateTimeFormat(`en`, timeOptions).format(date);
|
|
||||||
|
|
||||||
let queue = {
|
|
||||||
id: `spotify:queue-${date}`,
|
|
||||||
uri: `spotify:queue`,
|
|
||||||
name: "Queue",
|
|
||||||
imgUri: undefined,
|
|
||||||
tracks: tracks,
|
|
||||||
owner: `${time} - ${day} ${month} ${year}`,
|
|
||||||
}
|
|
||||||
|
|
||||||
LIST.addToStorage(queue);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show the clicked Playlist
|
|
||||||
async function showPlaylist(event) {
|
|
||||||
console.log(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**************************************************************************
|
|
||||||
Merge Playlists
|
|
||||||
**************************************************************************/
|
|
||||||
// Merge all Playlists
|
|
||||||
function mergePlaylists(pattern) {
|
|
||||||
var tracks = LIST.getStorage().map((pl) => pl.tracks)
|
|
||||||
|
|
||||||
switch (pattern) {
|
|
||||||
case 1: return shuffle(tracks);
|
|
||||||
case 2: return alternate(tracks);
|
|
||||||
default: return append(tracks);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Alternate Playlists
|
|
||||||
function alternate(arrays) {
|
|
||||||
var combined = []
|
|
||||||
|
|
||||||
while (arrays.length != 0) {
|
|
||||||
var current = arrays.shift()
|
|
||||||
if (current.length != 0) {
|
|
||||||
combined.push(current.shift())
|
|
||||||
|
|
||||||
if (current.length != 0) {
|
|
||||||
arrays.push(current)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return combined
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shuffle all tracks using the Durstenfeld Shuffle
|
|
||||||
function shuffle(arrays) {
|
|
||||||
var combined = append(arrays)
|
|
||||||
|
|
||||||
for (var i = combined.length - 1; i > 0; i--) {
|
|
||||||
var j = Math.floor(Math.random() * (i + 1));
|
|
||||||
var temp = combined[i];
|
|
||||||
combined[i] = combined[j];
|
|
||||||
combined[j] = temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
return combined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simply Concat all Playlist
|
|
||||||
function append(arrays) {
|
|
||||||
var combined = []
|
|
||||||
arrays.forEach((arr) => combined = combined.concat(arr))
|
|
||||||
|
|
||||||
return combined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**************************************************************************
|
|
||||||
Calls to the CosmosAPI
|
|
||||||
**************************************************************************/
|
|
||||||
// Fetch all Track from Playlist URI
|
|
||||||
async function fetchPlaylist(uri) {
|
|
||||||
return await new Promise((resolve, reject) => {
|
|
||||||
Spicetify.BridgeAPI.cosmosJSON(
|
|
||||||
{
|
|
||||||
method: "GET",
|
|
||||||
uri: `sp://core-playlist/v1/playlist/${uri}/`,
|
|
||||||
body: {
|
|
||||||
policy: {
|
|
||||||
link: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
(error, res) => {
|
|
||||||
if (error) {
|
|
||||||
reject(error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let id = `${uri}-${new Date()}`
|
|
||||||
let tracks = res.items.map((track) => track.link)
|
|
||||||
let img = res.playlist.picture
|
|
||||||
let name = res.playlist.name
|
|
||||||
let owner = res.playlist.owner.name
|
|
||||||
|
|
||||||
let playlist = {id: id, uri: uri, name: name, tracks: tracks, imgUri: img, owner: owner}
|
|
||||||
|
|
||||||
resolve(playlist);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new Playlist
|
|
||||||
async function createPlaylist(name) {
|
|
||||||
return await new Promise((resolve, reject) => {
|
|
||||||
Spicetify.BridgeAPI.cosmosJSON(
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
uri: `sp://core-playlist/v1/rootlist`,
|
|
||||||
body: {
|
|
||||||
operation: "create",
|
|
||||||
playlist: !0,
|
|
||||||
before: "start",
|
|
||||||
name: name,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
(error, res) => {
|
|
||||||
if (error) {
|
|
||||||
reject(error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(res);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// add track list to playlist
|
|
||||||
async function addTracks(uri, tracks) {
|
|
||||||
return await new Promise((resolve, reject) => {
|
|
||||||
Spicetify.BridgeAPI.cosmosJSON(
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
uri: `sp://core-playlist/v1/playlist/${uri}`,
|
|
||||||
body: {
|
|
||||||
operation: "add",
|
|
||||||
uris: tracks,
|
|
||||||
after: "end"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(error, res) => {
|
|
||||||
if (error) {
|
|
||||||
reject(error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(res);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})();
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,125 +0,0 @@
|
||||||
// <reference path="./globals.d.ts" />
|
|
||||||
|
|
||||||
(function Genre() {
|
|
||||||
const { CosmosAsync, Player } = Spicetify;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch genre from artist
|
|
||||||
*
|
|
||||||
* @param artistURI {string}
|
|
||||||
* @return {Promise<Array>}
|
|
||||||
*/
|
|
||||||
const fetchGenres = async (artistURI) => {
|
|
||||||
const res = await CosmosAsync.get(
|
|
||||||
`https://api.spotify.com/v1/artists/${artistURI}`
|
|
||||||
);
|
|
||||||
// noinspection JSUnresolvedVariable
|
|
||||||
return res.genres.slice(0, 3) // Only keep the first 3 genres
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch playlist from The Sound of Spotify for a given genre
|
|
||||||
* @param {String} genre
|
|
||||||
* @return {String|null}
|
|
||||||
*/
|
|
||||||
const fetchSoundOfSpotifyPlaylist = async (genre) => {
|
|
||||||
const query = encodeURIComponent(`The Sound of ${genre}`);
|
|
||||||
// Check localStorage for playlist
|
|
||||||
const cached = localStorage.getItem(`everynoise:${query}`);
|
|
||||||
if (cached !== null) {
|
|
||||||
return cached;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search for playlist and check results for the everynoise account
|
|
||||||
const re = new RegExp(`^the sound of ${genre.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'i');
|
|
||||||
const res = await CosmosAsync.get(`https://api.spotify.com/v1/search?q=${query}&type=playlist`)
|
|
||||||
for (const item of res.playlists.items) {
|
|
||||||
if (item.owner.id === "thesoundsofspotify" && re.test(item.name)) {
|
|
||||||
localStorage.setItem(`everynoise:${genre}`, item.uri);
|
|
||||||
return item.uri
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// Store the current playback id
|
|
||||||
let playback = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {Node}
|
|
||||||
*/
|
|
||||||
let genreContainer = null;
|
|
||||||
|
|
||||||
let infoContainer = document.querySelector('div.main-trackInfo-container');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove genre injection in the UI
|
|
||||||
*/
|
|
||||||
const cleanInjection = () => {
|
|
||||||
if (genreContainer !== null) {
|
|
||||||
try {
|
|
||||||
infoContainer.removeChild(genreContainer);
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inject genres to UI
|
|
||||||
*/
|
|
||||||
const inject = () => {
|
|
||||||
Player.addEventListener('onprogress', async () => {
|
|
||||||
if (Player.data.track.metadata.hasOwnProperty('artist_uri')) {
|
|
||||||
// If the registered song isn't the same as the one currently being played then fetch genres
|
|
||||||
if (playback !== Player.data.playback_id) {
|
|
||||||
// Save the new track
|
|
||||||
playback = Player.data.playback_id;
|
|
||||||
const id = Player.data.track.metadata.artist_uri.split(':')[2];
|
|
||||||
const genres = await fetchGenres(id);
|
|
||||||
|
|
||||||
cleanInjection();
|
|
||||||
|
|
||||||
genreContainer = document.createElement('div');
|
|
||||||
// noinspection JSUndefinedPropertyAssignment
|
|
||||||
genreContainer.className = 'main-trackInfo-genres ellipsis-one-line main-type-finale';
|
|
||||||
// noinspection JSUnresolvedVariable
|
|
||||||
genreContainer.style.color = 'var(--spice-extratext)';
|
|
||||||
|
|
||||||
for (const i in genres) {
|
|
||||||
let element;
|
|
||||||
const uri = await fetchSoundOfSpotifyPlaylist(genres[i]);
|
|
||||||
if (uri !== null) {
|
|
||||||
element = document.createElement('a');
|
|
||||||
element.innerHTML = genres[i];
|
|
||||||
element.href = uri;
|
|
||||||
} else {
|
|
||||||
element = document.createElement('span');
|
|
||||||
}
|
|
||||||
element.innerHTML = genres[i];
|
|
||||||
element.style.fontSize ="11px";
|
|
||||||
genreContainer.appendChild(element);
|
|
||||||
if (i < genres.length-1) {
|
|
||||||
const separator = document.createElement('span');
|
|
||||||
separator.innerHTML = ', ';
|
|
||||||
genreContainer.appendChild(separator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
infoContainer = document.querySelector('div.main-trackInfo-container');
|
|
||||||
if(!infoContainer) cleanInjection();
|
|
||||||
infoContainer.appendChild(genreContainer);
|
|
||||||
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cleanInjection();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!CosmosAsync) {
|
|
||||||
setTimeout(Genre, 500);
|
|
||||||
} else {
|
|
||||||
inject();
|
|
||||||
}
|
|
||||||
})();
|
|
File diff suppressed because one or more lines are too long
|
@ -1,127 +0,0 @@
|
||||||
// NAME: History Shortcut
|
|
||||||
// AUTHOR: einzigartigerName
|
|
||||||
// DESCRIPTION: Adds a Shortcut to your Listening History to the Sidebar
|
|
||||||
|
|
||||||
(function HistoryShortcut() {
|
|
||||||
|
|
||||||
const { CosmosAPI, Player, LocalStorage, PlaybackControl, ContextMenu, URI } = Spicetify
|
|
||||||
if (!(CosmosAPI && Player && LocalStorage && PlaybackControl && ContextMenu && URI)) {
|
|
||||||
setTimeout(HistoryShortcut, 300)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const ITEM_LABEL = "History"
|
|
||||||
|
|
||||||
const HISTORY_DIV_CLASS = "SidebarListItem"
|
|
||||||
const HISTORY_DIV_CLASS_ACTIVE = "SidebarListItem SidebarListItem--is-active"
|
|
||||||
|
|
||||||
const HISTORY_ANKER_CLASS = "SidebarListItemLink SidebarListItemLink--tall spoticon-time-24"
|
|
||||||
const HISTORY_ANKER_CLASS_ACTIVE = "SidebarListItemLink SidebarListItemLink--is-highlighted SidebarListItemLink--tall spoticon-time-24"
|
|
||||||
|
|
||||||
let historyItem = createHistoyItem()
|
|
||||||
// Get Sidebar Lists
|
|
||||||
var topicList = document.querySelector("#view-navigation-bar > div > div.LeftSidebar__section > div > ul")
|
|
||||||
if (topicList) {
|
|
||||||
// Add to first in list
|
|
||||||
// On default layout this would be the Home/Browse/Radio List
|
|
||||||
topicList.appendChild(historyItem.div)
|
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const toCheckMutate = document.getElementById('view-content');
|
|
||||||
const config = { attributes: true, childList: true, subtree: true };
|
|
||||||
|
|
||||||
let observerCallback = function(_, _) {
|
|
||||||
appQueue = document.getElementById("app-queue")
|
|
||||||
if (!appQueue){ return }
|
|
||||||
|
|
||||||
if (appQueue.getAttribute("class") === "active"
|
|
||||||
&& appQueue.getAttribute("data-app-uri") === "spotify:app:queue:history"
|
|
||||||
) {
|
|
||||||
onClickHistory()
|
|
||||||
} else {
|
|
||||||
onLeaveHistory()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let observer = new MutationObserver(observerCallback)
|
|
||||||
observer.observe(toCheckMutate, config)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Deactivate Active Status for History Item
|
|
||||||
function onLeaveHistory() {
|
|
||||||
historyItem.div.setAttribute("class",HISTORY_DIV_CLASS)
|
|
||||||
historyItem.anker.setAttribute("class", HISTORY_ANKER_CLASS)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Activate Active Status for History Item
|
|
||||||
function onClickHistory() {
|
|
||||||
historyItem.div.setAttribute("class", HISTORY_DIV_CLASS_ACTIVE)
|
|
||||||
historyItem.anker.setAttribute("class", HISTORY_ANKER_CLASS_ACTIVE)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Construct the List Item
|
|
||||||
function createHistoyItem() {
|
|
||||||
/* List Item
|
|
||||||
* <li class="SidebarListItem">
|
|
||||||
*/
|
|
||||||
let listItem = document.createElement("li")
|
|
||||||
listItem.setAttribute("class", HISTORY_DIV_CLASS)
|
|
||||||
|
|
||||||
/* Outer Div Element
|
|
||||||
* <div class="DropTarget SidebarListItem__drop-target DropTarget--tracks DropTarget--albums DropTarget--artists DropTarget--playlists">
|
|
||||||
*/
|
|
||||||
let outer = document.createElement("div")
|
|
||||||
outer.setAttribute("class", "DropTarget SidebarListItem__drop-target")
|
|
||||||
|
|
||||||
/* Middle Div Element
|
|
||||||
* <div class="SidebarListItem__inner">
|
|
||||||
*/
|
|
||||||
let inner = document.createElement("div")
|
|
||||||
inner.setAttribute("class", "SidebarListItem__inner")
|
|
||||||
|
|
||||||
/* Link Div Element
|
|
||||||
* <div class="SidebarListItem__link">
|
|
||||||
*/
|
|
||||||
let link = document.createElement("div")
|
|
||||||
link.setAttribute("class", "SidebarListItem__link")
|
|
||||||
|
|
||||||
/* Anker
|
|
||||||
* <a class="SidebarListItemLink SidebarListItemLink--tall spoticon-time-24"
|
|
||||||
* draggable="false"
|
|
||||||
* href="spotify:app:queue:history"
|
|
||||||
* data-sidebar-list-item-uri="spotify:app:queue:history"
|
|
||||||
* data-ta-id="sidebar-list-item-link">
|
|
||||||
*/
|
|
||||||
anker = document.createElement("a")
|
|
||||||
anker.setAttribute("class", HISTORY_ANKER_CLASS)
|
|
||||||
anker.setAttribute("draggable", "false")
|
|
||||||
anker.setAttribute("href", "spotify:app:queue:history")
|
|
||||||
anker.setAttribute("data-sidebar-list-item-uri", "spotify:app:queue:history")
|
|
||||||
anker.setAttribute("data-ta-id", "sidebar-list-item-link")
|
|
||||||
|
|
||||||
/* Item Text
|
|
||||||
* <span class="SidebarListItem__label"
|
|
||||||
* dir="auto">
|
|
||||||
* History
|
|
||||||
* </span>
|
|
||||||
*/
|
|
||||||
span = document.createElement("span")
|
|
||||||
span.setAttribute("class", "SidebarListItem__label")
|
|
||||||
span.setAttribute("dir", "auto")
|
|
||||||
span.textContent = ITEM_LABEL
|
|
||||||
|
|
||||||
|
|
||||||
anker.appendChild(span)
|
|
||||||
link.appendChild(anker)
|
|
||||||
inner.appendChild(link)
|
|
||||||
outer.appendChild(inner)
|
|
||||||
listItem.appendChild(outer)
|
|
||||||
|
|
||||||
listItem.addEventListener("click", onClickHistory)
|
|
||||||
|
|
||||||
return {div: listItem, anker: anker}
|
|
||||||
}
|
|
||||||
})();
|
|
|
@ -1,532 +0,0 @@
|
||||||
//@ts-check
|
|
||||||
|
|
||||||
// NAME: Keyboard Shortcut
|
|
||||||
// AUTHOR: dax
|
|
||||||
// DESCRIPTION: Register a few more keybinds to support keyboard-driven navigation in Spotify client.
|
|
||||||
|
|
||||||
/// <reference path="../spicetify-cli/globals.d.ts" />
|
|
||||||
|
|
||||||
(function KeyboardShortcutMy() {
|
|
||||||
if (!Spicetify.Keyboard) {
|
|
||||||
setTimeout(KeyboardShortcutMy, 1000);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SCROLL_STEP = 50;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register your own keybind with function `registerBind`
|
|
||||||
*
|
|
||||||
* Syntax:
|
|
||||||
* registerBind(keyName, ctrl, shift, alt, callback)
|
|
||||||
*
|
|
||||||
* ctrl, shift and alt are boolean, true or false
|
|
||||||
*
|
|
||||||
* Valid keyName:
|
|
||||||
* - BACKSPACE - C - Y - F3
|
|
||||||
* - TAB - D - Z - F4
|
|
||||||
* - ENTER - E - WINDOW_LEFT - F5
|
|
||||||
* - SHIFT - F - WINDOW_RIGHT - F6
|
|
||||||
* - CTRL - G - SELECT - F7
|
|
||||||
* - ALT - H - NUMPAD_0 - F8
|
|
||||||
* - PAUSE/BREAK - I - NUMPAD_1 - F9
|
|
||||||
* - CAPS - J - NUMPAD_2 - F10
|
|
||||||
* - ESCAPE - K - NUMPAD_3 - F11
|
|
||||||
* - SPACE - L - NUMPAD_4 - F12
|
|
||||||
* - PAGE_UP - M - NUMPAD_5 - NUM_LOCK
|
|
||||||
* - PAGE_DOWN - N - NUMPAD_6 - SCROLL_LOCK
|
|
||||||
* - END - O - NUMPAD_7 - ;
|
|
||||||
* - HOME - P - NUMPAD_8 - =
|
|
||||||
* - ARROW_LEFT - Q - NUMPAD_9 - ,
|
|
||||||
* - ARROW_UP - R - MULTIPLY - -
|
|
||||||
* - ARROW_RIGHT - S - ADD - /
|
|
||||||
* - ARROW_DOWN - T - SUBTRACT - `
|
|
||||||
* - INSERT - U - DECIMAL_POINT - [
|
|
||||||
* - DELETE - V - DIVIDE - \
|
|
||||||
* - A - W - F1 - ]
|
|
||||||
* - B - X - F2 - "
|
|
||||||
*
|
|
||||||
* Use one of keyName as a string. If key that you want isn't in that list,
|
|
||||||
* you can also put its keycode number in keyName as a number.
|
|
||||||
*
|
|
||||||
* callback is name of function you want your shortcut to bind to. It also
|
|
||||||
* returns one KeyboardEvent parameter.
|
|
||||||
*
|
|
||||||
* Following are my default keybinds, use them as examples.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
//My Personal Binds----------------------------------------------
|
|
||||||
|
|
||||||
// Seek to progress percent of song
|
|
||||||
registerBind("NUMPAD_0", false, false, false, ()=>Spicetify.Player.seek(0));
|
|
||||||
registerBind("NUMPAD_1", false, false, false, ()=>Spicetify.Player.seek(.1));
|
|
||||||
registerBind("NUMPAD_2", false, false, false, ()=>Spicetify.Player.seek(.2));
|
|
||||||
registerBind("NUMPAD_3", false, false, false, ()=>Spicetify.Player.seek(.3));
|
|
||||||
registerBind("NUMPAD_4", false, false, false, ()=>Spicetify.Player.seek(.4));
|
|
||||||
registerBind("NUMPAD_5", false, false, false, ()=>Spicetify.Player.seek(.5));
|
|
||||||
registerBind("NUMPAD_6", false, false, false, ()=>Spicetify.Player.seek(.6));
|
|
||||||
registerBind("NUMPAD_7", false, false, false, ()=>Spicetify.Player.seek(.7));
|
|
||||||
registerBind("NUMPAD_8", false, false, false, ()=>Spicetify.Player.seek(.8));
|
|
||||||
registerBind("NUMPAD_9", false, false, false, ()=>Spicetify.Player.seek(.9));
|
|
||||||
|
|
||||||
|
|
||||||
// Q to open Queue page
|
|
||||||
registerBind("Q", false, false, false, clickQueueButton);
|
|
||||||
|
|
||||||
// C to open current playing context, L to open Lyrics, H to open Home Tab , Y to Open the Ypur Library, S to open the Lyrics Plus Custom App
|
|
||||||
registerBind("C", false, false, false, openContext)
|
|
||||||
registerBind("L", false, false, false, toggleLyrics);
|
|
||||||
registerBind("H", false, false, false, openHome);
|
|
||||||
registerBind("Y", false, false, false, openLibrary);
|
|
||||||
registerBind("S", false, false, false, openLyrics);
|
|
||||||
|
|
||||||
// Arrow keys to change volume
|
|
||||||
registerBind("ARROW_DOWN", false, false, false, decreaseVolume);
|
|
||||||
registerBind("ARROW_UP" , false, false, false, increaseVolume);
|
|
||||||
|
|
||||||
// Arrow keys to seek track
|
|
||||||
registerBind("ARROW_RIGHT", false, false, false, seekForward);
|
|
||||||
registerBind("ARROW_LEFT", false, false, false, seekBack);
|
|
||||||
|
|
||||||
|
|
||||||
function sleep (time) {
|
|
||||||
return new Promise((resolve) => setTimeout(resolve, time));
|
|
||||||
}
|
|
||||||
|
|
||||||
function clickQueueButton() {
|
|
||||||
document.querySelector('.control-button-wrapper>button[Aria-label="Queue"]').click();
|
|
||||||
}
|
|
||||||
|
|
||||||
function openContext(){
|
|
||||||
big = document.querySelector("#main > div > div.Root__top-container > nav > div.main-navBar-navBar > div:nth-child(3) > div > div > a > div")
|
|
||||||
small = document.querySelector("#main > div > div.Root__top-container > div.Root__now-playing-bar > footer > div > div.main-nowPlayingBar-left > div > div.main-coverSlotCollapsed-container > div > a > div")
|
|
||||||
if(big)
|
|
||||||
big.click();
|
|
||||||
else
|
|
||||||
small.click()
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleLyrics() {
|
|
||||||
document.querySelector('button[title="Popup Lyrics"]').click()
|
|
||||||
}
|
|
||||||
function openHome(){
|
|
||||||
ele = document.querySelector(`.main-navBar-navBar a[href="/"]`)
|
|
||||||
if(ele)
|
|
||||||
ele.click();
|
|
||||||
}
|
|
||||||
function openLyrics(){
|
|
||||||
ele = document.querySelector(`.main-navBar-navBar a[href="/lyrics-plus"]`)
|
|
||||||
if(ele)
|
|
||||||
ele.click();
|
|
||||||
}
|
|
||||||
function openLibrary(){
|
|
||||||
ele = document.querySelector(`.main-navBar-navBar a[href="/collection"]`)
|
|
||||||
if(ele)
|
|
||||||
ele.click();
|
|
||||||
}
|
|
||||||
function seekForward(){
|
|
||||||
Spicetify.Player.skipForward(5000)
|
|
||||||
}
|
|
||||||
function seekBack(){
|
|
||||||
Spicetify.Player.skipBack(5000)
|
|
||||||
}
|
|
||||||
async function decreaseVolume(){
|
|
||||||
if(!document.querySelector(".main-trackList-selected")){
|
|
||||||
if(Spicetify.Platform?.PlaybackAPI === undefined) Spicetify.Player?.origin?.setVolume(getVolume() - 0.05)
|
|
||||||
else await Spicetify.Platform.PlaybackAPI.setVolume(getVolume() - 0.05)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async function increaseVolume(){
|
|
||||||
if(!document.querySelector(".main-trackList-selected")){
|
|
||||||
if(Spicetify.Platform?.PlaybackAPI === undefined) Spicetify.Player?.origin?.setVolume(getVolume() + 0.05)
|
|
||||||
else await Spicetify.Platform.PlaybackAPI.setVolume(getVolume() + 0.05)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function getVolume(){
|
|
||||||
return (Spicetify.Player?.origin?._volume?._volume ?? Spicetify.Platform?.PlaybackAPI?._volume)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Ctrl + Tab and Ctrl + Shift + Tab to switch sidebar items
|
|
||||||
registerBind("TAB", true, false, false, rotateSidebarDown);
|
|
||||||
registerBind("TAB", true, true, false, rotateSidebarUp);
|
|
||||||
|
|
||||||
// Ctrl + Q to open Queue page
|
|
||||||
// registerBind("Q", true, false, false, clickQueueButton);
|
|
||||||
|
|
||||||
// Shift + H and Shift + L to go back and forward page
|
|
||||||
registerBind("H", false, true, false, clickNavigatingBackButton);
|
|
||||||
registerBind("L", false, true, false, clickNavigatingForwardButton);
|
|
||||||
|
|
||||||
// PageUp, PageDown to focus on iframe app before scrolling
|
|
||||||
registerBind("PAGE_UP", false, true, false, focusOnApp);
|
|
||||||
registerBind("PAGE_DOWN", false, true, false, focusOnApp);
|
|
||||||
|
|
||||||
// J and K to vertically scroll app
|
|
||||||
registerBind("J", false, false, false, appScrollDown);
|
|
||||||
registerBind("K", false, false, false, appScrollUp);
|
|
||||||
|
|
||||||
// G and Shift + G to scroll to top and to bottom
|
|
||||||
registerBind("G", false, false, false, appScrollTop);
|
|
||||||
registerBind("G", false, true, false, appScrollBottom);
|
|
||||||
|
|
||||||
// M to Like/Unlike track
|
|
||||||
registerBind("M", false, false, false, Spicetify.Player.toggleHeart);
|
|
||||||
|
|
||||||
// Forward Slash to open search page
|
|
||||||
registerBind("/", false, false, false, openSearchPage);
|
|
||||||
|
|
||||||
// A to activate Link Follow function
|
|
||||||
const vim = new VimBind();
|
|
||||||
registerBind("A", false, false, false, vim.activate.bind(vim));
|
|
||||||
|
|
||||||
// Esc to cancel Link Follow
|
|
||||||
vim.setCancelKey("ESCAPE")
|
|
||||||
vim.setCancelKey("Z")
|
|
||||||
|
|
||||||
function rotateSidebarDown() {
|
|
||||||
rotateSidebar(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
function rotateSidebarUp() {
|
|
||||||
rotateSidebar(-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// function clickQueueButton() {
|
|
||||||
// document.querySelector(".control-button-wrapper .spoticon-queue-16").click();
|
|
||||||
// }
|
|
||||||
|
|
||||||
function clickNavigatingBackButton() {
|
|
||||||
document.querySelector(".main-topBar-historyButtons .main-topBar-back").click();
|
|
||||||
}
|
|
||||||
|
|
||||||
function clickNavigatingForwardButton() {
|
|
||||||
document.querySelector(".main-topBar-historyButtons .main-topBar-forward").click();
|
|
||||||
}
|
|
||||||
|
|
||||||
function appScrollDown() {
|
|
||||||
const app = focusOnApp();
|
|
||||||
if (app) {
|
|
||||||
app.scrollBy(0, SCROLL_STEP);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function appScrollUp() {
|
|
||||||
const app = focusOnApp();
|
|
||||||
if (app) {
|
|
||||||
app.scrollBy(0, -SCROLL_STEP);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function appScrollBottom() {
|
|
||||||
const app = focusOnApp();
|
|
||||||
app.scroll(0, app.scrollHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
function appScrollTop() {
|
|
||||||
const app = focusOnApp();
|
|
||||||
app.scroll(0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {KeyboardEvent} event
|
|
||||||
*/
|
|
||||||
function openSearchPage(event) {
|
|
||||||
const searchInput = document.querySelector(".main-topBar-topbarContentWrapper input");
|
|
||||||
if (searchInput) {
|
|
||||||
searchInput.focus();
|
|
||||||
} else {
|
|
||||||
const sidebarItem = document.querySelector(`.main-navBar-navBar a[href="/search"]`);
|
|
||||||
if (sidebarItem) {
|
|
||||||
sidebarItem.click();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {Spicetify.Keyboard.ValidKey} keyName
|
|
||||||
* @param {boolean} ctrl
|
|
||||||
* @param {boolean} shift
|
|
||||||
* @param {boolean} alt
|
|
||||||
* @param {(event: KeyboardEvent) => void} callback
|
|
||||||
*/
|
|
||||||
function registerBind(keyName, ctrl, shift, alt, callback) {
|
|
||||||
const key = Spicetify.Keyboard.KEYS[keyName];
|
|
||||||
|
|
||||||
Spicetify.Keyboard.registerShortcut(
|
|
||||||
{
|
|
||||||
key,
|
|
||||||
ctrl,
|
|
||||||
shift,
|
|
||||||
alt,
|
|
||||||
},
|
|
||||||
(event) => {
|
|
||||||
if (!vim.isActive) {
|
|
||||||
callback(event);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function focusOnApp() {
|
|
||||||
return document.querySelector("main .os-viewport");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
function findActiveIndex(allItems) {
|
|
||||||
const active = document.querySelector(
|
|
||||||
".main-navBar-navBarLinkActive, .main-collectionLinkButton-selected, .main-rootlist-rootlistItemLinkActive"
|
|
||||||
);
|
|
||||||
if (!active) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let index = 0;
|
|
||||||
for (const item of allItems) {
|
|
||||||
if (item === active) {
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {1 | -1} direction
|
|
||||||
*/
|
|
||||||
function rotateSidebar(direction) {
|
|
||||||
const allItems = document.querySelectorAll(
|
|
||||||
".main-navBar-navBarLink, .main-collectionLinkButton-collectionLinkButton, .main-rootlist-rootlistItemLink"
|
|
||||||
);
|
|
||||||
const maxIndex = allItems.length - 1;
|
|
||||||
let index = findActiveIndex(allItems) + direction;
|
|
||||||
|
|
||||||
if (index < 0) index = maxIndex;
|
|
||||||
else if (index > maxIndex) index = 0;
|
|
||||||
|
|
||||||
let toClick = allItems[index];
|
|
||||||
if (!toClick.hasAttribute("href")) {
|
|
||||||
toClick = toClick.querySelector(".main-rootlist-rootlistItemLink");
|
|
||||||
}
|
|
||||||
|
|
||||||
toClick.click();
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
function VimBind() {
|
|
||||||
const elementQuery = ["[href]", "button", "td.tl-play", "td.tl-number", "tr.TableRow"].join(",");
|
|
||||||
|
|
||||||
const keyList = "qwertasdfgzxcvyuiophjklbnm".split("");
|
|
||||||
|
|
||||||
const lastKeyIndex = keyList.length - 1;
|
|
||||||
|
|
||||||
this.isActive = false;
|
|
||||||
|
|
||||||
const vimOverlay = document.createElement("div");
|
|
||||||
vimOverlay.id = "vim-overlay";
|
|
||||||
vimOverlay.style.zIndex = "9999";
|
|
||||||
vimOverlay.style.position = "absolute";
|
|
||||||
vimOverlay.style.width = "100%";
|
|
||||||
vimOverlay.style.height = "100%";
|
|
||||||
vimOverlay.style.display = "none";
|
|
||||||
vimOverlay.innerHTML = `<style>
|
|
||||||
.vim-key {
|
|
||||||
position: fixed;
|
|
||||||
padding: 3px 6px;
|
|
||||||
background-color: black;
|
|
||||||
border-radius: 3px;
|
|
||||||
border: solid 2px white;
|
|
||||||
color: white;
|
|
||||||
text-transform: lowercase;
|
|
||||||
line-height: normal;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
</style>`;
|
|
||||||
document.body.append(vimOverlay);
|
|
||||||
|
|
||||||
const mousetrap = new Spicetify.Mousetrap(document);
|
|
||||||
mousetrap.bind(keyList, listenToKeys.bind(this), "keypress");
|
|
||||||
// Pause mousetrap event emitter
|
|
||||||
const orgStopCallback = mousetrap.stopCallback;
|
|
||||||
mousetrap.stopCallback = () => true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {KeyboardEvent} event
|
|
||||||
*/
|
|
||||||
this.activate = function (event) {
|
|
||||||
vimOverlay.style.display = "block";
|
|
||||||
|
|
||||||
const vimkey = getVims();
|
|
||||||
if (vimkey.length > 0) {
|
|
||||||
vimkey.forEach((e) => e.remove());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let firstKey = 0;
|
|
||||||
let secondKey = 0;
|
|
||||||
|
|
||||||
getLinks().forEach((e) => {
|
|
||||||
if (e.style.display === "none" || e.style.visibility === "hidden" || e.style.opacity === "0") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bound = e.getBoundingClientRect();
|
|
||||||
let owner = document.body;
|
|
||||||
|
|
||||||
let top = bound.top;
|
|
||||||
let left = bound.left;
|
|
||||||
|
|
||||||
if (
|
|
||||||
bound.bottom > owner.clientHeight ||
|
|
||||||
bound.left > owner.clientWidth ||
|
|
||||||
bound.right < 0 ||
|
|
||||||
bound.top < 0 ||
|
|
||||||
bound.width === 0 ||
|
|
||||||
bound.height === 0
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
vimOverlay.append(createKey(e, keyList[firstKey] + keyList[secondKey], top, left));
|
|
||||||
|
|
||||||
secondKey++;
|
|
||||||
if (secondKey > lastKeyIndex) {
|
|
||||||
secondKey = 0;
|
|
||||||
firstKey++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.isActive = true;
|
|
||||||
setTimeout(() => (mousetrap.stopCallback = orgStopCallback.bind(mousetrap)), 100);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {KeyboardEvent} event
|
|
||||||
*/
|
|
||||||
this.deactivate = function (event) {
|
|
||||||
mousetrap.stopCallback = () => true;
|
|
||||||
this.isActive = false;
|
|
||||||
vimOverlay.style.display = "none";
|
|
||||||
getVims().forEach((e) => e.remove());
|
|
||||||
};
|
|
||||||
|
|
||||||
function getLinks() {
|
|
||||||
const elements = Array.from(document.querySelectorAll(elementQuery));
|
|
||||||
return elements;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getVims() {
|
|
||||||
return Array.from(vimOverlay.getElementsByClassName("vim-key"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {KeyboardEvent} event
|
|
||||||
*/
|
|
||||||
function listenToKeys(event) {
|
|
||||||
if (!this.isActive) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const vimkey = getVims();
|
|
||||||
|
|
||||||
if (vimkey.length === 0) {
|
|
||||||
this.deactivate(event);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const div of vimkey) {
|
|
||||||
const text = div.innerText.toLowerCase();
|
|
||||||
if (text[0] !== event.key) {
|
|
||||||
div.remove();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newText = text.slice(1);
|
|
||||||
if (newText.length === 0) {
|
|
||||||
click(div.target);
|
|
||||||
this.deactivate(event);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.innerText = newText;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vimOverlay.childNodes.length === 1) {
|
|
||||||
this.deactivate(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function click(element) {
|
|
||||||
if (element.hasAttribute("href") || element.tagName === "BUTTON") {
|
|
||||||
element.click();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const findButton = element.querySelector(`button[data-ta-id="play-button"]`) || element.querySelector(`button[data-button="play"]`);
|
|
||||||
if (findButton) {
|
|
||||||
findButton.click();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
alert("Let me know where you found this button, please. I can't click this for you without that information.");
|
|
||||||
return;
|
|
||||||
// TableCell case where play button is hidden
|
|
||||||
// Index number is in first column
|
|
||||||
const index = parseInt(element.firstChild.innerText) - 1;
|
|
||||||
const context = getContextUri();
|
|
||||||
if (index >= 0 && context) {
|
|
||||||
console.log(index);
|
|
||||||
console.log(context);
|
|
||||||
|
|
||||||
//Spicetify.PlaybackControl.playFromResolver(context, { index }, () => {});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createKey(target, key, top, left) {
|
|
||||||
const div = document.createElement("span");
|
|
||||||
div.classList.add("vim-key");
|
|
||||||
div.innerText = key;
|
|
||||||
div.style.top = top + "px";
|
|
||||||
div.style.left = left + "px";
|
|
||||||
div.target = target;
|
|
||||||
return div;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getContextUri() {
|
|
||||||
const username = __spotify.username;
|
|
||||||
const activeApp = localStorage.getItem(username + ":activeApp");
|
|
||||||
if (activeApp) {
|
|
||||||
try {
|
|
||||||
return JSON.parse(activeApp).uri.replace("app:", "");
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {Spicetify.Keyboard.ValidKey} key
|
|
||||||
*/
|
|
||||||
this.setCancelKey = function (key) {
|
|
||||||
mousetrap.bind(Spicetify.Keyboard.KEYS[key], this.deactivate.bind(this));
|
|
||||||
};
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
var playlistDicons=(()=>{var s=Object.create,o=Object.defineProperty,c=Object.getOwnPropertyDescriptor,p=Object.getOwnPropertyNames,d=Object.getPrototypeOf,m=Object.prototype.hasOwnProperty,e=(e=>"undefined"!=typeof require?require:"undefined"!=typeof Proxy?new Proxy(e,{get:(e,t)=>("undefined"!=typeof require?require:e)[t]}):e)(function(e){if("react"===e)return Spicetify.React;if("react-dom"===e)return Spicetify.ReactDOM;if("undefined"!=typeof require)return require.apply(this,arguments);throw new Error('Dynamic require of "'+e+'" is not supported')}),t=(e,t,i)=>{i=null!=e?s(d(e)):{};var a=!t&&e&&e.__esModule?i:o(i,"default",{value:e,enumerable:!0}),n=e,l=void 0,r=void 0;if(n&&"object"==typeof n||"function"==typeof n)for(let e of p(n))m.call(a,e)||e===l||o(a,e,{get:()=>n[e],enumerable:!(r=c(n,e))||r.enumerable});return a},u=t(e("react")),y=t(e("react-dom"));var i=t(e("react"));function g(){return i.default.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",height:"24px",viewBox:"0 0 24 24",width:"24px",fill:"#FFFFFF"},i.default.createElement("path",{d:"M0 0h24v24H0V0z",fill:"none"}),i.default.createElement("path",{d:"M9.17 6l2 2H20v10H4V6h5.17M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"}))}var f=new Map;async function r(o){const e=await new Promise(t=>{const i=setInterval(()=>{var e=document.querySelectorAll("#spicetify-playlist-list li a");0<e.length&&(clearInterval(i),t(Array.from(e)))},100)});e.forEach(e=>{var t,i;const a=e.href.split("/").at(-1);var n=e.href.split("/").at(-2),l=f.get(a);if(null!=(t=e.parentElement)&&t.classList.add("playlist-item"),l)null!=(t=e.parentElement)&&t.prepend(l);else switch(n){case"playlist":var r=o.find(e=>e.id===a),r=function(e){const t=document.createElement(e?"img":"div");return t.classList.add("playlist-item__img"),e?t.setAttribute("src",e):t.classList.add("no-icon"),t}((null==(r=null==r?void 0:r.images[0])?void 0:r.url)||"");null!=(i=e.parentElement)&&i.prepend(r),f.set(a,r);break;case"folder":{const s=document.createElement("div");s.classList.add("playlist-item__img","folder"),y.default.render(u.default.createElement(g,null),s),null!=(i=e.parentElement)&&i.prepend(s),f.set(a,s);break}default:console.warn("[playlist-icons] playlist list anchor type not recognized: "+n)}})}var v="playlist-icons_big";var a=async function(){for(var e,a;null==Spicetify||!Spicetify.Platform||null==Spicetify||!Spicetify.CosmosAsync;)await new Promise(e=>setTimeout(e,100));const t=null!=(e=JSON.parse(localStorage.getItem(v)))&&e,i=await async function e(t){t=await Spicetify.CosmosAsync.get(t);return[...t.items,...t.next?await e(t.next):[]]}("https://api.spotify.com/v1/me/playlists?limit=50"),n=(a="#spicetify-playlist-list",await new Promise(t=>{const i=setInterval(()=>{var e=document.querySelector(a);e&&(clearInterval(i),t(e))},100)})),l=new MutationObserver(async()=>{l.disconnect(),await r(i),l.observe(n,{childList:!0,subtree:!0})});await r(i),l.observe(n,{childList:!0,subtree:!0}),t&&n.classList.add("big-icons"),new Spicetify.Menu.Item("Big playlist icons",t,e=>{e.setState(!e.isEnabled),localStorage.setItem(v,JSON.stringify(!t)),n.classList.toggle("big-icons")}).register()};(async()=>{await a()})()})();(async()=>{var e;document.getElementById("playlistDicons")||((e=document.createElement("style")).id="playlistDicons",e.textContent=String.raw`
|
|
||||||
:root{---playlist-img-spacing:6px}.playlist-item{padding-top:0;padding-bottom:0;align-items:center}.playlist-item__img{width:1.5em;height:1.5em;border-radius:2px;margin-right:12px;filter:brightness(85%)}.playlist-item__img.folder{background-color:var(--spice-tab-active);display:flex;align-items:center;justify-content:center}.playlist-item__img.folder svg{width:1.1em;height:1.1em}.playlist-item__img.no-icon{background-color:var(--spice-tab-active);height:1.5em}.playlist-item:hover .playlist-item__img{transition:.2s ease-out;filter:brightness(100%)}.big-icons .playlist-item{padding-top:var(---playlist-img-spacing);padding-bottom:var(---playlist-img-spacing)}.big-icons .playlist-item__img{border-radius:4px;width:2em;height:2em}.big-icons .playlist-item__img.folder{padding:4px}.big-icons .playlist-item__img.folder svg{width:1.5em;height:1.5em}.big-icons>div{contain:unset}
|
|
||||||
`.trim(),document.head.appendChild(e))})();
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,32 +0,0 @@
|
||||||
// @ts-check
|
|
||||||
|
|
||||||
// NAME: Add Volume Percentage
|
|
||||||
// AUTHOR: daksh2k
|
|
||||||
// DESCRIPTION: Add the Volume Percentage to the Volume Bar
|
|
||||||
|
|
||||||
/// <reference path="../globals.d.ts" />
|
|
||||||
|
|
||||||
(function addVolumep() {
|
|
||||||
const volumeBar = document.querySelector(".volume-bar");
|
|
||||||
if (!(volumeBar && Spicetify.Player)) {
|
|
||||||
setTimeout(addVolumep, 200);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const ele = document.createElement("span");
|
|
||||||
ele.classList.add("volume-percent");
|
|
||||||
ele.setAttribute("style", "font-size: 14px; padding-left: 10px; min-width: 45px;");
|
|
||||||
|
|
||||||
volumeBar.append(ele);
|
|
||||||
// @ts-ignore
|
|
||||||
volumeBar.style.flex = "0 1 180px";
|
|
||||||
|
|
||||||
updatePercentage();
|
|
||||||
function updatePercentage() {
|
|
||||||
const currVolume = Math.round((Spicetify.Player?.origin?._volume?._volume ?? Spicetify.Platform?.PlaybackAPI?._volume) * 100);
|
|
||||||
ele.innerText = currVolume == -100 ? `` : `${currVolume}%`;
|
|
||||||
// @ts-ignore
|
|
||||||
document.querySelector(".main-connectBar-connectBar")?.style.setProperty("--triangle-position", "229px");
|
|
||||||
}
|
|
||||||
if (Spicetify.Platform?.PlaybackAPI === undefined) Spicetify.Player.origin._events.addListener("volume", updatePercentage);
|
|
||||||
else Spicetify.Platform.PlaybackAPI._events.addListener("volume", updatePercentage);
|
|
||||||
})();
|
|
|
@ -1,96 +0,0 @@
|
||||||
//@ts-check
|
|
||||||
|
|
||||||
// NAME: Wikify
|
|
||||||
// AUTHOR: CharlieS1103
|
|
||||||
// DESCRIPTION: View an artists wikipedia page to learn more about them
|
|
||||||
|
|
||||||
/// <reference path="../../spicetify-cli/globals.d.ts" />
|
|
||||||
(function WikiFy() {
|
|
||||||
if (!document.body.classList.contains('wikify-injected')) {
|
|
||||||
var styleSheet = document.createElement("style")
|
|
||||||
|
|
||||||
styleSheet.innerHTML =
|
|
||||||
`body > generic-modal > div > div {
|
|
||||||
background-color: beige !important;
|
|
||||||
color: black !important;
|
|
||||||
} `
|
|
||||||
document.body.appendChild(styleSheet)
|
|
||||||
document.body.classList.add('wikify-injected');
|
|
||||||
}
|
|
||||||
const {
|
|
||||||
CosmosAsync,
|
|
||||||
URI
|
|
||||||
} = Spicetify;
|
|
||||||
if (!(CosmosAsync && URI)) {
|
|
||||||
setTimeout(WikiFy, 10);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const lang = Spicetify.Locale._locale;
|
|
||||||
const buttontxt = "View Wiki"
|
|
||||||
//Watch for when the song is changed
|
|
||||||
|
|
||||||
function error() {
|
|
||||||
Spicetify.PopupModal.display({
|
|
||||||
title: "Error",
|
|
||||||
content: "Selected artist does not have a WikiPedia page, Sorry."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getWikiText(uris) {
|
|
||||||
|
|
||||||
const rawUri = uris[0];
|
|
||||||
const uri = rawUri.split(":")[2]
|
|
||||||
const artistName = await CosmosAsync.get(`https://api.spotify.com/v1/artists/${uri}`)
|
|
||||||
const artistNameTrimmed = (artistName.name).replace(/\s/g, "%20");
|
|
||||||
|
|
||||||
if (artistName != null) {
|
|
||||||
try {
|
|
||||||
const wikiInfo = await CosmosAsync.get(`https://${lang}.wikipedia.org/w/api.php?action=query&format=json&prop=extracts%7Cdescription&titles=${artistNameTrimmed}`)
|
|
||||||
//TODO: option to choose local language or english / english fallback? / subcontextmenu to choose?
|
|
||||||
//https://en.wikipedia.org/w/api.php?action=query&format=json&uselang=en&list=search&srsearch=${artistNameTrimmed}
|
|
||||||
|
|
||||||
const wikiInfoArr = wikiInfo.query.pages
|
|
||||||
const page = Object.values(wikiInfoArr)[0];
|
|
||||||
if (page != null || page != undefined) {
|
|
||||||
const pageText = page.extract.replace(/<!--[\s\S]*?-->/g, '');
|
|
||||||
if (pageText != "\n") {
|
|
||||||
Spicetify.PopupModal.display({
|
|
||||||
title: "WikiFy",
|
|
||||||
content: page.extract
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
error();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
error();
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
Spicetify.PopupModal.display({
|
|
||||||
title: "Error",
|
|
||||||
content: "Request failed",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function shouldDisplayContextMenu(uris) {
|
|
||||||
if (uris.length > 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const uri = uris[0];
|
|
||||||
const uriObj = Spicetify.URI.fromString(uri);
|
|
||||||
if (uriObj.type === Spicetify.URI.Type.TRACK || uriObj.type === Spicetify.URI.Type.ARTIST) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cntxMenu = new Spicetify.ContextMenu.Item(
|
|
||||||
buttontxt,
|
|
||||||
getWikiText,
|
|
||||||
shouldDisplayContextMenu,
|
|
||||||
);
|
|
||||||
|
|
||||||
cntxMenu.register();
|
|
||||||
|
|
||||||
})();
|
|
|
@ -1,761 +0,0 @@
|
||||||
{
|
|
||||||
"Color-Scheme": "catppuccin-macchiato",
|
|
||||||
"Scheme-Features": "mono",
|
|
||||||
"Flatten-Colors": "normal",
|
|
||||||
"Dark-Modals": "dark",
|
|
||||||
"App-Title": "",
|
|
||||||
"Button-Radius": "10",
|
|
||||||
"Custom-Font": true,
|
|
||||||
"Home-Header-Snippet": true,
|
|
||||||
"Home-Header-Color": "",
|
|
||||||
"Topbar-Inside-Titlebar-Snippet": false,
|
|
||||||
"Horizontal-pageLinks-Snippet": false,
|
|
||||||
"visible-column-bar-Snippet": false,
|
|
||||||
"Tracklist-Gradient-Height": "",
|
|
||||||
"Hoverable-Timers-Snippet": false,
|
|
||||||
"Remove-Device-Picker-Notification-Snippet": true,
|
|
||||||
"Remove-Progress-Bar-Gradient-Snippet": false,
|
|
||||||
"Remove-Lyrics-Button-Snippet": true,
|
|
||||||
"Custom-Cover-Art-Dimensions": false,
|
|
||||||
"Right-Art-Snippet": false,
|
|
||||||
"Image-Blur": "",
|
|
||||||
"Apple-Music-Gradient-Snippet": true,
|
|
||||||
"Custom-Image": true,
|
|
||||||
"Color-Schemes": {
|
|
||||||
"Comfy": {
|
|
||||||
"text": "FFFFFF",
|
|
||||||
"subtext": "B9BBBE",
|
|
||||||
"main": "23283D",
|
|
||||||
"main-elevated": "32364A",
|
|
||||||
"main-transition": "1E2233",
|
|
||||||
"highlight": "45495B",
|
|
||||||
"highlight-elevated": "383D50",
|
|
||||||
"sidebar": "1E2233",
|
|
||||||
"player": "101320",
|
|
||||||
"card": "101320",
|
|
||||||
"shadow": "1E2233",
|
|
||||||
"selected-row": "F1F1F1",
|
|
||||||
"button": "7289DA",
|
|
||||||
"button-active": "5C6FB1",
|
|
||||||
"button-disabled": "4B588C",
|
|
||||||
"tab-active": "1E2233",
|
|
||||||
"notification": "7289DA",
|
|
||||||
"notification-error": "D25050",
|
|
||||||
"misc": "000000",
|
|
||||||
"play-button": "7289DA",
|
|
||||||
"play-button-active": "869ADF",
|
|
||||||
"progress-fg": "1ED760",
|
|
||||||
"progress-bg": "7289DA",
|
|
||||||
"heart": "D25050",
|
|
||||||
"pagelink-active": "5C6EB1",
|
|
||||||
"radio-btn-active": "7289DA"
|
|
||||||
},
|
|
||||||
"Spotify": {
|
|
||||||
"text": "FFFFFF",
|
|
||||||
"subtext": "B3B3B3",
|
|
||||||
"main": "121212",
|
|
||||||
"main-elevated": "242424",
|
|
||||||
"main-transition": "000000",
|
|
||||||
"highlight": "1A1A1A",
|
|
||||||
"highlight-elevated": "2A2A2A",
|
|
||||||
"sidebar": "000000",
|
|
||||||
"player": "181818",
|
|
||||||
"card": "282828",
|
|
||||||
"shadow": "000000",
|
|
||||||
"selected-row": "FFFFFF",
|
|
||||||
"button": "1DB954",
|
|
||||||
"button-active": "1ED760",
|
|
||||||
"button-disabled": "535353",
|
|
||||||
"tab-active": "333333",
|
|
||||||
"notification": "4687D6",
|
|
||||||
"notification-error": "E22134",
|
|
||||||
"misc": "7F7F7F",
|
|
||||||
"play-button": "1DB954",
|
|
||||||
"play-button-active": "1DB954",
|
|
||||||
"progress-fg": "1DB954",
|
|
||||||
"progress-bg": "636363",
|
|
||||||
"heart": "1DB954",
|
|
||||||
"pagelink-active": "333333",
|
|
||||||
"radio-btn-active": "1DB954"
|
|
||||||
},
|
|
||||||
"Nord": {
|
|
||||||
"text": "B2BCCC",
|
|
||||||
"subtext": "B2BCCC",
|
|
||||||
"main": "2E3440",
|
|
||||||
"main-elevated": "3C414C",
|
|
||||||
"main-transition": "262B35",
|
|
||||||
"highlight": "50555F",
|
|
||||||
"highlight-elevated": "424852",
|
|
||||||
"sidebar": "262B35",
|
|
||||||
"player": "2E3440",
|
|
||||||
"card": "363D4C",
|
|
||||||
"shadow": "1D2128",
|
|
||||||
"selected-row": "B2BCCC",
|
|
||||||
"button": "8A99AF",
|
|
||||||
"button-active": "718CAD",
|
|
||||||
"button-disabled": "434C5E",
|
|
||||||
"tab-active": "363D4C",
|
|
||||||
"notification": "363D4C",
|
|
||||||
"notification-error": "A9555E",
|
|
||||||
"misc": "FFFFFF",
|
|
||||||
"play-button": "8A99AF",
|
|
||||||
"play-button-active": "718CAD",
|
|
||||||
"progress-fg": "8A99AF",
|
|
||||||
"progress-bg": "4B566A",
|
|
||||||
"heart": "718CAD",
|
|
||||||
"pagelink-active": "B7C1D5",
|
|
||||||
"radio-btn-active": "363D4C"
|
|
||||||
},
|
|
||||||
"Everforest": {
|
|
||||||
"text": "D3C6AA",
|
|
||||||
"subtext": "9AA79D",
|
|
||||||
"main": "272E33",
|
|
||||||
"main-elevated": "30363A",
|
|
||||||
"main-transition": "495156",
|
|
||||||
"highlight": "444747",
|
|
||||||
"highlight-elevated": "34393D",
|
|
||||||
"sidebar": "2E383C",
|
|
||||||
"player": "272E33",
|
|
||||||
"card": "374145",
|
|
||||||
"shadow": "374145",
|
|
||||||
"selected-row": "D3C6AA",
|
|
||||||
"button": "A7C080",
|
|
||||||
"button-active": "AEC984",
|
|
||||||
"button-disabled": "374145",
|
|
||||||
"tab-active": "181C1E",
|
|
||||||
"notification": "86AF87",
|
|
||||||
"notification-error": "E67E80",
|
|
||||||
"misc": "000000",
|
|
||||||
"play-button": "A7C080",
|
|
||||||
"play-button-active": "AEC984",
|
|
||||||
"progress-fg": "A7C080",
|
|
||||||
"progress-bg": "9DA9A0",
|
|
||||||
"heart": "A7C080",
|
|
||||||
"pagelink-active": "181C1E",
|
|
||||||
"radio-btn-active": "86AF87"
|
|
||||||
},
|
|
||||||
"Kanagawa": {
|
|
||||||
"text": "545464",
|
|
||||||
"subtext": "545464",
|
|
||||||
"main": "F2EBBC",
|
|
||||||
"main-elevated": "E6DFB7",
|
|
||||||
"main-transition": "D5CEA3",
|
|
||||||
"highlight": "D5CfAf",
|
|
||||||
"highlight-elevated": "E0DAB4",
|
|
||||||
"sidebar": "E7DBA0",
|
|
||||||
"player": "F2EBBC",
|
|
||||||
"card": "D5CEA3",
|
|
||||||
"shadow": "E0DAB4",
|
|
||||||
"selected-row": "43436C",
|
|
||||||
"button": "43242B",
|
|
||||||
"button-active": "39395D",
|
|
||||||
"button-disabled": "D5CEA3",
|
|
||||||
"tab-active": "E7DBA0",
|
|
||||||
"notification": "43242B",
|
|
||||||
"notification-error": "E82423",
|
|
||||||
"misc": "000000",
|
|
||||||
"play-button": "43242B",
|
|
||||||
"play-button-active": "43242B",
|
|
||||||
"progress-fg": "43242B",
|
|
||||||
"progress-bg": "77713E",
|
|
||||||
"heart": "E82423",
|
|
||||||
"pagelink-active": "FFFFFF",
|
|
||||||
"radio-btn-active": "E7DBA0"
|
|
||||||
},
|
|
||||||
"Houjicha": {
|
|
||||||
"text": "B59F92",
|
|
||||||
"subtext": "997B6F",
|
|
||||||
"main": "473F3C",
|
|
||||||
"main-elevated": "534C49",
|
|
||||||
"main-transition": "5C514D",
|
|
||||||
"highlight": "574F4B",
|
|
||||||
"highlight-elevated": "59524F",
|
|
||||||
"sidebar": "514741",
|
|
||||||
"player": "473F3C",
|
|
||||||
"card": "78645C",
|
|
||||||
"shadow": "78645C",
|
|
||||||
"selected-row": "A49A96",
|
|
||||||
"button": "6E916F",
|
|
||||||
"button-active": "B3A49A",
|
|
||||||
"button-disabled": "67564D",
|
|
||||||
"tab-active": "6D5D54",
|
|
||||||
"notification": "86AF87",
|
|
||||||
"notification-error": "D25050",
|
|
||||||
"misc": "000000",
|
|
||||||
"play-button": "B59F92",
|
|
||||||
"play-button-active": "C7AfA1",
|
|
||||||
"progress-fg": "86AF87",
|
|
||||||
"progress-bg": "B59F92",
|
|
||||||
"heart": "86AF87",
|
|
||||||
"pagelink-active": "FFFFFF",
|
|
||||||
"radio-btn-active": "877063"
|
|
||||||
},
|
|
||||||
"Kitty": {
|
|
||||||
"text": "FFFFFF",
|
|
||||||
"subtext": "FFDDDC",
|
|
||||||
"main": "C94985",
|
|
||||||
"main-elevated": "CD548C",
|
|
||||||
"main-transition": "DE4B90",
|
|
||||||
"highlight": "DE4B90",
|
|
||||||
"highlight-elevated": "CF598D",
|
|
||||||
"sidebar": "E072A6",
|
|
||||||
"player": "941550",
|
|
||||||
"card": "DE5BA5",
|
|
||||||
"shadow": "2B0718",
|
|
||||||
"selected-row": "FFDDDC",
|
|
||||||
"button": "941550",
|
|
||||||
"button-active": "FFFFFF",
|
|
||||||
"button-disabled": "941550",
|
|
||||||
"tab-active": "E072A6",
|
|
||||||
"notification": "E072A6",
|
|
||||||
"notification-error": "994B6F",
|
|
||||||
"misc": "000000",
|
|
||||||
"play-button": "FFFFFF",
|
|
||||||
"play-button-active": "A13267",
|
|
||||||
"progress-fg": "DE4B90",
|
|
||||||
"progress-bg": "994B6F",
|
|
||||||
"heart": "A13267",
|
|
||||||
"pagelink-active": "DE4B90",
|
|
||||||
"radio-btn-active": "994B6F"
|
|
||||||
},
|
|
||||||
"Lunar": {
|
|
||||||
"text": "F3F3F3",
|
|
||||||
"subtext": "CECECE",
|
|
||||||
"main": "161616",
|
|
||||||
"main-elevated": "232323",
|
|
||||||
"main-transition": "101010",
|
|
||||||
"highlight": "2A2A2A",
|
|
||||||
"highlight-elevated": "292929",
|
|
||||||
"sidebar": "202020",
|
|
||||||
"player": "161616",
|
|
||||||
"card": "303030",
|
|
||||||
"shadow": "252525",
|
|
||||||
"selected-row": "CECECE",
|
|
||||||
"button": "3281EA",
|
|
||||||
"button-active": "0284E8",
|
|
||||||
"button-disabled": "303030",
|
|
||||||
"tab-active": "EBBCBA",
|
|
||||||
"notification": "3281EA",
|
|
||||||
"notification-error": "B10C0C",
|
|
||||||
"misc": "252525",
|
|
||||||
"play-button": "EBBCBA",
|
|
||||||
"play-button-active": "EBA9A7",
|
|
||||||
"progress-fg": "025CA1",
|
|
||||||
"progress-bg": "202020",
|
|
||||||
"heart": "EBBCBA",
|
|
||||||
"pagelink-active": "FFFFFF",
|
|
||||||
"radio-btn-active": "0284E8"
|
|
||||||
},
|
|
||||||
"Deep": {
|
|
||||||
"text": "4F9A87",
|
|
||||||
"subtext": "406560",
|
|
||||||
"main": "040614",
|
|
||||||
"main-elevated": "0A111D",
|
|
||||||
"main-transition": "0F111A",
|
|
||||||
"highlight": "0F1F28",
|
|
||||||
"highlight-elevated": "0C1520",
|
|
||||||
"sidebar": "0F111A",
|
|
||||||
"player": "040614",
|
|
||||||
"card": "0F1118",
|
|
||||||
"shadow": "0F1118",
|
|
||||||
"selected-row": "4F9A87",
|
|
||||||
"button": "106165",
|
|
||||||
"button-active": "4F9A87",
|
|
||||||
"button-disabled": "0C1C19",
|
|
||||||
"tab-active": "0A1527",
|
|
||||||
"notification": "051024",
|
|
||||||
"notification-error": "051024",
|
|
||||||
"misc": "406560",
|
|
||||||
"play-button": "106165",
|
|
||||||
"play-button-active": "4F9A87",
|
|
||||||
"progress-fg": "4F9A87",
|
|
||||||
"progress-bg": "106165",
|
|
||||||
"heart": "D25050",
|
|
||||||
"pagelink-active": "4F9A87",
|
|
||||||
"radio-btn-active": "106165"
|
|
||||||
},
|
|
||||||
"Velvet": {
|
|
||||||
"text": "AD434E",
|
|
||||||
"subtext": "762F37",
|
|
||||||
"main": "1E1E1E",
|
|
||||||
"main-elevated": "272021",
|
|
||||||
"main-transition": "161616",
|
|
||||||
"highlight": "322324",
|
|
||||||
"highlight-elevated": "2A2122",
|
|
||||||
"sidebar": "161616",
|
|
||||||
"player": "080808",
|
|
||||||
"card": "0F0F0F",
|
|
||||||
"shadow": "161616",
|
|
||||||
"selected-row": "973B45",
|
|
||||||
"button": "6B2B32",
|
|
||||||
"button-active": "552328",
|
|
||||||
"button-disabled": "262626",
|
|
||||||
"tab-active": "161616",
|
|
||||||
"notification": "6B2B32",
|
|
||||||
"notification-error": "60272D",
|
|
||||||
"misc": "000000",
|
|
||||||
"play-button": "552328",
|
|
||||||
"play-button-active": "6B2B32",
|
|
||||||
"progress-fg": "81333B",
|
|
||||||
"progress-bg": "A23F49",
|
|
||||||
"heart": "60272D",
|
|
||||||
"pagelink-active": "4A1F23",
|
|
||||||
"radio-btn-active": "6B2B32"
|
|
||||||
},
|
|
||||||
"Yami": {
|
|
||||||
"text": "D4D4D4",
|
|
||||||
"subtext": "D4D4D4",
|
|
||||||
"main": "0A0A10",
|
|
||||||
"main-elevated": "0D0D14",
|
|
||||||
"main-transition": "0A0A10",
|
|
||||||
"highlight": "191923",
|
|
||||||
"highlight-elevated": "0E0E15",
|
|
||||||
"sidebar": "0A0A10",
|
|
||||||
"player": "0A0A10",
|
|
||||||
"card": "191923",
|
|
||||||
"shadow": "AF8BF3",
|
|
||||||
"selected-row": "353447",
|
|
||||||
"button": "191923",
|
|
||||||
"button-active": "353447",
|
|
||||||
"button-disabled": "111117",
|
|
||||||
"tab-active": "191923",
|
|
||||||
"notification": "0A0A10",
|
|
||||||
"notification-error": "F7407B",
|
|
||||||
"misc": "0A0A10",
|
|
||||||
"play-button": "2B2A3D",
|
|
||||||
"play-button-active": "353447",
|
|
||||||
"progress-fg": "F7407B",
|
|
||||||
"progress-bg": "0A0A10",
|
|
||||||
"heart": "F7407B",
|
|
||||||
"pagelink-active": "F7407B",
|
|
||||||
"radio-btn-active": "191923"
|
|
||||||
},
|
|
||||||
"Hikari": {
|
|
||||||
"text": "CDBBF9",
|
|
||||||
"subtext": "8A7EA8",
|
|
||||||
"main": "050406",
|
|
||||||
"main-elevated": "0D0D14",
|
|
||||||
"main-transition": "0B0910",
|
|
||||||
"highlight": "1B1923",
|
|
||||||
"highlight-elevated": "0E0E15",
|
|
||||||
"sidebar": "0D0D14",
|
|
||||||
"player": "0D0D14",
|
|
||||||
"card": "1F132D",
|
|
||||||
"shadow": "1F132D",
|
|
||||||
"selected-row": "F1F1F1",
|
|
||||||
"button": "7F00E8",
|
|
||||||
"button-active": "7F00E8",
|
|
||||||
"button-disabled": "1C0033",
|
|
||||||
"tab-active": "1B1923",
|
|
||||||
"notification": "0A0A10",
|
|
||||||
"notification-error": "7F00E8",
|
|
||||||
"misc": "0A0A10",
|
|
||||||
"play-button": "CDBBF9",
|
|
||||||
"play-button-active": "8A7EA8",
|
|
||||||
"progress-fg": "7F00E8",
|
|
||||||
"progress-bg": "0D0D14",
|
|
||||||
"heart": "CDBBF9",
|
|
||||||
"pagelink-active": "1B1923",
|
|
||||||
"radio-btn-active": "191923"
|
|
||||||
},
|
|
||||||
"catppuccin-latte": {
|
|
||||||
"text": "4C4F69",
|
|
||||||
"subtext": "5C5F77",
|
|
||||||
"main": "E6E9EF",
|
|
||||||
"main-elevated": "DCE0E6",
|
|
||||||
"main-transition": "E6E9EF",
|
|
||||||
"highlight": "CED3DB",
|
|
||||||
"highlight-elevated": "D8DBE3",
|
|
||||||
"sidebar": "EFF1F5",
|
|
||||||
"player": "E6E9EF",
|
|
||||||
"card": "BCC0CC",
|
|
||||||
"shadow": "E6E9EF",
|
|
||||||
"selected-row": "5C5F77",
|
|
||||||
"button": "1E66F5",
|
|
||||||
"button-active": "209FB5",
|
|
||||||
"button-disabled": "BCC0CC",
|
|
||||||
"tab-active": "CCD0DA",
|
|
||||||
"notification": "1E66F5",
|
|
||||||
"notification-error": "D20F39",
|
|
||||||
"misc": "ACB0BE",
|
|
||||||
"play-button": "EA76CB",
|
|
||||||
"play-button-active": "DD7878",
|
|
||||||
"progress-fg": "04A5E5",
|
|
||||||
"progress-bg": "BCC0CC",
|
|
||||||
"heart": "FE640B",
|
|
||||||
"pagelink-active": "FFFFFF",
|
|
||||||
"radio-btn-active": "209FB5"
|
|
||||||
},
|
|
||||||
"catppuccin-frappe": {
|
|
||||||
"text": "C6D0F5",
|
|
||||||
"subtext": "B5BFE2",
|
|
||||||
"main": "292C3C",
|
|
||||||
"main-elevated": "333648",
|
|
||||||
"main-transition": "292C3C",
|
|
||||||
"highlight": "3B4058",
|
|
||||||
"highlight-elevated": "373B4D",
|
|
||||||
"sidebar": "303446",
|
|
||||||
"player": "292C3C",
|
|
||||||
"card": "51576D",
|
|
||||||
"shadow": "626880",
|
|
||||||
"selected-row": "B5BFE2",
|
|
||||||
"button": "8CAAEE",
|
|
||||||
"button-active": "85C1DC",
|
|
||||||
"button-disabled": "51576D",
|
|
||||||
"tab-active": "414559",
|
|
||||||
"notification": "8CAAEE",
|
|
||||||
"notification-error": "E78284",
|
|
||||||
"misc": "626880",
|
|
||||||
"play-button": "F4B8E4",
|
|
||||||
"play-button-active": "EECEBE",
|
|
||||||
"progress-fg": "99D1DB",
|
|
||||||
"progress-bg": "51576D",
|
|
||||||
"heart": "EF9F76",
|
|
||||||
"pagelink-active": "FFFFFF",
|
|
||||||
"radio-btn-active": "85C1DC"
|
|
||||||
},
|
|
||||||
"catppuccin-macchiato": {
|
|
||||||
"text": "CAD3F5",
|
|
||||||
"subtext": "B8C0E0",
|
|
||||||
"main": "181926",
|
|
||||||
"main-elevated": "232533",
|
|
||||||
"main-transition": "181926",
|
|
||||||
"highlight": "333645",
|
|
||||||
"highlight-elevated": "292A39",
|
|
||||||
"sidebar": "24273A",
|
|
||||||
"player": "181926",
|
|
||||||
"card": "494D64",
|
|
||||||
"shadow": "5B6078",
|
|
||||||
"selected-row": "B8C0E0",
|
|
||||||
"button": "8AADF4",
|
|
||||||
"button-active": "7DC4E4",
|
|
||||||
"button-disabled": "494D64",
|
|
||||||
"tab-active": "363A4F",
|
|
||||||
"notification": "8AADF4",
|
|
||||||
"notification-error": "ED8796",
|
|
||||||
"misc": "5B6078",
|
|
||||||
"play-button": "F5BDE6",
|
|
||||||
"play-button-active": "F0C6C6",
|
|
||||||
"progress-fg": "91D7E3",
|
|
||||||
"progress-bg": "494D64",
|
|
||||||
"heart": "F5A97F",
|
|
||||||
"pagelink-active": "FFFFFF",
|
|
||||||
"radio-btn-active": "7DC4E4"
|
|
||||||
},
|
|
||||||
"catppuccin-mocha": {
|
|
||||||
"text": "CDD6F4",
|
|
||||||
"subtext": "BAC2DE",
|
|
||||||
"main": "181825",
|
|
||||||
"main-elevated": "232432",
|
|
||||||
"main-transition": "181825",
|
|
||||||
"highlight": "333645",
|
|
||||||
"highlight-elevated": "292A38",
|
|
||||||
"sidebar": "1E1E2E",
|
|
||||||
"player": "181825",
|
|
||||||
"card": "45475A",
|
|
||||||
"shadow": "585B70",
|
|
||||||
"selected-row": "BAC2DE",
|
|
||||||
"button": "89B4FA",
|
|
||||||
"button-active": "74C7EC",
|
|
||||||
"button-disabled": "45475A",
|
|
||||||
"tab-active": "313244",
|
|
||||||
"notification": "89B4FA",
|
|
||||||
"notification-error": "F38BA8",
|
|
||||||
"misc": "585B70",
|
|
||||||
"play-button": "F5C2E7",
|
|
||||||
"play-button-active": "F2CDCD",
|
|
||||||
"progress-fg": "89DCEB",
|
|
||||||
"progress-bg": "45475A",
|
|
||||||
"heart": "FAB387",
|
|
||||||
"pagelink-active": "FFFFFF",
|
|
||||||
"radio-btn-active": "74C7EC"
|
|
||||||
},
|
|
||||||
"rose-pine": {
|
|
||||||
"text": "E0DEF4",
|
|
||||||
"subtext": "908CAA",
|
|
||||||
"main": "1F1D2E",
|
|
||||||
"main-elevated": "2D2B3C",
|
|
||||||
"main-transition": "26233A",
|
|
||||||
"highlight": "403D52",
|
|
||||||
"highlight-elevated": "333142",
|
|
||||||
"sidebar": "191724",
|
|
||||||
"player": "1F1D2E",
|
|
||||||
"card": "403D52",
|
|
||||||
"shadow": "524F67",
|
|
||||||
"selected-row": "E0DEF4",
|
|
||||||
"button": "EBBCBA",
|
|
||||||
"button-active": "EBBCBA",
|
|
||||||
"button-disabled": "6E6A86",
|
|
||||||
"tab-active": "524F67",
|
|
||||||
"notification": "9CCFD8",
|
|
||||||
"notification-error": "EB6F92",
|
|
||||||
"misc": "FFFFFF",
|
|
||||||
"play-button": "EBBCBA",
|
|
||||||
"play-button-active": "EBBCBA",
|
|
||||||
"progress-fg": "EBBCBA",
|
|
||||||
"progress-bg": "403D52",
|
|
||||||
"heart": "EB6F92",
|
|
||||||
"pagelink-active": "EBBCBA",
|
|
||||||
"radio-btn-active": "9CCFD8"
|
|
||||||
},
|
|
||||||
"rose-pine-moon": {
|
|
||||||
"text": "E0DEF4",
|
|
||||||
"subtext": "908CAA",
|
|
||||||
"main": "2A273F",
|
|
||||||
"main-elevated": "37344C",
|
|
||||||
"main-transition": "393552",
|
|
||||||
"highlight": "44415A",
|
|
||||||
"highlight-elevated": "3D3A52",
|
|
||||||
"sidebar": "232136",
|
|
||||||
"player": "2A273F",
|
|
||||||
"card": "44415A",
|
|
||||||
"shadow": "56526E",
|
|
||||||
"selected-row": "E0DEF4",
|
|
||||||
"button": "EA9A97",
|
|
||||||
"button-active": "EA9A97",
|
|
||||||
"button-disabled": "6E6A86",
|
|
||||||
"tab-active": "56526E",
|
|
||||||
"notification": "9CCFD8",
|
|
||||||
"notification-error": "EB6F92",
|
|
||||||
"misc": "FFFFFF",
|
|
||||||
"play-button": "EA9A97",
|
|
||||||
"play-button-active": "EA9A97",
|
|
||||||
"progress-fg": "EA9A97",
|
|
||||||
"progress-bg": "44415A",
|
|
||||||
"heart": "EB6F92",
|
|
||||||
"pagelink-active": "EA9A97",
|
|
||||||
"radio-btn-active": "9CCFD8"
|
|
||||||
},
|
|
||||||
"rose-pine-dawn": {
|
|
||||||
"text": "575279",
|
|
||||||
"subtext": "797593",
|
|
||||||
"main": "FFFAF3",
|
|
||||||
"main-elevated": "F3EEEB",
|
|
||||||
"main-transition": "F2E9E1",
|
|
||||||
"highlight": "DFDAD9",
|
|
||||||
"highlight-elevated": "EEE9E6",
|
|
||||||
"sidebar": "FAF4ED",
|
|
||||||
"player": "FFFAF3",
|
|
||||||
"card": "DFDAD9",
|
|
||||||
"shadow": "FAF4ED",
|
|
||||||
"selected-row": "575279",
|
|
||||||
"button": "D7827E",
|
|
||||||
"button-active": "D7827E",
|
|
||||||
"button-disabled": "9893A5",
|
|
||||||
"tab-active": "CECACD",
|
|
||||||
"notification": "56949F",
|
|
||||||
"notification-error": "B4637A",
|
|
||||||
"misc": "FFFFFF",
|
|
||||||
"play-button": "D7827E",
|
|
||||||
"play-button-active": "D7827E",
|
|
||||||
"progress-fg": "D7827E",
|
|
||||||
"progress-bg": "DFDAD9",
|
|
||||||
"heart": "B4637A",
|
|
||||||
"pagelink-active": "D7827E",
|
|
||||||
"radio-btn-active": "907AA9"
|
|
||||||
},
|
|
||||||
"Mono": {
|
|
||||||
"text": "FFFFFF",
|
|
||||||
"subtext": "B9BBBE",
|
|
||||||
"main": "171717",
|
|
||||||
"main-elevated": "262626",
|
|
||||||
"main-transition": "FFFFFF",
|
|
||||||
"highlight": "3B3B3B",
|
|
||||||
"highlight-elevated": "2E2E2E",
|
|
||||||
"sidebar": "101010",
|
|
||||||
"player": "171717",
|
|
||||||
"card": "343434",
|
|
||||||
"shadow": "595858",
|
|
||||||
"selected-row": "F1F1F1",
|
|
||||||
"button": "FFFFFF",
|
|
||||||
"button-active": "C5C5C5",
|
|
||||||
"button-disabled": "4A4949",
|
|
||||||
"tab-active": "303030",
|
|
||||||
"notification": "101010",
|
|
||||||
"notification-error": "D25050",
|
|
||||||
"misc": "000000",
|
|
||||||
"play-button": "FFFFFF",
|
|
||||||
"play-button-active": "FFFFFF",
|
|
||||||
"progress-fg": "FFFFFF",
|
|
||||||
"progress-bg": "343434",
|
|
||||||
"heart": "FFFFFF",
|
|
||||||
"pagelink-active": "787878",
|
|
||||||
"radio-btn-active": "737373"
|
|
||||||
},
|
|
||||||
"Sunset": {
|
|
||||||
"text": "FFCE3F",
|
|
||||||
"subtext": "FEF3BB",
|
|
||||||
"main": "171717",
|
|
||||||
"main-elevated": "272622",
|
|
||||||
"main-transition": "000000",
|
|
||||||
"highlight": "3D3C32",
|
|
||||||
"highlight-elevated": "2F2E28",
|
|
||||||
"sidebar": "101010",
|
|
||||||
"player": "171717",
|
|
||||||
"card": "CC9756",
|
|
||||||
"shadow": "E3B47B",
|
|
||||||
"selected-row": "FEF3BB",
|
|
||||||
"button": "FFCE3F",
|
|
||||||
"button-active": "BF9B30",
|
|
||||||
"button-disabled": "4A4949",
|
|
||||||
"tab-active": "303030",
|
|
||||||
"notification": "FFFFFF",
|
|
||||||
"notification-error": "D25050",
|
|
||||||
"misc": "000000",
|
|
||||||
"play-button": "FFCE3F",
|
|
||||||
"play-button-active": "FC9E3A",
|
|
||||||
"progress-fg": "FF8300",
|
|
||||||
"progress-bg": "343434",
|
|
||||||
"heart": "FF8300",
|
|
||||||
"pagelink-active": "FEF3BB",
|
|
||||||
"radio-btn-active": "FEF3BB"
|
|
||||||
},
|
|
||||||
"Neon": {
|
|
||||||
"text": "588BAE",
|
|
||||||
"subtext": "EAFFFF",
|
|
||||||
"main": "171717",
|
|
||||||
"main-elevated": "262626",
|
|
||||||
"main-transition": "000000",
|
|
||||||
"highlight": "3B3B3B",
|
|
||||||
"highlight-elevated": "2E2E2E",
|
|
||||||
"sidebar": "101010",
|
|
||||||
"player": "171717",
|
|
||||||
"card": "7FA1B5",
|
|
||||||
"shadow": "A9C9DB",
|
|
||||||
"selected-row": "F1F1F1",
|
|
||||||
"button": "588BAE",
|
|
||||||
"button-active": "3B5D75",
|
|
||||||
"button-disabled": "4A4949",
|
|
||||||
"tab-active": "303030",
|
|
||||||
"notification": "FFFFFF",
|
|
||||||
"notification-error": "D25050",
|
|
||||||
"misc": "000000",
|
|
||||||
"play-button": "588BAE",
|
|
||||||
"play-button-active": "5085AB",
|
|
||||||
"progress-fg": "00AFDB",
|
|
||||||
"progress-bg": "343434",
|
|
||||||
"heart": "00AFDB",
|
|
||||||
"pagelink-active": "BBE7FE",
|
|
||||||
"radio-btn-active": "EAFFFF"
|
|
||||||
},
|
|
||||||
"Forest": {
|
|
||||||
"text": "B2C5B3",
|
|
||||||
"subtext": "D5DDDE",
|
|
||||||
"main": "171717",
|
|
||||||
"main-elevated": "262626",
|
|
||||||
"main-transition": "000000",
|
|
||||||
"highlight": "3B3B3B",
|
|
||||||
"highlight-elevated": "2E2E2E",
|
|
||||||
"sidebar": "101010",
|
|
||||||
"player": "171717",
|
|
||||||
"card": "5C6E59",
|
|
||||||
"shadow": "3C5148",
|
|
||||||
"selected-row": "F1F1F1",
|
|
||||||
"button": "B2C5B3",
|
|
||||||
"button-active": "F1F1F1",
|
|
||||||
"button-disabled": "4A4949",
|
|
||||||
"tab-active": "303030",
|
|
||||||
"notification": "FFFFFF",
|
|
||||||
"notification-error": "D25050",
|
|
||||||
"misc": "000000",
|
|
||||||
"play-button": "3C5148",
|
|
||||||
"play-button-active": "43705D",
|
|
||||||
"progress-fg": "3C5148",
|
|
||||||
"progress-bg": "343434",
|
|
||||||
"heart": "3C5148",
|
|
||||||
"pagelink-active": "3C5148",
|
|
||||||
"radio-btn-active": "737373"
|
|
||||||
},
|
|
||||||
"Sakura": {
|
|
||||||
"text": "FCB4CA",
|
|
||||||
"subtext": "FFDCDC",
|
|
||||||
"main": "171717",
|
|
||||||
"main-elevated": "262626",
|
|
||||||
"main-transition": "000000",
|
|
||||||
"highlight": "3D3838",
|
|
||||||
"highlight-elevated": "2E2E2E",
|
|
||||||
"sidebar": "101010",
|
|
||||||
"player": "171717",
|
|
||||||
"card": "D68BA2",
|
|
||||||
"shadow": "FCB4CA",
|
|
||||||
"selected-row": "FFDCDC",
|
|
||||||
"button": "FCB4CA",
|
|
||||||
"button-active": "D48AA0",
|
|
||||||
"button-disabled": "4A4949",
|
|
||||||
"tab-active": "303030",
|
|
||||||
"notification": "FFFFFF",
|
|
||||||
"notification-error": "D25050",
|
|
||||||
"misc": "000000",
|
|
||||||
"play-button": "F42C38",
|
|
||||||
"play-button-active": "BA182B",
|
|
||||||
"progress-fg": "CFEEFA",
|
|
||||||
"progress-bg": "343434",
|
|
||||||
"heart": "F25477",
|
|
||||||
"pagelink-active": "F5BCDB",
|
|
||||||
"radio-btn-active": "FFDCDC"
|
|
||||||
},
|
|
||||||
"Vaporwave": {
|
|
||||||
"text": "01CDFE",
|
|
||||||
"subtext": "EAFFFF",
|
|
||||||
"main": "171717",
|
|
||||||
"main-elevated": "262626",
|
|
||||||
"main-transition": "000000",
|
|
||||||
"highlight": "3B3B3B",
|
|
||||||
"highlight-elevated": "2E2E2E",
|
|
||||||
"sidebar": "101010",
|
|
||||||
"player": "171717",
|
|
||||||
"card": "007F9E",
|
|
||||||
"shadow": "2EC2E6",
|
|
||||||
"selected-row": "F1F1F1",
|
|
||||||
"button": "01CDFE",
|
|
||||||
"button-active": "118BA8",
|
|
||||||
"button-disabled": "4A4949",
|
|
||||||
"tab-active": "303030",
|
|
||||||
"notification": "FFFFFF",
|
|
||||||
"notification-error": "D25050",
|
|
||||||
"misc": "000000",
|
|
||||||
"play-button": "FFD300",
|
|
||||||
"play-button-active": "E3C01B",
|
|
||||||
"progress-fg": "F706CF",
|
|
||||||
"progress-bg": "343434",
|
|
||||||
"heart": "F706CF",
|
|
||||||
"pagelink-active": "C0D6FA",
|
|
||||||
"radio-btn-active": "EAFFFF"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Font": "JetBrainsMono Nerd Font",
|
|
||||||
"Home-Header-Snippet-Collapsed": true,
|
|
||||||
"Custom-Font-Collapsed": false,
|
|
||||||
"Gradient-Noise": "",
|
|
||||||
"Gradient-Blend": "",
|
|
||||||
"Gradient-Speed": "",
|
|
||||||
"Gradient-Size": "",
|
|
||||||
"Gradient-Radius": "",
|
|
||||||
"Apple-Music-Gradient-Snippet-Collapsed": true,
|
|
||||||
"Custom-Image-URL": "images/kurz.png",
|
|
||||||
"Custom-Playbar-Snippet": true,
|
|
||||||
"AM-Gradient-Include-Existing-Snippet": false,
|
|
||||||
"Banner-Enabled": true,
|
|
||||||
"Remove-Tracklist-Index": true,
|
|
||||||
"Remove-Column-Bar-Snippet": true,
|
|
||||||
"Tracklist-Gradient-Opacity": "",
|
|
||||||
"Collapse-Topbar-Snippet": true,
|
|
||||||
"Topbar-Inside-Titlebar-Snippet-Collapsed": true,
|
|
||||||
"Smooth-Progress-Bar-Snippet": true,
|
|
||||||
"Remove-Connect-Bar-Snippet": true,
|
|
||||||
"Remove-Tracklist-Gradient-Noise": false,
|
|
||||||
"App-Titlebar-Height": "",
|
|
||||||
"Replace-Existing-Banners": false,
|
|
||||||
"Prefer-Existing-Image": false,
|
|
||||||
"Header-Background": false,
|
|
||||||
"Tracklist-Header-Background": false,
|
|
||||||
"Tracklist-Header-Background-Opacity": "",
|
|
||||||
"Tracklist-Header-Background-Collapsed": false,
|
|
||||||
"Custom-Image-Collapsed": false,
|
|
||||||
"Cover-Art-Width": "",
|
|
||||||
"Cover-Art-Height": "",
|
|
||||||
"Cover-Art-Radius": "",
|
|
||||||
"Cover-Art-Left": "",
|
|
||||||
"Cover-Art-Bottom": "",
|
|
||||||
"Custom-Cover-Art-Dimensions-Collapsed": false
|
|
||||||
}
|
|
2
.config/spicetify/Themes/marketplace/color.ini
Normal file
2
.config/spicetify/Themes/marketplace/color.ini
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[Marketplace]
|
||||||
|
|
|
@ -1,34 +1,32 @@
|
||||||
|
[Setting]
|
||||||
|
overwrite_assets = 0
|
||||||
|
spotify_launch_flags =
|
||||||
|
spotify_path = /opt/spotify/
|
||||||
|
prefs_path = /home/matt/.config/spotify/prefs
|
||||||
|
color_scheme =
|
||||||
|
inject_theme_js = 1
|
||||||
|
replace_colors = 1
|
||||||
|
current_theme = marketplace
|
||||||
|
inject_css = 1
|
||||||
|
check_spicetify_update = 1
|
||||||
|
always_enable_devtools = 0
|
||||||
|
|
||||||
[Preprocesses]
|
[Preprocesses]
|
||||||
|
expose_apis = 1
|
||||||
|
disable_sentry = 1
|
||||||
disable_ui_logging = 1
|
disable_ui_logging = 1
|
||||||
remove_rtl_rule = 1
|
remove_rtl_rule = 1
|
||||||
expose_apis = 1
|
|
||||||
disable_upgrade_check = 1
|
|
||||||
disable_sentry = 1
|
|
||||||
|
|
||||||
[AdditionalOptions]
|
[AdditionalOptions]
|
||||||
sidebar_config = 1
|
sidebar_config = 1
|
||||||
home_config = 1
|
home_config = 1
|
||||||
experimental_features = 1
|
experimental_features = 1
|
||||||
extensions = adblock.js|hidePodcasts.js|historyShortcut.js|copyPlaylist.js|wikify.js|fullScreen.js|volumePercentage.js|bookmark.js|loopyLoop.js|keyboardShortcutMy.js|genre.js|playlistIcons.js
|
extensions =
|
||||||
custom_apps = lyrics-plus|marketplace|stats
|
custom_apps = marketplace|stats|library|better-local-files
|
||||||
|
|
||||||
[Patch]
|
[Patch]
|
||||||
|
|
||||||
[Setting]
|
|
||||||
spotify_path = /opt/spotify
|
|
||||||
color_scheme = Comfy
|
|
||||||
overwrite_assets = 1
|
|
||||||
spotify_launch_flags =
|
|
||||||
check_spicetify_upgrade = 0
|
|
||||||
prefs_path = /home/matt/.config/spotify/prefs
|
|
||||||
current_theme = Comfy
|
|
||||||
inject_css = 1
|
|
||||||
replace_colors = 1
|
|
||||||
inject_theme_js = 1
|
|
||||||
check_spicetify_update = 1
|
|
||||||
always_enable_devtools = 0
|
|
||||||
|
|
||||||
; DO NOT CHANGE!
|
; DO NOT CHANGE!
|
||||||
[Backup]
|
[Backup]
|
||||||
version = 1.1.72.439.gc253025e
|
version = 1.2.37.701.ge66eb7bc
|
||||||
with = 2.36.11
|
with = 2.36.13
|
||||||
|
|
Loading…
Add table
Reference in a new issue