♻️ 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 */
|
||||
.navBar-module__topBarHeaderItem___v29bR_stats {
|
||||
/* ../../../AppData/Local/Temp/tmp-17120-n3iWRzissBjg/18f3cbbb4bc3/navBar.module.css */
|
||||
.navBar-module__topBarHeaderItem___piw4C_stats {
|
||||
-webkit-app-region: no-drag;
|
||||
display: inline-block;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.navBar-module__topBarHeaderItemLink___VeyBY_stats {
|
||||
.navBar-module__topBarHeaderItemLink___xA4uv_stats {
|
||||
margin: 0 8px 0 0;
|
||||
}
|
||||
.navBar-module__topBarActive___-qYPu_stats {
|
||||
.navBar-module__topBarActive___XhWpm_stats {
|
||||
background-color: var(--spice-tab-active);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.navBar-module__topBarHeaderItemLink___VeyBY_stats {
|
||||
.navBar-module__topBarHeaderItemLink___xA4uv_stats {
|
||||
border-radius: 4px;
|
||||
color: var(--spice-text);
|
||||
display: inline-block;
|
||||
|
@ -21,12 +21,12 @@
|
|||
text-decoration: none !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
.navBar-module__topBarNav___1OtdR_stats {
|
||||
.navBar-module__topBarNav___qWGeZ_stats {
|
||||
-webkit-app-region: drag;
|
||||
pointer-events: none;
|
||||
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);
|
||||
border: 0;
|
||||
max-width: 150px;
|
||||
|
@ -38,74 +38,45 @@
|
|||
-moz-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;
|
||||
margin-left: 8px;
|
||||
}
|
||||
div.navBar-module__topBarHeaderItemLink___VeyBY_stats {
|
||||
div.navBar-module__topBarHeaderItemLink___xA4uv_stats {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* ../../AppData/Local/Temp/tmp-6912-LESOepgMxajG/18ca59b275b0/app.css */
|
||||
.stats-grid {
|
||||
--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 {
|
||||
/* ../../../AppData/Local/Temp/tmp-17120-n3iWRzissBjg/18f3cbbb33e0/app.css */
|
||||
#stats-app .stats-gridInline {
|
||||
--grid-gap: 24px;
|
||||
grid-template-columns: repeat(10, 180px) !important;
|
||||
overflow-x: hidden;
|
||||
scroll-behavior: smooth;
|
||||
margin-top: 5px;
|
||||
}
|
||||
[data-scroll=both] {
|
||||
#stats-app [data-scroll=both] {
|
||||
-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);
|
||||
}
|
||||
[data-scroll=end] {
|
||||
#stats-app [data-scroll=end] {
|
||||
-webkit-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);
|
||||
mask-image: linear-gradient(to right, black 90%, transparent);
|
||||
}
|
||||
.stats-loadingWrapper {
|
||||
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 {
|
||||
#stats-app .stats-libraryOverview {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
align-items: center;
|
||||
}
|
||||
.stats-scrollButton {
|
||||
#stats-app .stats-trackPageTitle {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
align-items: center;
|
||||
}
|
||||
#stats-app .stats-scrollButton {
|
||||
width: 40px;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
|
@ -114,69 +85,20 @@ div.navBar-module__topBarHeaderItemLink___VeyBY_stats {
|
|||
background-color: var(--spice-player);
|
||||
color: var(--spice-subtext);
|
||||
}
|
||||
.stats-scrollButton:hover {
|
||||
#stats-app .stats-scrollButton:hover {
|
||||
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);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 16px 0;
|
||||
}
|
||||
.stats-tracklistHeader > div {
|
||||
#stats-app .stats-tracklistHeader > div {
|
||||
display: flex;
|
||||
-webkit-app-region: no-drag;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
}
|
||||
.stats-genreCard {
|
||||
#stats-app .stats-make-playlist-button {
|
||||
margin-inline-start: 12px;
|
||||
}
|
||||
#stats-app .stats-genreCard {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
@ -185,44 +107,36 @@ div.navBar-module__topBarHeaderItemLink___VeyBY_stats {
|
|||
background: var(--spice-player);
|
||||
position: relative;
|
||||
}
|
||||
.stats-genreRow {
|
||||
#stats-app .stats-genreRow {
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
.stats-genreRowFill {
|
||||
#stats-app .stats-genreRowFill {
|
||||
background: var(--spice-button);
|
||||
height: 100%;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.stats-genreText {
|
||||
#stats-app .stats-genreText {
|
||||
color: var(--spice-player);
|
||||
font-size: 0.875rem;
|
||||
margin-left: 7px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.stats-genreValue {
|
||||
#stats-app .stats-genreValue {
|
||||
color: var(--spice-text);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.stats-cardValue {
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
color: var(--spice-text);
|
||||
#stats-app .stats-genreCard + .stats-gridInlineSection {
|
||||
margin-top: 3px;
|
||||
}
|
||||
.stats-cardText {
|
||||
color: var(--spice-text);
|
||||
}
|
||||
.new-update {
|
||||
background-color: var(--spice-player);
|
||||
color: var(--spice-text);
|
||||
border-radius: 8px;
|
||||
padding: 2px 12px;
|
||||
margin: 0 24px;
|
||||
border: 0px;
|
||||
#stats-app .main-trackList-rowHeartButton,
|
||||
#stats-app .main-trackList-rowMoreButton {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
.GenericModal[aria-label="Playlist Stats"] .main-embedWidgetGenerator-container {
|
||||
width: 80vw;
|
||||
|
@ -232,25 +146,14 @@ div.navBar-module__topBarHeaderItemLink___VeyBY_stats {
|
|||
.GenericModal[aria-label="Playlist Stats"] .main-shelf-title {
|
||||
color: var(--spice-text);
|
||||
}
|
||||
.status-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
/* ../../AppData/Local/Temp/tmp-6912-LESOepgMxajG/18ca59b28921/settings_modal.css */
|
||||
#stats-config-container {
|
||||
/* ../../../AppData/Local/Temp/tmp-17120-n3iWRzissBjg/18f3cbbb48b1/config_modal.css */
|
||||
.config-container {
|
||||
gap: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
#stats-config-container .toggle-wrapper {
|
||||
display: inline-flex;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
#stats-config-container .section-header {
|
||||
.config-container .section-header {
|
||||
box-sizing: border-box;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
margin-block: 0px;
|
||||
|
@ -258,7 +161,7 @@ div.navBar-module__topBarHeaderItemLink___VeyBY_stats {
|
|||
font-weight: 700;
|
||||
color: var(--spice-text);
|
||||
}
|
||||
#stats-config-container .col.description {
|
||||
.config-container .col.description {
|
||||
box-sizing: border-box;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
margin-block: 0px;
|
||||
|
@ -266,60 +169,11 @@ div.navBar-module__topBarHeaderItemLink___VeyBY_stats {
|
|||
font-weight: 400;
|
||||
color: var(--spice-subtext);
|
||||
}
|
||||
#stats-config-container .disabled {
|
||||
.config-container .disabled {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
#stats-config-container .toggle-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 {
|
||||
.config-container .text-input {
|
||||
background: rgba(var(--spice-rgb-selected-row), 0.1);
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
|
@ -330,12 +184,12 @@ div.navBar-module__topBarHeaderItemLink___VeyBY_stats {
|
|||
padding: 0 12px;
|
||||
width: 100%;
|
||||
}
|
||||
#stats-config-container .text-input:focus {
|
||||
.config-container .text-input:focus {
|
||||
background-color: var(--spice-tab-active);
|
||||
border: 1px solid var(--spice-button-disabled);
|
||||
outline: none;
|
||||
}
|
||||
#stats-config-container .dropdown-input {
|
||||
.config-container .dropdown-input {
|
||||
background-color: var(--spice-tab-active);
|
||||
border: 0;
|
||||
border-radius: 4px;
|
||||
|
@ -348,7 +202,7 @@ div.navBar-module__topBarHeaderItemLink___VeyBY_stats {
|
|||
padding: 0 32px 0 12px;
|
||||
width: 100%;
|
||||
}
|
||||
#stats-config-container .tooltip-icon {
|
||||
.config-container .tooltip-icon {
|
||||
float: right;
|
||||
margin-left: 10px;
|
||||
display: flex;
|
||||
|
@ -356,13 +210,67 @@ div.navBar-module__topBarHeaderItemLink___VeyBY_stats {
|
|||
height: 22px;
|
||||
fill: var(--spice-subtext);
|
||||
}
|
||||
#stats-config-container .tooltip-icon:hover {
|
||||
.config-container .tooltip-icon:hover {
|
||||
fill: var(--spice-text);
|
||||
}
|
||||
#stats-config-container .tooltip {
|
||||
.config-container .tooltip {
|
||||
text-align: center;
|
||||
}
|
||||
#stats-config-container .setting-row {
|
||||
.config-container .setting-row {
|
||||
display: flex;
|
||||
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]
|
||||
disable_ui_logging = 1
|
||||
remove_rtl_rule = 1
|
||||
expose_apis = 1
|
||||
disable_upgrade_check = 1
|
||||
disable_sentry = 1
|
||||
expose_apis = 1
|
||||
disable_sentry = 1
|
||||
disable_ui_logging = 1
|
||||
remove_rtl_rule = 1
|
||||
|
||||
[AdditionalOptions]
|
||||
sidebar_config = 1
|
||||
home_config = 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
|
||||
custom_apps = lyrics-plus|marketplace|stats
|
||||
extensions =
|
||||
custom_apps = marketplace|stats|library|better-local-files
|
||||
|
||||
[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!
|
||||
[Backup]
|
||||
version = 1.1.72.439.gc253025e
|
||||
with = 2.36.11
|
||||
version = 1.2.37.701.ge66eb7bc
|
||||
with = 2.36.13
|
||||
|
|
Loading…
Add table
Reference in a new issue