mirror of
https://github.com/kristoferssolo/solorice.git
synced 2026-03-18 08:09:40 +00:00
Use dotter
This commit is contained in:
99
config/spicetify/CustomApps/eternal-jukebox/README.md
Normal file
99
config/spicetify/CustomApps/eternal-jukebox/README.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# The Eternal Jukebox
|
||||
|
||||
For when your favorite song just isn't long enough.
|
||||
|
||||

|
||||
|
||||
A rewrite of the [Infinite / Eternal Jukebox](https://eternalbox.dev/jukebox_index.html) for Spicetify.
|
||||
It finds pathways through similar segments of the song and plays a never-ending and ever changing version of the song.
|
||||
|
||||
> **Warning**
|
||||
> The custom app is still in **beta**.
|
||||
> See [known issues](#known-issues) and [upcoming features](#upcoming-features).
|
||||
|
||||
|
||||
## Auto Installation (Linux)
|
||||
```
|
||||
sh <(curl -s https://raw.githubusercontent.com/Pithaya/spicetify-apps/main/custom-apps/eternal-jukebox/src/install.sh)
|
||||
|
||||
```
|
||||
|
||||
## Manual Installation
|
||||
|
||||
1. Run `spicetify config-dir` to open the spicetify folder.
|
||||
2. Go to the `CustomApps` folder.
|
||||
3. Create a `eternal-jukebox` folder.
|
||||
4. Download the custom app files as a zip from [here](https://github.com/Pithaya/spicetify-apps-dist/archive/refs/heads/dist/eternal-jukebox.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 eternal-jukebox
|
||||
spicetify apply
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
A new "infinity" button allows you to enable and disable the jukebox. As long as the jukebox is enabled, the current song will play endlessly.
|
||||
|
||||

|
||||
|
||||
Changing the current song will automatically play it through the jukebox.
|
||||
|
||||

|
||||
|
||||
The custom app allows you to see a visualization of the jukebox's progress through the song.
|
||||
|
||||

|
||||
|
||||
The circle is made out of the different beats of the song. Branches, or edges are the path linking similar beats together.
|
||||
|
||||
Holding the `SHIFT` key allows you to keep repeating a part of the song by "jumping" through edges linking the same beats.
|
||||
|
||||
Clicking on a beat will seek to that part of the song.
|
||||
|
||||
Below the graph you will find some stats about the current song:
|
||||
|
||||
- **Total beats**: How many beats were played.
|
||||
- **Current branch change**: The current percentage of chance to follow an edge when playing a beat.
|
||||
- **Listen time**: How long you've been listening to the song.
|
||||
|
||||
### Settings
|
||||
|
||||
The settings button on the top right allows you to tune the jukebox.
|
||||
|
||||

|
||||
|
||||
- **Branch similarity threshold**: The maximum allowed "distance" between two branches. The higher it is, the more branches will be generated.
|
||||
- **Branch probability range**: The minimum and maximum percentage of chance to use a branch each beat. The chance will start at the minimum value, and will increase by the **Branch probability ramp-up speed** value for every beat where it is not branching, until it reaches the maximum value.
|
||||
- **Branch probability ramp-up speed**: How fast the **Branch probability chance** value should increase.
|
||||
- **Loop extension optimization**: If checked, will try to add the longest backward branch it can at the last branching beat.
|
||||
- **Allow only reverse branches**: If checked, will only add branches going back in the song.
|
||||
- **Allow only long branches**: If checked, will only add long branches. A branch is considered long if it covers at least a fifth of the song's length.
|
||||
- **Remove sequential branches**: If checked, will remove consecutive branches of the same length.
|
||||
|
||||
The reset button can be used to reset the settings to the default values.
|
||||
|
||||
## Known issues
|
||||
|
||||
- Audio lag when jumping between parts of the song
|
||||
- Jukebox "freezing" and getting out of sync
|
||||
- Songs getting stuck in short loops due to issues with the graph generation
|
||||
|
||||
## Upcoming features
|
||||
|
||||
- More graph interactivity
|
||||
|
||||
## Uninstall
|
||||
|
||||
1. Run `spicetify config-dir` to open the spicetify folder
|
||||
2. Go to the `CustomApps` folder
|
||||
3. Delete the `eternal-jukebox` folder
|
||||
|
||||
Then, run the following commands:
|
||||
|
||||
```sh
|
||||
spicetify config custom_apps eternal-jukebox-
|
||||
spicetify apply
|
||||
```
|
||||
1
config/spicetify/CustomApps/eternal-jukebox/extension.js
Normal file
1
config/spicetify/CustomApps/eternal-jukebox/extension.js
Normal file
File diff suppressed because one or more lines are too long
12
config/spicetify/CustomApps/eternal-jukebox/index.js
Normal file
12
config/spicetify/CustomApps/eternal-jukebox/index.js
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "Eternal Jukebox",
|
||||
"icon": "<svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n style=\"stroke-width: 2px !important;\"\n>\n <path d=\"M18.178 8c5.096 0 5.096 8 0 8-5.095 0-7.133-8-12.739-8-4.585 0-4.585 8 0 8 5.606 0 7.644-8 12.74-8z\"></path>\n</svg>",
|
||||
"active-icon": "<svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n style=\"stroke-width: 2px !important;\"\n>\n <path d=\"M18.178 8c5.096 0 5.096 8 0 8-5.095 0-7.133-8-12.739-8-4.585 0-4.585 8 0 8 5.606 0 7.644-8 12.74-8z\"></path>\n</svg>",
|
||||
"subfiles": [],
|
||||
"subfiles_extension": [
|
||||
"extension.js"
|
||||
]
|
||||
}
|
||||
BIN
config/spicetify/CustomApps/eternal-jukebox/preview.png
Normal file
BIN
config/spicetify/CustomApps/eternal-jukebox/preview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 242 KiB |
1
config/spicetify/CustomApps/eternal-jukebox/style.css
Normal file
1
config/spicetify/CustomApps/eternal-jukebox/style.css
Normal file
File diff suppressed because one or more lines are too long
1
config/spicetify/CustomApps/history-in-sidebar/.gitattributes
vendored
Normal file
1
config/spicetify/CustomApps/history-in-sidebar/.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
dist/* linguist-vendored
|
||||
147
config/spicetify/CustomApps/history-in-sidebar/.gitignore
vendored
Normal file
147
config/spicetify/CustomApps/history-in-sidebar/.gitignore
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/node
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=node
|
||||
|
||||
### Node ###
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# build-local directory
|
||||
dist/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
### Node Patch ###
|
||||
# Serverless Webpack directories
|
||||
.webpack/
|
||||
|
||||
# Optional stylelint cache
|
||||
|
||||
# SvelteKit build / generate output
|
||||
.svelte-kit
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/node
|
||||
27
config/spicetify/CustomApps/history-in-sidebar/README.md
Normal file
27
config/spicetify/CustomApps/history-in-sidebar/README.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# History in Sidebar
|
||||
|
||||
Adds a shortcut for the "Recently Played" screen to the sidebar.
|
||||
|
||||
Saves one full click!
|
||||
|
||||
> If you like it, please consider starring it on GitHub 🌟
|
||||
|
||||
<p align="center">
|
||||
<img src="https://github.com/Bergbok/Spicetify-Creations/assets/66174189/ded310d5-374a-4238-98b1-bd2fad737604"/></img>
|
||||
</p>
|
||||
|
||||
## Installation
|
||||
|
||||
1. Install Spicetify ([guide](https://spicetify.app/docs/advanced-usage/installation))
|
||||
2. Download it from [here](https://github.com/Bergbok/Spicetify-Creations/archive/refs/heads/dist/history-in-sidebar.zip)
|
||||
3. Run `spicetify config-dir` in a terminal
|
||||
4. Extract the zip into the CustomApps folder
|
||||
5. Rename the extracted folder to `history-in-sidebar`
|
||||
6. Run `spicetify config custom_apps history-in-sidebar`
|
||||
7. Run `spicetify apply`
|
||||
|
||||
> If you get stuck check out [Spicetify's official guide](https://spicetify.app/docs/advanced-usage/custom-apps/).
|
||||
|
||||
## License
|
||||
|
||||
This repository is licensed under the [MIT License](https://github.com/Bergbok/Spicetify-Creations/blob/main/LICENSE).
|
||||
1934
config/spicetify/CustomApps/history-in-sidebar/package-lock.json
generated
Normal file
1934
config/spicetify/CustomApps/history-in-sidebar/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
17
config/spicetify/CustomApps/history-in-sidebar/package.json
Normal file
17
config/spicetify/CustomApps/history-in-sidebar/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "history-in-sidebar",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "spicetify-creator",
|
||||
"build-local": "spicetify-creator --out=dist --minify",
|
||||
"watch": "spicetify-creator --watch"
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.24",
|
||||
"@types/react": "^18.2.63",
|
||||
"@types/react-dom": "^18.2.19",
|
||||
"spicetify-creator": "^1.0.17"
|
||||
}
|
||||
}
|
||||
BIN
config/spicetify/CustomApps/history-in-sidebar/preview.png
Normal file
BIN
config/spicetify/CustomApps/history-in-sidebar/preview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
13
config/spicetify/CustomApps/history-in-sidebar/src/app.tsx
Normal file
13
config/spicetify/CustomApps/history-in-sidebar/src/app.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react'
|
||||
|
||||
class App extends React.Component {
|
||||
componentDidMount() {
|
||||
Spicetify.Platform.History.push('/history');
|
||||
}
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" viewBox="0 0 24 24" id="history">
|
||||
<path d="M21.001 12a9 9 0 0 0-9-9 1 1 0 1 1 0-2c6.075 0 11 4.925 11 11s-4.925 11-11 11-11-4.925-11-11a1 1 0 1 1 2 0 9 9 0 1 0 18 0zM7.58 4.422a1.25 1.25 0 1 1-1.25-2.165 1.25 1.25 0 0 1 1.25 2.165z"></path>
|
||||
<path d="M11.034 6a1 1 0 0 1 2 0v5H16a1 1 0 1 1 0 2h-4.966V6zM2.67 8.083a1.25 1.25 0 1 0 1.25-2.165 1.25 1.25 0 0 0-1.25 2.165z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 458 B |
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"displayName": "History",
|
||||
"nameId": "history-in-sidebar",
|
||||
"icon": "assets/icon.svg",
|
||||
"activeIcon": "assets/icon.svg"
|
||||
}
|
||||
14
config/spicetify/CustomApps/history-in-sidebar/tsconfig.json
Normal file
14
config/spicetify/CustomApps/history-in-sidebar/tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"jsx": "react",
|
||||
"module": "commonjs",
|
||||
"resolveJsonModule": true,
|
||||
"outDir": "dist",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["./src/**/*", "../../libs/shared/src/types/**/*"]
|
||||
}
|
||||
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);
|
||||
})();
|
||||
|
||||
})();
|
||||
300
config/spicetify/CustomApps/library/collections_wrapper.js
Normal file
300
config/spicetify/CustomApps/library/collections_wrapper.js
Normal file
@@ -0,0 +1,300 @@
|
||||
(async function() {
|
||||
while (!Spicetify.React || !Spicetify.ReactDOM) {
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
}
|
||||
"use strict";
|
||||
var library = (() => {
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var __publicField = (obj, key, value) => {
|
||||
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
||||
return value;
|
||||
};
|
||||
|
||||
// src/extensions/collections_wrapper.ts
|
||||
var collections_wrapper_exports = {};
|
||||
__export(collections_wrapper_exports, {
|
||||
default: () => collections_wrapper_default
|
||||
});
|
||||
|
||||
// ../node_modules/uuid/dist/esm-browser/rng.js
|
||||
var getRandomValues;
|
||||
var rnds8 = new Uint8Array(16);
|
||||
function rng() {
|
||||
if (!getRandomValues) {
|
||||
getRandomValues = typeof crypto !== "undefined" && crypto.getRandomValues && crypto.getRandomValues.bind(crypto);
|
||||
if (!getRandomValues) {
|
||||
throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");
|
||||
}
|
||||
}
|
||||
return getRandomValues(rnds8);
|
||||
}
|
||||
|
||||
// ../node_modules/uuid/dist/esm-browser/stringify.js
|
||||
var byteToHex = [];
|
||||
for (let i = 0; i < 256; ++i) {
|
||||
byteToHex.push((i + 256).toString(16).slice(1));
|
||||
}
|
||||
function unsafeStringify(arr, offset = 0) {
|
||||
return byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]];
|
||||
}
|
||||
|
||||
// ../node_modules/uuid/dist/esm-browser/native.js
|
||||
var randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto);
|
||||
var native_default = {
|
||||
randomUUID
|
||||
};
|
||||
|
||||
// ../node_modules/uuid/dist/esm-browser/v4.js
|
||||
function v4(options, buf, offset) {
|
||||
if (native_default.randomUUID && !buf && !options) {
|
||||
return native_default.randomUUID();
|
||||
}
|
||||
options = options || {};
|
||||
const rnds = options.random || (options.rng || rng)();
|
||||
rnds[6] = rnds[6] & 15 | 64;
|
||||
rnds[8] = rnds[8] & 63 | 128;
|
||||
if (buf) {
|
||||
offset = offset || 0;
|
||||
for (let i = 0; i < 16; ++i) {
|
||||
buf[offset + i] = rnds[i];
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
return unsafeStringify(rnds);
|
||||
}
|
||||
var v4_default = v4;
|
||||
|
||||
// src/extensions/collections_wrapper.ts
|
||||
var _CollectionsWrapper = class extends EventTarget {
|
||||
_collections;
|
||||
constructor() {
|
||||
super();
|
||||
this._collections = JSON.parse(localStorage.getItem("library:collections") || "[]");
|
||||
}
|
||||
saveCollections() {
|
||||
localStorage.setItem("library:collections", JSON.stringify(this._collections));
|
||||
this.dispatchEvent(new CustomEvent("update", { detail: this._collections }));
|
||||
}
|
||||
getCollection(uri) {
|
||||
return this._collections.find((collection) => collection.uri === uri);
|
||||
}
|
||||
async getCollectionContents(uri) {
|
||||
const collection = this.getCollection(uri);
|
||||
if (!collection)
|
||||
throw new Error("Collection not found");
|
||||
const items = this._collections.filter((collection2) => collection2.parentCollection === uri);
|
||||
const albums = await Spicetify.Platform.LibraryAPI.getContents({
|
||||
filters: ["0"],
|
||||
offset: 0,
|
||||
limit: 9999
|
||||
});
|
||||
items.push(...albums.items.filter((album) => collection.items.includes(album.uri)));
|
||||
return items;
|
||||
}
|
||||
async getContents(props) {
|
||||
const { collectionUri, offset, limit, textFilter } = props;
|
||||
let items = collectionUri ? await this.getCollectionContents(collectionUri) : this._collections;
|
||||
const openedCollectionName = collectionUri ? this.getCollection(collectionUri)?.name : void 0;
|
||||
if (textFilter) {
|
||||
const regex = new RegExp(`\\b${textFilter}`, "i");
|
||||
items = items.filter((collection) => regex.test(collection.name));
|
||||
}
|
||||
items = items.slice(offset, offset + limit);
|
||||
return { items, totalLength: this._collections.length, offset, openedCollectionName };
|
||||
}
|
||||
async cleanCollections() {
|
||||
for (const collection of this._collections) {
|
||||
const boolArray = await Spicetify.Platform.LibraryAPI.contains(...collection.items);
|
||||
if (boolArray.includes(false)) {
|
||||
collection.items = collection.items.filter((_, i) => boolArray[i]);
|
||||
this.saveCollections();
|
||||
Spicetify.showNotification("Album removed from collection");
|
||||
this.syncCollection(collection.uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
async syncCollection(uri) {
|
||||
const collection = this.getCollection(uri);
|
||||
if (!collection)
|
||||
return;
|
||||
const { PlaylistAPI } = Spicetify.Platform;
|
||||
if (!collection.syncedPlaylistUri)
|
||||
return;
|
||||
const playlist = await PlaylistAPI.getPlaylist(collection.syncedPlaylistUri);
|
||||
const playlistTracks = playlist.contents.items.filter((t) => t.type === "track").map((t) => t.uri);
|
||||
const collectionTracks = await this.getTracklist(uri);
|
||||
const wanted = collectionTracks.filter((track) => !playlistTracks.includes(track));
|
||||
const unwanted = playlistTracks.filter((track) => !collectionTracks.includes(track)).map((uri2) => ({ uri: uri2, uid: [] }));
|
||||
if (wanted.length)
|
||||
await PlaylistAPI.add(collection.syncedPlaylistUri, wanted, { before: "end" });
|
||||
if (unwanted.length)
|
||||
await PlaylistAPI.remove(collection.syncedPlaylistUri, unwanted);
|
||||
}
|
||||
unsyncCollection(uri) {
|
||||
const collection = this.getCollection(uri);
|
||||
if (!collection)
|
||||
return;
|
||||
collection.syncedPlaylistUri = void 0;
|
||||
this.saveCollections();
|
||||
}
|
||||
async getTracklist(collectionUri) {
|
||||
const collection = this.getCollection(collectionUri);
|
||||
if (!collection)
|
||||
return [];
|
||||
return Promise.all(
|
||||
collection.items.map(async (uri) => {
|
||||
const album = await Spicetify.Platform.LibraryAPI.getAlbum(uri);
|
||||
return album.items.map((t) => t.uri);
|
||||
})
|
||||
).then((tracks) => tracks.flat());
|
||||
}
|
||||
async convertToPlaylist(uri) {
|
||||
const collection = this.getCollection(uri);
|
||||
if (!collection)
|
||||
return;
|
||||
const { Platform, showNotification } = Spicetify;
|
||||
const { RootlistAPI, PlaylistAPI } = Platform;
|
||||
if (collection.syncedPlaylistUri) {
|
||||
showNotification("Synced Playlist already exists", true);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const playlistUri = await RootlistAPI.createPlaylist(collection.name, { before: "start" });
|
||||
const items = await this.getTracklist(uri);
|
||||
await PlaylistAPI.add(playlistUri, items, { before: "start" });
|
||||
collection.syncedPlaylistUri = playlistUri;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
showNotification("Failed to create playlist", true);
|
||||
}
|
||||
}
|
||||
async createCollectionFromDiscog(artistUri) {
|
||||
const [raw, info] = await Promise.all([
|
||||
Spicetify.GraphQL.Request(Spicetify.GraphQL.Definitions.queryArtistDiscographyAlbums, {
|
||||
uri: artistUri,
|
||||
offset: 0,
|
||||
limit: 50
|
||||
}),
|
||||
Spicetify.GraphQL.Request(Spicetify.GraphQL.Definitions.queryArtistOverview, {
|
||||
uri: artistUri,
|
||||
locale: Spicetify.Locale.getLocale(),
|
||||
includePrerelease: false
|
||||
})
|
||||
]);
|
||||
const items = raw?.data?.artistUnion.discography.albums?.items;
|
||||
const name = info?.data?.artistUnion.profile.name;
|
||||
const image = info?.data?.artistUnion.visuals.avatarImage?.sources?.[0]?.url;
|
||||
if (!name || !items?.length) {
|
||||
Spicetify.showNotification("Artist not found or has no albums");
|
||||
return;
|
||||
}
|
||||
const collectionUri = this.createCollection(`${name} Albums`);
|
||||
if (image)
|
||||
this.setCollectionImage(collectionUri, image);
|
||||
for (const album of items) {
|
||||
this.addAlbumToCollection(collectionUri, album.releases.items[0].uri);
|
||||
}
|
||||
}
|
||||
createCollection(name, parentCollection = "") {
|
||||
const id = v4_default();
|
||||
this._collections.push({
|
||||
type: "collection",
|
||||
uri: id,
|
||||
name,
|
||||
items: [],
|
||||
addedAt: new Date(),
|
||||
lastPlayedAt: new Date(),
|
||||
parentCollection
|
||||
});
|
||||
this.saveCollections();
|
||||
Spicetify.showNotification("Collection created");
|
||||
return id;
|
||||
}
|
||||
deleteCollection(uri) {
|
||||
this._collections = this._collections.filter((collection) => collection.uri !== uri);
|
||||
this.saveCollections();
|
||||
Spicetify.showNotification("Collection deleted");
|
||||
}
|
||||
deleteCollectionAndAlbums(uri) {
|
||||
const collection = this.getCollection(uri);
|
||||
if (!collection)
|
||||
return;
|
||||
for (const album of collection.items) {
|
||||
Spicetify.Platform.LibraryAPI.remove({ uris: [album] });
|
||||
}
|
||||
this.deleteCollection(uri);
|
||||
}
|
||||
async addAlbumToCollection(collectionUri, albumUri) {
|
||||
const collection = this.getCollection(collectionUri);
|
||||
if (!collection)
|
||||
return;
|
||||
await Spicetify.Platform.LibraryAPI.add({ uris: [albumUri] });
|
||||
collection.items.push(albumUri);
|
||||
this.saveCollections();
|
||||
Spicetify.showNotification("Album added to collection");
|
||||
this.syncCollection(collectionUri);
|
||||
}
|
||||
removeAlbumFromCollection(collectionUri, albumUri) {
|
||||
const collection = this.getCollection(collectionUri);
|
||||
if (!collection)
|
||||
return;
|
||||
collection.items = collection.items.filter((item) => item !== albumUri);
|
||||
this.saveCollections();
|
||||
Spicetify.showNotification("Album removed from collection");
|
||||
this.syncCollection(collectionUri);
|
||||
}
|
||||
getCollectionsWithAlbum(albumUri) {
|
||||
return this._collections.filter((collection) => {
|
||||
return collection.items.some((item) => item === albumUri);
|
||||
});
|
||||
}
|
||||
renameCollection(uri, name) {
|
||||
const collection = this.getCollection(uri);
|
||||
if (!collection)
|
||||
return;
|
||||
collection.name = name;
|
||||
this.saveCollections();
|
||||
Spicetify.showNotification("Collection renamed");
|
||||
}
|
||||
setCollectionImage(uri, url) {
|
||||
const collection = this.getCollection(uri);
|
||||
if (!collection)
|
||||
return;
|
||||
collection.image = url;
|
||||
this.saveCollections();
|
||||
Spicetify.showNotification("Collection image set");
|
||||
}
|
||||
removeCollectionImage(uri) {
|
||||
const collection = this.getCollection(uri);
|
||||
if (!collection)
|
||||
return;
|
||||
collection.image = void 0;
|
||||
this.saveCollections();
|
||||
Spicetify.showNotification("Collection image removed");
|
||||
}
|
||||
};
|
||||
var CollectionsWrapper = _CollectionsWrapper;
|
||||
__publicField(CollectionsWrapper, "INSTANCE", new _CollectionsWrapper());
|
||||
window.CollectionsWrapper = CollectionsWrapper.INSTANCE;
|
||||
var collections_wrapper_default = CollectionsWrapper;
|
||||
return __toCommonJS(collections_wrapper_exports);
|
||||
})();
|
||||
|
||||
})();
|
||||
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 });
|
||||
})();
|
||||
|
||||
})();
|
||||
983
config/spicetify/CustomApps/library/extension.js
Normal file
983
config/spicetify/CustomApps/library/extension.js
Normal file
@@ -0,0 +1,983 @@
|
||||
(async function() {
|
||||
while (!Spicetify.React || !Spicetify.ReactDOM) {
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
}
|
||||
"use strict";
|
||||
var library = (() => {
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
||||
var __commonJS = (cb, mod) => function __require() {
|
||||
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __publicField = (obj, key, value) => {
|
||||
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
||||
return value;
|
||||
};
|
||||
|
||||
// external-global-plugin:react
|
||||
var require_react = __commonJS({
|
||||
"external-global-plugin:react"(exports, module) {
|
||||
module.exports = Spicetify.React;
|
||||
}
|
||||
});
|
||||
|
||||
// external-global-plugin:react-dom
|
||||
var require_react_dom = __commonJS({
|
||||
"external-global-plugin:react-dom"(exports, module) {
|
||||
module.exports = Spicetify.ReactDOM;
|
||||
}
|
||||
});
|
||||
|
||||
// ../shared/config/config_wrapper.tsx
|
||||
var import_react2 = __toESM(require_react());
|
||||
|
||||
// ../shared/config/config_modal.tsx
|
||||
var import_react = __toESM(require_react());
|
||||
var TextInput = (props) => {
|
||||
const handleTextChange = (event) => {
|
||||
props.callback(event.target.value);
|
||||
};
|
||||
return /* @__PURE__ */ import_react.default.createElement("label", {
|
||||
className: "text-input-wrapper"
|
||||
}, /* @__PURE__ */ import_react.default.createElement("input", {
|
||||
className: "text-input",
|
||||
type: "text",
|
||||
value: props.value || "",
|
||||
"data-storage-key": props.storageKey,
|
||||
placeholder: props.placeholder,
|
||||
id: `text-input:${props.storageKey}`,
|
||||
title: `Text input for ${props.storageKey}`,
|
||||
onChange: handleTextChange
|
||||
}));
|
||||
};
|
||||
var Dropdown = (props) => {
|
||||
const handleDropdownChange = (event) => {
|
||||
props.callback(event.target.value);
|
||||
};
|
||||
return /* @__PURE__ */ import_react.default.createElement("label", {
|
||||
className: "dropdown-wrapper"
|
||||
}, /* @__PURE__ */ import_react.default.createElement("select", {
|
||||
className: "dropdown-input",
|
||||
value: props.value,
|
||||
"data-storage-key": props.storageKey,
|
||||
id: `dropdown:${props.storageKey}`,
|
||||
title: `Dropdown for ${props.storageKey}`,
|
||||
onChange: handleDropdownChange
|
||||
}, props.options.map((option, index) => /* @__PURE__ */ import_react.default.createElement("option", {
|
||||
key: index,
|
||||
value: option
|
||||
}, option))));
|
||||
};
|
||||
var ToggleInput = (props) => {
|
||||
const { Toggle } = Spicetify.ReactComponent;
|
||||
const handleToggleChange = (newValue) => {
|
||||
props.callback(newValue);
|
||||
};
|
||||
return /* @__PURE__ */ import_react.default.createElement(Toggle, {
|
||||
id: `toggle:${props.storageKey}`,
|
||||
value: props.value,
|
||||
onSelected: (newValue) => handleToggleChange(newValue)
|
||||
});
|
||||
};
|
||||
var SliderInput = (props) => {
|
||||
const { Slider } = Spicetify.ReactComponent;
|
||||
const handleSliderChange = (newValue) => {
|
||||
const calculatedValue = props.min + newValue * (props.max - props.min);
|
||||
props.callback(calculatedValue);
|
||||
};
|
||||
const value = (props.value - props.min) / (props.max - props.min);
|
||||
return /* @__PURE__ */ import_react.default.createElement(Slider, {
|
||||
id: `slider:${props.storageKey}`,
|
||||
value,
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.1,
|
||||
onDragMove: (newValue) => handleSliderChange(newValue),
|
||||
onDragStart: () => {
|
||||
},
|
||||
onDragEnd: () => {
|
||||
}
|
||||
});
|
||||
};
|
||||
var TooltipIcon = () => {
|
||||
return /* @__PURE__ */ import_react.default.createElement("svg", {
|
||||
role: "img",
|
||||
height: "16",
|
||||
width: "16",
|
||||
className: "Svg-sc-ytk21e-0 uPxdw nW1RKQOkzcJcX6aDCZB4",
|
||||
viewBox: "0 0 16 16"
|
||||
}, /* @__PURE__ */ import_react.default.createElement("path", {
|
||||
d: "M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8z"
|
||||
}), /* @__PURE__ */ import_react.default.createElement("path", {
|
||||
d: "M7.25 12.026v-1.5h1.5v1.5h-1.5zm.884-7.096A1.125 1.125 0 007.06 6.39l-1.431.448a2.625 2.625 0 115.13-.784c0 .54-.156 1.015-.503 1.488-.3.408-.7.652-.973.818l-.112.068c-.185.116-.26.203-.302.283-.046.087-.097.245-.097.57h-1.5c0-.47.072-.898.274-1.277.206-.385.507-.645.827-.846l.147-.092c.285-.177.413-.257.526-.41.169-.23.213-.397.213-.602 0-.622-.503-1.125-1.125-1.125z"
|
||||
}));
|
||||
};
|
||||
var ConfigRow = (props) => {
|
||||
return /* @__PURE__ */ import_react.default.createElement("div", {
|
||||
className: "setting-row"
|
||||
}, /* @__PURE__ */ import_react.default.createElement("label", {
|
||||
className: "col description"
|
||||
}, props.name, props.desc && /* @__PURE__ */ import_react.default.createElement(Spicetify.ReactComponent.TooltipWrapper, {
|
||||
label: /* @__PURE__ */ import_react.default.createElement("div", {
|
||||
dangerouslySetInnerHTML: { __html: props.desc }
|
||||
}),
|
||||
renderInline: true,
|
||||
showDelay: 10,
|
||||
placement: "top",
|
||||
labelClassName: "tooltip",
|
||||
disabled: false
|
||||
}, /* @__PURE__ */ import_react.default.createElement("div", {
|
||||
className: "tooltip-icon"
|
||||
}, /* @__PURE__ */ import_react.default.createElement(TooltipIcon, null)))), /* @__PURE__ */ import_react.default.createElement("div", {
|
||||
className: "col action"
|
||||
}, props.children));
|
||||
};
|
||||
var ConfigModal = (props) => {
|
||||
const { config, structure, appKey, updateAppConfig } = props;
|
||||
const [modalConfig, setModalConfig] = import_react.default.useState({ ...config });
|
||||
const modalRows = structure.map((modalRow, index) => {
|
||||
const key = modalRow.key;
|
||||
const currentValue = modalConfig[key];
|
||||
const updateItem = (state) => {
|
||||
console.debug(`toggling ${key} to ${state}`);
|
||||
localStorage.setItem(`${appKey}:config:${key}`, String(state));
|
||||
if (modalRow.callback)
|
||||
modalRow.callback(state);
|
||||
const newConfig = { ...modalConfig };
|
||||
newConfig[key] = state;
|
||||
updateAppConfig(newConfig);
|
||||
setModalConfig(newConfig);
|
||||
};
|
||||
const header = modalRow.sectionHeader;
|
||||
const element = () => {
|
||||
switch (modalRow.type) {
|
||||
case "toggle":
|
||||
return /* @__PURE__ */ import_react.default.createElement(ToggleInput, {
|
||||
storageKey: key,
|
||||
value: currentValue,
|
||||
callback: updateItem
|
||||
});
|
||||
case "text":
|
||||
return /* @__PURE__ */ import_react.default.createElement(TextInput, {
|
||||
storageKey: key,
|
||||
value: currentValue,
|
||||
callback: updateItem
|
||||
});
|
||||
case "dropdown":
|
||||
return /* @__PURE__ */ import_react.default.createElement(Dropdown, {
|
||||
storageKey: key,
|
||||
value: currentValue,
|
||||
options: modalRow.options,
|
||||
callback: updateItem
|
||||
});
|
||||
case "slider":
|
||||
return /* @__PURE__ */ import_react.default.createElement(SliderInput, {
|
||||
storageKey: key,
|
||||
value: currentValue,
|
||||
min: modalRow.min,
|
||||
max: modalRow.max,
|
||||
step: modalRow.step,
|
||||
callback: updateItem
|
||||
});
|
||||
}
|
||||
};
|
||||
return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, header && index !== 0 && /* @__PURE__ */ import_react.default.createElement("br", null), header && /* @__PURE__ */ import_react.default.createElement("h2", {
|
||||
className: "section-header"
|
||||
}, modalRow.sectionHeader), /* @__PURE__ */ import_react.default.createElement(ConfigRow, {
|
||||
name: modalRow.name,
|
||||
desc: modalRow.desc
|
||||
}, element()));
|
||||
});
|
||||
return /* @__PURE__ */ import_react.default.createElement("div", {
|
||||
className: "config-container"
|
||||
}, modalRows);
|
||||
};
|
||||
var config_modal_default = ConfigModal;
|
||||
|
||||
// ../shared/config/config_wrapper.tsx
|
||||
var _ConfigWrapper = class {
|
||||
Config;
|
||||
launchModal;
|
||||
constructor(modalStructure, key) {
|
||||
const config = modalStructure.map((modalStructureRow) => {
|
||||
const value = _ConfigWrapper.getLocalStorageDataFromKey(
|
||||
`${key}:config:${modalStructureRow.key}`,
|
||||
modalStructureRow.def
|
||||
);
|
||||
modalStructureRow.callback?.(value);
|
||||
return { [modalStructureRow.key]: value };
|
||||
});
|
||||
this.Config = Object.assign({}, ...config);
|
||||
this.launchModal = (callback) => {
|
||||
const updateConfig = (config2) => {
|
||||
this.Config = { ...config2 };
|
||||
callback?.(config2);
|
||||
};
|
||||
Spicetify.PopupModal.display({
|
||||
title: `${key.charAt(0).toUpperCase() + key.slice(1)} Settings`,
|
||||
content: /* @__PURE__ */ import_react2.default.createElement(config_modal_default, {
|
||||
config: this.Config,
|
||||
structure: modalStructure,
|
||||
appKey: key,
|
||||
updateAppConfig: updateConfig
|
||||
}),
|
||||
isLarge: true
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
var ConfigWrapper = _ConfigWrapper;
|
||||
__publicField(ConfigWrapper, "getLocalStorageDataFromKey", (key, fallback) => {
|
||||
const data = localStorage.getItem(key);
|
||||
if (data) {
|
||||
try {
|
||||
return JSON.parse(data);
|
||||
} catch (err) {
|
||||
return data;
|
||||
}
|
||||
} else {
|
||||
return fallback;
|
||||
}
|
||||
});
|
||||
var config_wrapper_default = ConfigWrapper;
|
||||
|
||||
// src/extensions/extension.tsx
|
||||
var import_react10 = __toESM(require_react());
|
||||
var import_react_dom = __toESM(require_react_dom());
|
||||
|
||||
// src/components/toggle_filters.tsx
|
||||
var import_react3 = __toESM(require_react());
|
||||
var UpIcon = () => {
|
||||
const { IconComponent } = Spicetify.ReactComponent;
|
||||
return /* @__PURE__ */ import_react3.default.createElement(IconComponent, {
|
||||
semanticColor: "textSubdued",
|
||||
dangerouslySetInnerHTML: {
|
||||
__html: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M.998 8.81A.749.749 0 0 1 .47 7.53L7.99 0l7.522 7.53a.75.75 0 1 1-1.06 1.06L8.74 2.87v12.38a.75.75 0 1 1-1.498 0V2.87L1.528 8.59a.751.751 0 0 1-.53.22z"></path></svg>'
|
||||
},
|
||||
iconSize: 16
|
||||
});
|
||||
};
|
||||
var DownIcon = () => {
|
||||
const { IconComponent } = Spicetify.ReactComponent;
|
||||
return /* @__PURE__ */ import_react3.default.createElement(IconComponent, {
|
||||
semanticColor: "textSubdued",
|
||||
dangerouslySetInnerHTML: {
|
||||
__html: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M.998 7.19A.749.749 0 0 0 .47 8.47L7.99 16l7.522-7.53a.75.75 0 1 0-1.06-1.06L8.74 13.13V.75a.75.75 0 1 0-1.498 0v12.38L1.528 7.41a.749.749 0 0 0-.53-.22z"></path></svg>'
|
||||
},
|
||||
iconSize: 16
|
||||
});
|
||||
};
|
||||
var ToggleFiltersButton = () => {
|
||||
const [direction, setDirection] = import_react3.default.useState(
|
||||
document.body.classList.contains("show-ylx-filters") ? "up" : "down"
|
||||
);
|
||||
const { ButtonTertiary } = Spicetify.ReactComponent;
|
||||
const toggleDirection = () => {
|
||||
if (direction === "down") {
|
||||
document.body.classList.add("show-ylx-filters");
|
||||
setDirection("up");
|
||||
} else {
|
||||
setDirection("down");
|
||||
document.body.classList.remove("show-ylx-filters");
|
||||
}
|
||||
};
|
||||
const Icon = direction === "down" ? DownIcon : UpIcon;
|
||||
return /* @__PURE__ */ import_react3.default.createElement(ButtonTertiary, {
|
||||
buttonSize: "sm",
|
||||
"aria-label": "Show Filters",
|
||||
iconOnly: Icon,
|
||||
onClick: toggleDirection
|
||||
});
|
||||
};
|
||||
var toggle_filters_default = ToggleFiltersButton;
|
||||
|
||||
// src/components/collapse_button.tsx
|
||||
var import_react4 = __toESM(require_react());
|
||||
var collapseLibrary = () => {
|
||||
Spicetify.Platform.LocalStorageAPI.setItem("ylx-sidebar-state", 1);
|
||||
};
|
||||
var CollapseIcon = () => {
|
||||
const { IconComponent } = Spicetify.ReactComponent;
|
||||
return /* @__PURE__ */ import_react4.default.createElement(IconComponent, {
|
||||
semanticColor: "textSubdued",
|
||||
dangerouslySetInnerHTML: {
|
||||
__html: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M8.81 1A.749.749 0 0 0 7.53.47L0 7.99l7.53 7.521a.75.75 0 0 0 1.234-.815.75.75 0 0 0-.174-.243L2.87 8.74h12.38a.75.75 0 1 0 0-1.498H2.87l5.72-5.713c.14-.14.22-.331.22-.53z"></path></svg>'
|
||||
},
|
||||
iconSize: 16
|
||||
});
|
||||
};
|
||||
var CollapseButton = () => {
|
||||
const { ButtonTertiary } = Spicetify.ReactComponent;
|
||||
return /* @__PURE__ */ import_react4.default.createElement(ButtonTertiary, {
|
||||
buttonSize: "sm",
|
||||
"aria-label": "Show Filters",
|
||||
iconOnly: CollapseIcon,
|
||||
onClick: collapseLibrary
|
||||
});
|
||||
};
|
||||
var collapse_button_default = CollapseButton;
|
||||
|
||||
// src/components/album_menu_item.tsx
|
||||
var import_react8 = __toESM(require_react());
|
||||
|
||||
// src/components/leading_icon.tsx
|
||||
var import_react5 = __toESM(require_react());
|
||||
var LeadingIcon = ({ path }) => {
|
||||
return /* @__PURE__ */ import_react5.default.createElement(Spicetify.ReactComponent.IconComponent, {
|
||||
semanticColor: "textSubdued",
|
||||
dangerouslySetInnerHTML: {
|
||||
__html: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">${path}</svg>`
|
||||
},
|
||||
iconSize: 16
|
||||
});
|
||||
};
|
||||
var leading_icon_default = LeadingIcon;
|
||||
|
||||
// src/components/text_input_dialog.tsx
|
||||
var import_react6 = __toESM(require_react());
|
||||
var TextInputDialog = (props) => {
|
||||
const { def, placeholder, onSave } = props;
|
||||
const [value, setValue] = import_react6.default.useState(def || "");
|
||||
const onSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
Spicetify.PopupModal.hide();
|
||||
onSave(value);
|
||||
};
|
||||
return /* @__PURE__ */ import_react6.default.createElement(import_react6.default.Fragment, null, /* @__PURE__ */ import_react6.default.createElement("form", {
|
||||
className: "text-input-form",
|
||||
onSubmit
|
||||
}, /* @__PURE__ */ import_react6.default.createElement("label", {
|
||||
className: "text-input-wrapper"
|
||||
}, /* @__PURE__ */ import_react6.default.createElement("input", {
|
||||
className: "text-input",
|
||||
type: "text",
|
||||
value,
|
||||
placeholder,
|
||||
onChange: (e) => setValue(e.target.value)
|
||||
})), /* @__PURE__ */ import_react6.default.createElement("button", {
|
||||
type: "submit",
|
||||
"data-encore-id": "buttonPrimary",
|
||||
className: "Button-sc-qlcn5g-0 Button-small-buttonPrimary"
|
||||
}, /* @__PURE__ */ import_react6.default.createElement("span", {
|
||||
className: "ButtonInner-sc-14ud5tc-0 ButtonInner-small encore-bright-accent-set"
|
||||
}, "Save"))));
|
||||
};
|
||||
var text_input_dialog_default = TextInputDialog;
|
||||
|
||||
// src/components/searchbar.tsx
|
||||
var import_react7 = __toESM(require_react());
|
||||
var SearchBar = (props) => {
|
||||
const { setSearch, placeholder } = props;
|
||||
const handleChange = (e) => {
|
||||
setSearch(e.target.value);
|
||||
};
|
||||
return /* @__PURE__ */ import_react7.default.createElement("div", {
|
||||
className: "x-filterBox-filterInputContainer x-filterBox-expandedOrHasFilter",
|
||||
role: "search"
|
||||
}, /* @__PURE__ */ import_react7.default.createElement("input", {
|
||||
type: "text",
|
||||
className: "x-filterBox-filterInput",
|
||||
role: "searchbox",
|
||||
maxLength: 80,
|
||||
autoCorrect: "off",
|
||||
autoCapitalize: "off",
|
||||
spellCheck: "false",
|
||||
placeholder: `Search ${placeholder}`,
|
||||
"aria-hidden": "false",
|
||||
onChange: handleChange
|
||||
}), /* @__PURE__ */ import_react7.default.createElement("div", {
|
||||
className: "x-filterBox-overlay"
|
||||
}, /* @__PURE__ */ import_react7.default.createElement("span", {
|
||||
className: "x-filterBox-searchIconContainer"
|
||||
}, /* @__PURE__ */ import_react7.default.createElement("svg", {
|
||||
"data-encore-id": "icon",
|
||||
role: "img",
|
||||
"aria-hidden": "true",
|
||||
className: "Svg-sc-ytk21e-0 Svg-img-icon-small x-filterBox-searchIcon",
|
||||
viewBox: "0 0 16 16"
|
||||
}, /* @__PURE__ */ import_react7.default.createElement("path", {
|
||||
d: "M7 1.75a5.25 5.25 0 1 0 0 10.5 5.25 5.25 0 0 0 0-10.5zM.25 7a6.75 6.75 0 1 1 12.096 4.12l3.184 3.185a.75.75 0 1 1-1.06 1.06L11.304 12.2A6.75 6.75 0 0 1 .25 7z"
|
||||
})))), /* @__PURE__ */ import_react7.default.createElement("button", {
|
||||
className: "x-filterBox-expandButton",
|
||||
"aria-hidden": "false",
|
||||
"aria-label": "Search Playlists",
|
||||
type: "button"
|
||||
}, /* @__PURE__ */ import_react7.default.createElement("svg", {
|
||||
"data-encore-id": "icon",
|
||||
role: "img",
|
||||
"aria-hidden": "true",
|
||||
className: "Svg-sc-ytk21e-0 Svg-img-icon-small x-filterBox-searchIcon",
|
||||
viewBox: "0 0 16 16"
|
||||
}, /* @__PURE__ */ import_react7.default.createElement("path", {
|
||||
d: "M7 1.75a5.25 5.25 0 1 0 0 10.5 5.25 5.25 0 0 0 0-10.5zM.25 7a6.75 6.75 0 1 1 12.096 4.12l3.184 3.185a.75.75 0 1 1-1.06 1.06L11.304 12.2A6.75 6.75 0 0 1 .25 7z"
|
||||
}))));
|
||||
};
|
||||
var searchbar_default = SearchBar;
|
||||
|
||||
// src/components/album_menu_item.tsx
|
||||
var createCollection = () => {
|
||||
const onSave = (value) => {
|
||||
CollectionsWrapper.createCollection(value);
|
||||
};
|
||||
Spicetify.PopupModal.display({
|
||||
title: "Create Collection",
|
||||
content: /* @__PURE__ */ import_react8.default.createElement(text_input_dialog_default, {
|
||||
def: "New Collection",
|
||||
placeholder: "Collection Name",
|
||||
onSave
|
||||
})
|
||||
});
|
||||
};
|
||||
var CollectionSearchMenu = () => {
|
||||
const { MenuItem } = Spicetify.ReactComponent;
|
||||
const { SVGIcons } = Spicetify;
|
||||
const [textFilter, setTextFilter] = import_react8.default.useState("");
|
||||
const [collections, setCollections] = import_react8.default.useState(null);
|
||||
const context = import_react8.default.useContext(Spicetify.ContextMenuV2._context);
|
||||
const uri = context?.props?.uri;
|
||||
import_react8.default.useEffect(() => {
|
||||
const fetchCollections = async () => {
|
||||
setCollections(await CollectionsWrapper.getContents({ textFilter, limit: 20, offset: 0 }));
|
||||
};
|
||||
fetchCollections();
|
||||
}, [textFilter]);
|
||||
if (!collections)
|
||||
return /* @__PURE__ */ import_react8.default.createElement(import_react8.default.Fragment, null);
|
||||
const addToCollection = (collectionUri) => {
|
||||
CollectionsWrapper.addAlbumToCollection(collectionUri, uri);
|
||||
};
|
||||
const activeCollections = CollectionsWrapper.getCollectionsWithAlbum(uri);
|
||||
const hasCollections = activeCollections.length > 0;
|
||||
const removeFromCollections = () => {
|
||||
for (const collection of activeCollections) {
|
||||
CollectionsWrapper.removeAlbumFromCollection(collection.uri, uri);
|
||||
}
|
||||
};
|
||||
const allCollectionsLength = collections.totalLength;
|
||||
const menuItems = collections.items.map((collection, index) => {
|
||||
return /* @__PURE__ */ import_react8.default.createElement(MenuItem, {
|
||||
key: collection.uri,
|
||||
onClick: () => {
|
||||
addToCollection(collection.uri);
|
||||
},
|
||||
divider: index === 0 ? "before" : void 0
|
||||
}, collection.name);
|
||||
});
|
||||
const menuLength = allCollectionsLength + (hasCollections ? 1 : 0);
|
||||
return /* @__PURE__ */ import_react8.default.createElement("div", {
|
||||
className: "main-contextMenu-filterPlaylistSearchContainer",
|
||||
style: { "--context-menu-submenu-length": `${menuLength}` }
|
||||
}, /* @__PURE__ */ import_react8.default.createElement("li", {
|
||||
role: "presentation",
|
||||
className: "main-contextMenu-filterPlaylistSearch"
|
||||
}, /* @__PURE__ */ import_react8.default.createElement("div", {
|
||||
role: "menuitem"
|
||||
}, /* @__PURE__ */ import_react8.default.createElement(searchbar_default, {
|
||||
setSearch: setTextFilter,
|
||||
placeholder: "collections"
|
||||
}))), /* @__PURE__ */ import_react8.default.createElement(MenuItem, {
|
||||
key: "new-collection",
|
||||
leadingIcon: /* @__PURE__ */ import_react8.default.createElement(leading_icon_default, {
|
||||
path: SVGIcons.plus2px
|
||||
}),
|
||||
onClick: createCollection
|
||||
}, "Create collection"), hasCollections && /* @__PURE__ */ import_react8.default.createElement(MenuItem, {
|
||||
key: "remove-collection",
|
||||
leadingIcon: /* @__PURE__ */ import_react8.default.createElement(leading_icon_default, {
|
||||
path: SVGIcons.minus
|
||||
}),
|
||||
onClick: removeFromCollections
|
||||
}, "Remove from all"), menuItems);
|
||||
};
|
||||
var AlbumMenuItem = () => {
|
||||
const { MenuSubMenuItem } = Spicetify.ReactComponent;
|
||||
const { SVGIcons } = Spicetify;
|
||||
return /* @__PURE__ */ import_react8.default.createElement(MenuSubMenuItem, {
|
||||
displayText: "Add to collection",
|
||||
divider: "after",
|
||||
leadingIcon: /* @__PURE__ */ import_react8.default.createElement(leading_icon_default, {
|
||||
path: SVGIcons.plus2px
|
||||
})
|
||||
}, /* @__PURE__ */ import_react8.default.createElement(CollectionSearchMenu, null));
|
||||
};
|
||||
var album_menu_item_default = AlbumMenuItem;
|
||||
|
||||
// src/components/artist_menu_item.tsx
|
||||
var import_react9 = __toESM(require_react());
|
||||
var ArtistMenuItem = () => {
|
||||
const { MenuItem } = Spicetify.ReactComponent;
|
||||
const { SVGIcons } = Spicetify;
|
||||
const context = import_react9.default.useContext(Spicetify.ContextMenuV2._context);
|
||||
const uri = context?.props?.uri;
|
||||
return /* @__PURE__ */ import_react9.default.createElement(MenuItem, {
|
||||
divider: "after",
|
||||
leadingIcon: /* @__PURE__ */ import_react9.default.createElement(leading_icon_default, {
|
||||
path: SVGIcons.plus2px
|
||||
}),
|
||||
onClick: () => CollectionsWrapper.createCollectionFromDiscog(uri)
|
||||
}, "Create Discog Collection");
|
||||
};
|
||||
var artist_menu_item_default = ArtistMenuItem;
|
||||
|
||||
// ../node_modules/uuid/dist/esm-browser/rng.js
|
||||
var getRandomValues;
|
||||
var rnds8 = new Uint8Array(16);
|
||||
function rng() {
|
||||
if (!getRandomValues) {
|
||||
getRandomValues = typeof crypto !== "undefined" && crypto.getRandomValues && crypto.getRandomValues.bind(crypto);
|
||||
if (!getRandomValues) {
|
||||
throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");
|
||||
}
|
||||
}
|
||||
return getRandomValues(rnds8);
|
||||
}
|
||||
|
||||
// ../node_modules/uuid/dist/esm-browser/stringify.js
|
||||
var byteToHex = [];
|
||||
for (let i = 0; i < 256; ++i) {
|
||||
byteToHex.push((i + 256).toString(16).slice(1));
|
||||
}
|
||||
function unsafeStringify(arr, offset = 0) {
|
||||
return byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]];
|
||||
}
|
||||
|
||||
// ../node_modules/uuid/dist/esm-browser/native.js
|
||||
var randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto);
|
||||
var native_default = {
|
||||
randomUUID
|
||||
};
|
||||
|
||||
// ../node_modules/uuid/dist/esm-browser/v4.js
|
||||
function v4(options, buf, offset) {
|
||||
if (native_default.randomUUID && !buf && !options) {
|
||||
return native_default.randomUUID();
|
||||
}
|
||||
options = options || {};
|
||||
const rnds = options.random || (options.rng || rng)();
|
||||
rnds[6] = rnds[6] & 15 | 64;
|
||||
rnds[8] = rnds[8] & 63 | 128;
|
||||
if (buf) {
|
||||
offset = offset || 0;
|
||||
for (let i = 0; i < 16; ++i) {
|
||||
buf[offset + i] = rnds[i];
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
return unsafeStringify(rnds);
|
||||
}
|
||||
var v4_default = v4;
|
||||
|
||||
// src/extensions/collections_wrapper.ts
|
||||
var _CollectionsWrapper = class extends EventTarget {
|
||||
_collections;
|
||||
constructor() {
|
||||
super();
|
||||
this._collections = JSON.parse(localStorage.getItem("library:collections") || "[]");
|
||||
}
|
||||
saveCollections() {
|
||||
localStorage.setItem("library:collections", JSON.stringify(this._collections));
|
||||
this.dispatchEvent(new CustomEvent("update", { detail: this._collections }));
|
||||
}
|
||||
getCollection(uri) {
|
||||
return this._collections.find((collection) => collection.uri === uri);
|
||||
}
|
||||
async getCollectionContents(uri) {
|
||||
const collection = this.getCollection(uri);
|
||||
if (!collection)
|
||||
throw new Error("Collection not found");
|
||||
const items = this._collections.filter((collection2) => collection2.parentCollection === uri);
|
||||
const albums = await Spicetify.Platform.LibraryAPI.getContents({
|
||||
filters: ["0"],
|
||||
offset: 0,
|
||||
limit: 9999
|
||||
});
|
||||
items.push(...albums.items.filter((album) => collection.items.includes(album.uri)));
|
||||
return items;
|
||||
}
|
||||
async getContents(props) {
|
||||
const { collectionUri, offset, limit, textFilter } = props;
|
||||
let items = collectionUri ? await this.getCollectionContents(collectionUri) : this._collections;
|
||||
const openedCollectionName = collectionUri ? this.getCollection(collectionUri)?.name : void 0;
|
||||
if (textFilter) {
|
||||
const regex = new RegExp(`\\b${textFilter}`, "i");
|
||||
items = items.filter((collection) => regex.test(collection.name));
|
||||
}
|
||||
items = items.slice(offset, offset + limit);
|
||||
return { items, totalLength: this._collections.length, offset, openedCollectionName };
|
||||
}
|
||||
async cleanCollections() {
|
||||
for (const collection of this._collections) {
|
||||
const boolArray = await Spicetify.Platform.LibraryAPI.contains(...collection.items);
|
||||
if (boolArray.includes(false)) {
|
||||
collection.items = collection.items.filter((_, i) => boolArray[i]);
|
||||
this.saveCollections();
|
||||
Spicetify.showNotification("Album removed from collection");
|
||||
this.syncCollection(collection.uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
async syncCollection(uri) {
|
||||
const collection = this.getCollection(uri);
|
||||
if (!collection)
|
||||
return;
|
||||
const { PlaylistAPI } = Spicetify.Platform;
|
||||
if (!collection.syncedPlaylistUri)
|
||||
return;
|
||||
const playlist = await PlaylistAPI.getPlaylist(collection.syncedPlaylistUri);
|
||||
const playlistTracks = playlist.contents.items.filter((t) => t.type === "track").map((t) => t.uri);
|
||||
const collectionTracks = await this.getTracklist(uri);
|
||||
const wanted = collectionTracks.filter((track) => !playlistTracks.includes(track));
|
||||
const unwanted = playlistTracks.filter((track) => !collectionTracks.includes(track)).map((uri2) => ({ uri: uri2, uid: [] }));
|
||||
if (wanted.length)
|
||||
await PlaylistAPI.add(collection.syncedPlaylistUri, wanted, { before: "end" });
|
||||
if (unwanted.length)
|
||||
await PlaylistAPI.remove(collection.syncedPlaylistUri, unwanted);
|
||||
}
|
||||
unsyncCollection(uri) {
|
||||
const collection = this.getCollection(uri);
|
||||
if (!collection)
|
||||
return;
|
||||
collection.syncedPlaylistUri = void 0;
|
||||
this.saveCollections();
|
||||
}
|
||||
async getTracklist(collectionUri) {
|
||||
const collection = this.getCollection(collectionUri);
|
||||
if (!collection)
|
||||
return [];
|
||||
return Promise.all(
|
||||
collection.items.map(async (uri) => {
|
||||
const album = await Spicetify.Platform.LibraryAPI.getAlbum(uri);
|
||||
return album.items.map((t) => t.uri);
|
||||
})
|
||||
).then((tracks) => tracks.flat());
|
||||
}
|
||||
async convertToPlaylist(uri) {
|
||||
const collection = this.getCollection(uri);
|
||||
if (!collection)
|
||||
return;
|
||||
const { Platform, showNotification } = Spicetify;
|
||||
const { RootlistAPI, PlaylistAPI } = Platform;
|
||||
if (collection.syncedPlaylistUri) {
|
||||
showNotification("Synced Playlist already exists", true);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const playlistUri = await RootlistAPI.createPlaylist(collection.name, { before: "start" });
|
||||
const items = await this.getTracklist(uri);
|
||||
await PlaylistAPI.add(playlistUri, items, { before: "start" });
|
||||
collection.syncedPlaylistUri = playlistUri;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
showNotification("Failed to create playlist", true);
|
||||
}
|
||||
}
|
||||
async createCollectionFromDiscog(artistUri) {
|
||||
const [raw, info] = await Promise.all([
|
||||
Spicetify.GraphQL.Request(Spicetify.GraphQL.Definitions.queryArtistDiscographyAlbums, {
|
||||
uri: artistUri,
|
||||
offset: 0,
|
||||
limit: 50
|
||||
}),
|
||||
Spicetify.GraphQL.Request(Spicetify.GraphQL.Definitions.queryArtistOverview, {
|
||||
uri: artistUri,
|
||||
locale: Spicetify.Locale.getLocale(),
|
||||
includePrerelease: false
|
||||
})
|
||||
]);
|
||||
const items = raw?.data?.artistUnion.discography.albums?.items;
|
||||
const name = info?.data?.artistUnion.profile.name;
|
||||
const image = info?.data?.artistUnion.visuals.avatarImage?.sources?.[0]?.url;
|
||||
if (!name || !items?.length) {
|
||||
Spicetify.showNotification("Artist not found or has no albums");
|
||||
return;
|
||||
}
|
||||
const collectionUri = this.createCollection(`${name} Albums`);
|
||||
if (image)
|
||||
this.setCollectionImage(collectionUri, image);
|
||||
for (const album of items) {
|
||||
this.addAlbumToCollection(collectionUri, album.releases.items[0].uri);
|
||||
}
|
||||
}
|
||||
createCollection(name, parentCollection = "") {
|
||||
const id = v4_default();
|
||||
this._collections.push({
|
||||
type: "collection",
|
||||
uri: id,
|
||||
name,
|
||||
items: [],
|
||||
addedAt: new Date(),
|
||||
lastPlayedAt: new Date(),
|
||||
parentCollection
|
||||
});
|
||||
this.saveCollections();
|
||||
Spicetify.showNotification("Collection created");
|
||||
return id;
|
||||
}
|
||||
deleteCollection(uri) {
|
||||
this._collections = this._collections.filter((collection) => collection.uri !== uri);
|
||||
this.saveCollections();
|
||||
Spicetify.showNotification("Collection deleted");
|
||||
}
|
||||
deleteCollectionAndAlbums(uri) {
|
||||
const collection = this.getCollection(uri);
|
||||
if (!collection)
|
||||
return;
|
||||
for (const album of collection.items) {
|
||||
Spicetify.Platform.LibraryAPI.remove({ uris: [album] });
|
||||
}
|
||||
this.deleteCollection(uri);
|
||||
}
|
||||
async addAlbumToCollection(collectionUri, albumUri) {
|
||||
const collection = this.getCollection(collectionUri);
|
||||
if (!collection)
|
||||
return;
|
||||
await Spicetify.Platform.LibraryAPI.add({ uris: [albumUri] });
|
||||
collection.items.push(albumUri);
|
||||
this.saveCollections();
|
||||
Spicetify.showNotification("Album added to collection");
|
||||
this.syncCollection(collectionUri);
|
||||
}
|
||||
removeAlbumFromCollection(collectionUri, albumUri) {
|
||||
const collection = this.getCollection(collectionUri);
|
||||
if (!collection)
|
||||
return;
|
||||
collection.items = collection.items.filter((item) => item !== albumUri);
|
||||
this.saveCollections();
|
||||
Spicetify.showNotification("Album removed from collection");
|
||||
this.syncCollection(collectionUri);
|
||||
}
|
||||
getCollectionsWithAlbum(albumUri) {
|
||||
return this._collections.filter((collection) => {
|
||||
return collection.items.some((item) => item === albumUri);
|
||||
});
|
||||
}
|
||||
renameCollection(uri, name) {
|
||||
const collection = this.getCollection(uri);
|
||||
if (!collection)
|
||||
return;
|
||||
collection.name = name;
|
||||
this.saveCollections();
|
||||
Spicetify.showNotification("Collection renamed");
|
||||
}
|
||||
setCollectionImage(uri, url) {
|
||||
const collection = this.getCollection(uri);
|
||||
if (!collection)
|
||||
return;
|
||||
collection.image = url;
|
||||
this.saveCollections();
|
||||
Spicetify.showNotification("Collection image set");
|
||||
}
|
||||
removeCollectionImage(uri) {
|
||||
const collection = this.getCollection(uri);
|
||||
if (!collection)
|
||||
return;
|
||||
collection.image = void 0;
|
||||
this.saveCollections();
|
||||
Spicetify.showNotification("Collection image removed");
|
||||
}
|
||||
};
|
||||
var CollectionsWrapper2 = _CollectionsWrapper;
|
||||
__publicField(CollectionsWrapper2, "INSTANCE", new _CollectionsWrapper());
|
||||
window.CollectionsWrapper = CollectionsWrapper2.INSTANCE;
|
||||
|
||||
// src/extensions/folder_image_wrapper.ts
|
||||
var _FolderImageWrapper = class extends EventTarget {
|
||||
_folderImages;
|
||||
constructor() {
|
||||
super();
|
||||
this._folderImages = JSON.parse(localStorage.getItem("library:folderImages") || "{}");
|
||||
}
|
||||
getFolderImage(uri) {
|
||||
return this._folderImages[uri];
|
||||
}
|
||||
getFolderImages() {
|
||||
return this._folderImages;
|
||||
}
|
||||
setFolderImage({ uri, url }) {
|
||||
this._folderImages[uri] = url;
|
||||
this.saveFolderImages();
|
||||
Spicetify.showNotification("Folder image updated");
|
||||
}
|
||||
removeFolderImage(uri) {
|
||||
delete this._folderImages[uri];
|
||||
this.saveFolderImages();
|
||||
Spicetify.showNotification("Folder image removed");
|
||||
}
|
||||
saveFolderImages() {
|
||||
this.dispatchEvent(new CustomEvent("update", { detail: this._folderImages }));
|
||||
localStorage.setItem("library:folderImages", JSON.stringify(this._folderImages));
|
||||
}
|
||||
};
|
||||
var FolderImageWrapper2 = _FolderImageWrapper;
|
||||
__publicField(FolderImageWrapper2, "INSTANCE", new _FolderImageWrapper());
|
||||
window.FolderImageWrapper = FolderImageWrapper2.INSTANCE;
|
||||
|
||||
// src/extensions/extension.tsx
|
||||
var styleLink = document.createElement("link");
|
||||
styleLink.rel = "stylesheet";
|
||||
styleLink.href = "/spicetify-routes-library.css";
|
||||
document.head.appendChild(styleLink);
|
||||
var setCardSize = (size) => {
|
||||
document.documentElement.style.setProperty("--library-card-size", `${size}px`);
|
||||
};
|
||||
var setSearchBarSize = (enlarged) => {
|
||||
const size = enlarged ? 300 : 200;
|
||||
document.documentElement.style.setProperty("--library-searchbar-size", `${size}px`);
|
||||
};
|
||||
var FolderImage = ({ url }) => {
|
||||
return /* @__PURE__ */ import_react10.default.createElement("img", {
|
||||
alt: "Folder Image",
|
||||
"aria-hidden": "true",
|
||||
draggable: "false",
|
||||
loading: "eager",
|
||||
src: url,
|
||||
className: "main-image-image x-entityImage-image main-image-loading main-image-loaded"
|
||||
});
|
||||
};
|
||||
var FolderPlaceholder = () => {
|
||||
return /* @__PURE__ */ import_react10.default.createElement("div", {
|
||||
className: "x-entityImage-imagePlaceholder"
|
||||
}, /* @__PURE__ */ import_react10.default.createElement("svg", {
|
||||
"data-encore-id": "icon",
|
||||
role: "img",
|
||||
"aria-hidden": "true",
|
||||
className: "Svg-sc-ytk21e-0 Svg-img-icon-medium",
|
||||
viewBox: "0 0 24 24"
|
||||
}, /* @__PURE__ */ import_react10.default.createElement("path", {
|
||||
d: "M1 4a2 2 0 0 1 2-2h5.155a3 3 0 0 1 2.598 1.5l.866 1.5H21a2 2 0 0 1 2 2v13a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V4zm7.155 0H3v16h18V7H10.464L9.021 4.5a1 1 0 0 0-.866-.5z"
|
||||
})));
|
||||
};
|
||||
var SpicetifyLibrary = class {
|
||||
ConfigWrapper = new config_wrapper_default(
|
||||
[
|
||||
{
|
||||
name: "Card Size",
|
||||
key: "cardSize",
|
||||
type: "slider",
|
||||
min: 100,
|
||||
max: 200,
|
||||
step: 0.05,
|
||||
def: 180,
|
||||
callback: setCardSize
|
||||
},
|
||||
{
|
||||
name: "Extend Search Bar",
|
||||
key: "extendSearchBar",
|
||||
type: "toggle",
|
||||
def: false,
|
||||
callback: setSearchBarSize
|
||||
},
|
||||
{
|
||||
name: "Playlists Page",
|
||||
key: "show-playlists",
|
||||
type: "toggle",
|
||||
def: true,
|
||||
sectionHeader: "Pages"
|
||||
},
|
||||
{ name: "Albums Page", key: "show-albums", type: "toggle", def: true },
|
||||
{ name: "Collections Page", key: "show-collections", type: "toggle", def: true },
|
||||
{ name: "Artists Page", key: "show-artists", type: "toggle", def: true },
|
||||
{ name: "Shows Page", key: "show-shows", type: "toggle", def: true }
|
||||
],
|
||||
"library"
|
||||
);
|
||||
};
|
||||
window.SpicetifyLibrary = new SpicetifyLibrary();
|
||||
(function wait() {
|
||||
const { LocalStorageAPI } = Spicetify.Platform;
|
||||
if (!LocalStorageAPI) {
|
||||
setTimeout(wait, 100);
|
||||
return;
|
||||
}
|
||||
main(LocalStorageAPI);
|
||||
})();
|
||||
function main(LocalStorageAPI) {
|
||||
const isAlbum = (props) => props.uri?.includes("album");
|
||||
const isArtist = (props) => props.uri?.includes("artist");
|
||||
Spicetify.ContextMenuV2.registerItem(/* @__PURE__ */ import_react10.default.createElement(album_menu_item_default, null), isAlbum);
|
||||
Spicetify.ContextMenuV2.registerItem(/* @__PURE__ */ import_react10.default.createElement(artist_menu_item_default, null), isArtist);
|
||||
Spicetify.Platform.LibraryAPI.getEvents()._emitter.addListener("update", () => CollectionsWrapper.cleanCollections());
|
||||
function injectFolderImages() {
|
||||
const rootlist = document.querySelector(".main-rootlist-wrapper > div:nth-child(2)");
|
||||
if (!rootlist)
|
||||
return setTimeout(injectFolderImages, 100);
|
||||
setTimeout(() => {
|
||||
for (const el of Array.from(rootlist.children)) {
|
||||
const uri = el.querySelector("[aria-labelledby]")?.getAttribute("aria-labelledby")?.slice(14);
|
||||
if (uri?.includes("folder")) {
|
||||
const imageBox = el.querySelector(".x-entityImage-imageContainer");
|
||||
if (!imageBox)
|
||||
return;
|
||||
const imageUrl = FolderImageWrapper.getFolderImage(uri);
|
||||
if (!imageUrl)
|
||||
import_react_dom.default.render(/* @__PURE__ */ import_react10.default.createElement(FolderPlaceholder, null), imageBox);
|
||||
else
|
||||
import_react_dom.default.render(/* @__PURE__ */ import_react10.default.createElement(FolderImage, {
|
||||
url: imageUrl
|
||||
}), imageBox);
|
||||
}
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
injectFolderImages();
|
||||
FolderImageWrapper.addEventListener("update", injectFolderImages);
|
||||
function injectYLXButtons() {
|
||||
const ylx_filter = document.querySelector(".main-yourLibraryX-libraryRootlist > .main-yourLibraryX-libraryFilter");
|
||||
if (!ylx_filter) {
|
||||
return setTimeout(injectYLXButtons, 100);
|
||||
}
|
||||
injectFiltersButton(ylx_filter);
|
||||
injectCollapseButton(ylx_filter);
|
||||
}
|
||||
function injectFiltersButton(ylx_filter) {
|
||||
const toggleFiltersButton = document.createElement("span");
|
||||
toggleFiltersButton.classList.add("toggle-filters-button");
|
||||
ylx_filter.appendChild(toggleFiltersButton);
|
||||
import_react_dom.default.render(/* @__PURE__ */ import_react10.default.createElement(toggle_filters_default, null), toggleFiltersButton);
|
||||
}
|
||||
function injectCollapseButton(ylx_filter) {
|
||||
const collapseButton = document.createElement("span");
|
||||
collapseButton.classList.add("collapse-button");
|
||||
ylx_filter.appendChild(collapseButton);
|
||||
import_react_dom.default.render(
|
||||
/* @__PURE__ */ import_react10.default.createElement(Spicetify.ReactComponent.TooltipWrapper, {
|
||||
label: "Collapse Sidebar",
|
||||
placement: "top"
|
||||
}, /* @__PURE__ */ import_react10.default.createElement(collapse_button_default, null)),
|
||||
collapseButton
|
||||
);
|
||||
}
|
||||
const state = LocalStorageAPI.getItem("ylx-sidebar-state");
|
||||
if (state === 0)
|
||||
injectYLXButtons();
|
||||
LocalStorageAPI.getEvents()._emitter.addListener("update", (e) => {
|
||||
const { key, value } = e.data;
|
||||
if (key === "ylx-sidebar-state" && value === 0) {
|
||||
injectFolderImages();
|
||||
injectYLXButtons();
|
||||
}
|
||||
if (key === "ylx-sidebar-state" && value === 1) {
|
||||
injectFolderImages();
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
})();
|
||||
69
config/spicetify/CustomApps/library/folder_image_wrapper.js
Normal file
69
config/spicetify/CustomApps/library/folder_image_wrapper.js
Normal file
@@ -0,0 +1,69 @@
|
||||
(async function() {
|
||||
while (!Spicetify.React || !Spicetify.ReactDOM) {
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
}
|
||||
"use strict";
|
||||
var library = (() => {
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var __publicField = (obj, key, value) => {
|
||||
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
||||
return value;
|
||||
};
|
||||
|
||||
// src/extensions/folder_image_wrapper.ts
|
||||
var folder_image_wrapper_exports = {};
|
||||
__export(folder_image_wrapper_exports, {
|
||||
default: () => folder_image_wrapper_default
|
||||
});
|
||||
var _FolderImageWrapper = class extends EventTarget {
|
||||
_folderImages;
|
||||
constructor() {
|
||||
super();
|
||||
this._folderImages = JSON.parse(localStorage.getItem("library:folderImages") || "{}");
|
||||
}
|
||||
getFolderImage(uri) {
|
||||
return this._folderImages[uri];
|
||||
}
|
||||
getFolderImages() {
|
||||
return this._folderImages;
|
||||
}
|
||||
setFolderImage({ uri, url }) {
|
||||
this._folderImages[uri] = url;
|
||||
this.saveFolderImages();
|
||||
Spicetify.showNotification("Folder image updated");
|
||||
}
|
||||
removeFolderImage(uri) {
|
||||
delete this._folderImages[uri];
|
||||
this.saveFolderImages();
|
||||
Spicetify.showNotification("Folder image removed");
|
||||
}
|
||||
saveFolderImages() {
|
||||
this.dispatchEvent(new CustomEvent("update", { detail: this._folderImages }));
|
||||
localStorage.setItem("library:folderImages", JSON.stringify(this._folderImages));
|
||||
}
|
||||
};
|
||||
var FolderImageWrapper = _FolderImageWrapper;
|
||||
__publicField(FolderImageWrapper, "INSTANCE", new _FolderImageWrapper());
|
||||
window.FolderImageWrapper = FolderImageWrapper.INSTANCE;
|
||||
var folder_image_wrapper_default = FolderImageWrapper;
|
||||
return __toCommonJS(folder_image_wrapper_exports);
|
||||
})();
|
||||
|
||||
})();
|
||||
1615
config/spicetify/CustomApps/library/index.js
Normal file
1615
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"
|
||||
]
|
||||
}
|
||||
379
config/spicetify/CustomApps/library/style.css
Normal file
379
config/spicetify/CustomApps/library/style.css
Normal file
@@ -0,0 +1,379 @@
|
||||
/* ../../../AppData/Local/Temp/tmp-4464-wbZ6O1BKhuot/19178f16f304/navBar.module.css */
|
||||
.navBar-module__topBarHeaderItem___piw4C_library {
|
||||
-webkit-app-region: no-drag;
|
||||
display: inline-block;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.navBar-module__topBarHeaderItemLink___xA4uv_library {
|
||||
margin: 0 8px 0 0;
|
||||
}
|
||||
.navBar-module__topBarActive___XhWpm_library {
|
||||
background-color: var(--spice-tab-active);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.navBar-module__topBarHeaderItemLink___xA4uv_library {
|
||||
border-radius: 4px;
|
||||
color: var(--spice-text);
|
||||
display: inline-block;
|
||||
margin: 0 8px;
|
||||
padding: 8px 16px;
|
||||
position: relative;
|
||||
text-decoration: none !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
.navBar-module__topBarNav___qWGeZ_library {
|
||||
-webkit-app-region: drag;
|
||||
pointer-events: none;
|
||||
width: 100%;
|
||||
}
|
||||
.navBar-module__topBarHeaderItem___piw4C_library .navBar-module__optionsMenuDropBox___pzfNI_library {
|
||||
color: var(--spice-text);
|
||||
border: 0;
|
||||
max-width: 150px;
|
||||
height: 42px;
|
||||
padding: 0 30px 0 12px;
|
||||
background-color: initial;
|
||||
cursor: pointer;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
.navBar-module__topBarHeaderItem___piw4C_library .navBar-module__optionsMenuDropBox___pzfNI_library svg {
|
||||
position: absolute;
|
||||
margin-left: 8px;
|
||||
}
|
||||
div.navBar-module__topBarHeaderItemLink___xA4uv_library {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* ../../../AppData/Local/Temp/tmp-4464-wbZ6O1BKhuot/19178f16dc80/app.css */
|
||||
:root {
|
||||
--library-card-size: 180px;
|
||||
--library-searchbar-size: 200px;
|
||||
}
|
||||
#library-app .header-right .x-filterBox-expandedOrHasFilter .x-filterBox-filterInput {
|
||||
width: var(--library-searchbar-size);
|
||||
}
|
||||
#library-app .grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(var(--library-card-size), 1fr)) !important;
|
||||
}
|
||||
#library-app .main-card-cardContainer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
#library-app .load-more-card {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
#library-app .load-more-card div:nth-child(2) {
|
||||
text-align: center;
|
||||
font-size: var(--encore-text-size-base);
|
||||
}
|
||||
#library-app .load-more-card div:first-child {
|
||||
fill: var(--text-subdued);
|
||||
width: 80%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
#library-app .load-more-card:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
.text-input-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18px;
|
||||
}
|
||||
.text-input-form .text-input {
|
||||
background: rgba(var(--spice-rgb-selected-row), 0.1);
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
color: var(--spice-text);
|
||||
font-family: inherit;
|
||||
font-size: 14px;
|
||||
height: 32px;
|
||||
padding: 0 12px;
|
||||
width: 100%;
|
||||
}
|
||||
.text-input-form .text-input:focus {
|
||||
background-color: var(--spice-tab-active);
|
||||
border: 1px solid var(--spice-button-disabled);
|
||||
outline: none;
|
||||
}
|
||||
.text-input-form button {
|
||||
align-self: end;
|
||||
}
|
||||
|
||||
/* ../../../AppData/Local/Temp/tmp-4464-wbZ6O1BKhuot/19178f16ee21/external.css */
|
||||
body:not(.show-ylx-filters) .main-yourLibraryX-filterArea:not(:has(> .main-yourLibraryX-libraryFilter)),
|
||||
.main-yourLibraryX-header:not(:has(> .main-yourLibraryX-headerContent > .main-yourLibraryX-collapseButton > button:nth-child(2))),
|
||||
.main-yourLibraryX-collapseButton > button:first-child,
|
||||
.main-yourLibraryX-headerContent > button {
|
||||
display: none;
|
||||
}
|
||||
.main-yourLibraryX-library {
|
||||
padding-top: 8px;
|
||||
}
|
||||
.main-yourLibraryX-header {
|
||||
margin-top: -8px;
|
||||
}
|
||||
.main-yourLibraryX-libraryFilter .main-yourLibraryX-librarySortWrapper button span:first-child,
|
||||
.main-yourLibraryX-libraryFilter span[role=presentation] span[role=presentation] button span:first-child {
|
||||
display: none;
|
||||
}
|
||||
.main-yourLibraryX-libraryFilter .main-yourLibraryX-librarySortWrapper,
|
||||
.main-yourLibraryX-libraryFilter span[role=presentation] {
|
||||
margin-left: auto;
|
||||
}
|
||||
.toggle-filters-button > button:after,
|
||||
.collapse-button > button:after,
|
||||
.expand-button > button:after {
|
||||
display: none;
|
||||
}
|
||||
.expand-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
z-index: 1;
|
||||
margin-left: 5px;
|
||||
}
|
||||
.expand-button > button {
|
||||
visibility: hidden;
|
||||
margin: 0 5px;
|
||||
}
|
||||
li.main-yourLibraryX-navItem[data-id="/library"] {
|
||||
display: flex;
|
||||
}
|
||||
li.main-yourLibraryX-navItem[data-id="/library"] > a {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.main-yourLibraryX-libraryFilter .toggle-filters-button > button,
|
||||
.main-yourLibraryX-libraryFilter .collapse-button > button,
|
||||
.main-yourLibraryX-libraryFilter .main-yourLibraryX-librarySortWrapper > button,
|
||||
.main-yourLibraryX-libraryFilter span[role=presentation] span[role=presentation] > button {
|
||||
padding: 0;
|
||||
}
|
||||
.main-yourLibraryX-libraryFilter .toggle-filters-button,
|
||||
.main-yourLibraryX-libraryFilter .collapse-button,
|
||||
.main-yourLibraryX-libraryFilter .main-yourLibraryX-librarySortWrapper,
|
||||
.main-yourLibraryX-libraryFilter span[role=presentation] span[role=presentation] {
|
||||
display: flex;
|
||||
flex-basis: 32px;
|
||||
justify-content: center;
|
||||
min-width: 24px;
|
||||
flex-shrink: 10;
|
||||
}
|
||||
.main-yourLibraryX-libraryFilter .main-yourLibraryX-librarySortWrapper > button > span:nth-child(2),
|
||||
.main-yourLibraryX-libraryFilter span[role=presentation] span[role=presentation] > button > span:nth-child(2) {
|
||||
margin: 0;
|
||||
}
|
||||
.LayoutResizer__resize-bar {
|
||||
opacity: 0 !important;
|
||||
}
|
||||
.Root__nav-bar .x-filterBox-expandedOrHasFilter .x-filterBox-filterInput {
|
||||
width: 100%;
|
||||
}
|
||||
.Root__nav-bar .x-filterBox-expandedOrHasFilter {
|
||||
flex-grow: 1;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.Root__nav-bar:has(> .LayoutResizer__resize-bar:hover) .expand-button > button,
|
||||
.Root__nav-bar .expand-button:hover > button {
|
||||
visibility: visible;
|
||||
height: 32px;
|
||||
background-color: black;
|
||||
}
|
||||
.text-input-form .Button-small-buttonPrimary {
|
||||
box-sizing: border-box;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 700;
|
||||
font-family: var(--font-family, CircularSp, CircularSp-Arab, CircularSp-Hebr, CircularSp-Cyrl, CircularSp-Grek, CircularSp-Deva, var(--fallback-fonts, sans-serif));
|
||||
background-color: transparent;
|
||||
border: 0px;
|
||||
border-radius: 9999px;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
text-transform: none;
|
||||
touch-action: manipulation;
|
||||
transition-duration: 33ms;
|
||||
transition-property:
|
||||
background-color,
|
||||
border-color,
|
||||
color,
|
||||
box-shadow,
|
||||
filter,
|
||||
transform;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
vertical-align: middle;
|
||||
transform: translate3d(0px, 0px, 0px);
|
||||
padding: 0px;
|
||||
min-inline-size: 0px;
|
||||
}
|
||||
.text-input-form .ButtonInner-small {
|
||||
box-sizing: border-box;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
position: relative;
|
||||
background-color: var(--background-base, #1ed760);
|
||||
color: var(--text-base, #000000);
|
||||
display: flex;
|
||||
border-radius: 9999px;
|
||||
font-size: inherit;
|
||||
min-block-size: var(--encore-control-size-smaller, 32px);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-block-start: var(--encore-spacing-tighter-4, 4px);
|
||||
padding-block-end: var(--encore-spacing-tighter-4, 4px);
|
||||
padding-inline-start: var(--encore-spacing-base, 16px);
|
||||
padding-inline-end: var(--encore-spacing-base, 16px);
|
||||
}
|
||||
.text-input-form .Button-small-buttonPrimary:hover .ButtonInner-sc-14ud5tc-0,
|
||||
.text-input-form .Button-small-buttonPrimary:hover .ButtonFocus-sc-2hq6ey-0 {
|
||||
transform: scale(1.04);
|
||||
}
|
||||
|
||||
/* ../../../AppData/Local/Temp/tmp-4464-wbZ6O1BKhuot/19178f16f0a2/config_modal.css */
|
||||
.config-container {
|
||||
gap: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.config-container .section-header {
|
||||
box-sizing: border-box;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
margin-block: 0px;
|
||||
font-size: 1.125rem;
|
||||
font-weight: 700;
|
||||
color: var(--spice-text);
|
||||
}
|
||||
.config-container .col.description {
|
||||
box-sizing: border-box;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
margin-block: 0px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 400;
|
||||
color: var(--spice-subtext);
|
||||
}
|
||||
.config-container .disabled {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.config-container .text-input {
|
||||
background: rgba(var(--spice-rgb-selected-row), 0.1);
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
color: var(--spice-text);
|
||||
font-family: inherit;
|
||||
font-size: 14px;
|
||||
height: 32px;
|
||||
padding: 0 12px;
|
||||
width: 100%;
|
||||
}
|
||||
.config-container .text-input:focus {
|
||||
background-color: var(--spice-tab-active);
|
||||
border: 1px solid var(--spice-button-disabled);
|
||||
outline: none;
|
||||
}
|
||||
.config-container .dropdown-input {
|
||||
background-color: var(--spice-tab-active);
|
||||
border: 0;
|
||||
border-radius: 4px;
|
||||
color: rgba(var(--spice-rgb-selected-row), 0.7);
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
height: 32px;
|
||||
letter-spacing: 0.24px;
|
||||
line-height: 20px;
|
||||
padding: 0 32px 0 12px;
|
||||
width: 100%;
|
||||
}
|
||||
.config-container .tooltip-icon {
|
||||
float: right;
|
||||
margin-left: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 22px;
|
||||
fill: var(--spice-subtext);
|
||||
}
|
||||
.config-container .tooltip-icon:hover {
|
||||
fill: var(--spice-text);
|
||||
}
|
||||
.config-container .tooltip {
|
||||
text-align: center;
|
||||
}
|
||||
.config-container .setting-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.config-container .playback-progressbar {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
/* ../../../AppData/Local/Temp/tmp-4464-wbZ6O1BKhuot/19178f16f163/shared.css */
|
||||
.grid {
|
||||
--grid-gap: 24px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)) !important;
|
||||
}
|
||||
.loadingWrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 60vh;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
.loadingWrapper .status-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
fill: currentColor;
|
||||
}
|
||||
.page-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
.badge {
|
||||
position: absolute;
|
||||
top: 3%;
|
||||
left: 3%;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
border-radius: 50%;
|
||||
background-color: rgb(65, 110, 170);
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
.page-header {
|
||||
align-content: space-between;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 16px 0;
|
||||
}
|
||||
.page-header .header-right,
|
||||
.page-header .header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.page-header .header-right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.page-header .header-left {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.new-update {
|
||||
background-color: var(--spice-player);
|
||||
color: var(--spice-text);
|
||||
border-radius: 8px;
|
||||
padding: 2px 12px;
|
||||
margin: 0 24px;
|
||||
border: 0px;
|
||||
}
|
||||
35
config/spicetify/CustomApps/marketplace/README.md
Normal file
35
config/spicetify/CustomApps/marketplace/README.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Spicetify Marketplace
|
||||
|
||||
<p>
|
||||
<a href="https://github.com/spicetify/spicetify-marketplace/releases/latest">
|
||||
<img src="https://img.shields.io/github/v/release/spicetify/spicetify-marketplace?include_prereleases">
|
||||
</a>
|
||||
<a href="https://github.com/spicetify/spicetify-marketplace/releases">
|
||||
<img src="https://img.shields.io/github/downloads/spicetify/spicetify-marketplace/total.svg">
|
||||
</a>
|
||||
<a href="https://github.com/spicetify/spicetify-marketplace/issues?q=is%3Aissue+is%3Aclosed">
|
||||
<img src="https://img.shields.io/github/issues-closed/spicetify/spicetify-marketplace">
|
||||
</a>
|
||||
<a href="https://github.com/spicetify/spicetify-marketplace/commits/main">
|
||||
<img src="https://img.shields.io/github/commit-activity/m/spicetify/spicetify-marketplace">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
Customize your Spotify client directly from within [Spicetify](https://github.com/spicetify/spicetify-cli)!
|
||||
|
||||
Marketplace allows you to **browse, download, and install** extensions, themes, and CSS snippets with ease. You can also browse custom apps, but will need to do some manual installation to get them working.
|
||||
|
||||
Made with [Spicetify Creator](https://github.com/spicetify/spicetify-creator)
|
||||
|
||||
Head to the [wiki](https://github.com/spicetify/spicetify-marketplace/wiki) to get started!
|
||||
|
||||
---
|
||||
|
||||
## Links
|
||||
- [Overview](https://github.com/spicetify/spicetify-marketplace/wiki)
|
||||
- [Installation](https://github.com/spicetify/spicetify-marketplace/wiki/Installation)
|
||||
- [Publishing to Marketplace](https://github.com/spicetify/spicetify-marketplace/wiki/Publishing-to-Marketplace)
|
||||
- [Contributions](https://github.com/spicetify/spicetify-marketplace/wiki/Contributions)
|
||||
- [Development](https://github.com/spicetify/spicetify-marketplace/wiki/Development)
|
||||
- [Translating/Localizing Marketplace](https://github.com/spicetify/spicetify-marketplace/wiki/Localizing-Marketplace)
|
||||
|
||||
10
config/spicetify/CustomApps/marketplace/extension.js
Normal file
10
config/spicetify/CustomApps/marketplace/extension.js
Normal file
File diff suppressed because one or more lines are too long
13
config/spicetify/CustomApps/marketplace/index.js
Normal file
13
config/spicetify/CustomApps/marketplace/index.js
Normal file
File diff suppressed because one or more lines are too long
12
config/spicetify/CustomApps/marketplace/manifest.json
Normal file
12
config/spicetify/CustomApps/marketplace/manifest.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": {
|
||||
"en": "Marketplace",
|
||||
"ru": "Маркетплейс"
|
||||
},
|
||||
"icon": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"100%\" height=\"100%\" viewBox=\"0 0 76.465 68.262\"><path d=\"M151.909 72.923v6.5h10.097l8.663 44.567h48.968v-6.5h-43.61l-1.2-6.172h42.974l10.35-33.91h-59.915l-.872-4.485H151.91zm17.59 10.984h49.867l-6.393 20.91h-39.409l-4.064-20.91zm5.626 44.11a6.5 6.5 0 0 0-6.5 6.5 6.5 6.5 0 0 0 6.5 6.501 6.5 6.5 0 0 0 6.5-6.5 6.5 6.5 0 0 0-6.5-6.5zm38.274 0a6.5 6.5 0 0 0-6.5 6.5 6.5 6.5 0 0 0 6.5 6.501 6.5 6.5 0 0 0 6.5-6.5 6.5 6.5 0 0 0-6.5-6.5z\" style=\"fill:currentColor;stroke-width:.264583\" transform=\"translate(-151.909 -72.923)\"/></svg>\n",
|
||||
"active-icon": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"100%\" height=\"100%\" viewBox=\"0 0 76.465 68.262\"><path d=\"M151.909 72.923v6.5h10.097l8.663 44.567h48.968v-6.5h-43.61l-1.2-6.172h42.974l10.35-33.91h-59.915l-.872-4.485H151.91zm23.216 55.095a6.5 6.5 0 0 0-6.5 6.5 6.5 6.5 0 0 0 6.5 6.5 6.5 6.5 0 0 0 6.5-6.5 6.5 6.5 0 0 0-6.5-6.5zm38.274 0a6.5 6.5 0 0 0-6.5 6.5 6.5 6.5 0 0 0 6.5 6.5 6.5 6.5 0 0 0 6.5-6.5 6.5 6.5 0 0 0-6.5-6.5z\" style=\"fill:currentColor;stroke-width:.264583\" transform=\"translate(-151.909 -72.923)\"/></svg>\n",
|
||||
"subfiles": [],
|
||||
"subfiles_extension": [
|
||||
"extension.js"
|
||||
]
|
||||
}
|
||||
1
config/spicetify/CustomApps/marketplace/style.css
Normal file
1
config/spicetify/CustomApps/marketplace/style.css
Normal file
File diff suppressed because one or more lines are too long
66
config/spicetify/CustomApps/stats/cache.js
Normal file
66
config/spicetify/CustomApps/stats/cache.js
Normal file
@@ -0,0 +1,66 @@
|
||||
(async function() {
|
||||
while (!Spicetify.React || !Spicetify.ReactDOM) {
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
}
|
||||
"use strict";
|
||||
var stats = (() => {
|
||||
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/cache.ts
|
||||
var cache_exports = {};
|
||||
__export(cache_exports, {
|
||||
batchCacher: () => batchCacher,
|
||||
cacher: () => cacher,
|
||||
invalidator: () => invalidator,
|
||||
set: () => set
|
||||
});
|
||||
var cache = {};
|
||||
var set = (key, value) => {
|
||||
cache[key] = value;
|
||||
};
|
||||
var invalidate = (key) => {
|
||||
delete cache[key];
|
||||
};
|
||||
var cacher = (cb) => {
|
||||
return async ({ queryKey }) => {
|
||||
const key = queryKey.join("-");
|
||||
if (cache[key])
|
||||
return cache[key];
|
||||
const result = await cb();
|
||||
set(key, result);
|
||||
return result;
|
||||
};
|
||||
};
|
||||
var batchCacher = (prefix, cb) => {
|
||||
return async (ids) => {
|
||||
const cached = ids.map((id) => cache[`${prefix}-${id}`]);
|
||||
const uncached = ids.filter((_, index) => !cached[index]);
|
||||
const results = await cb(uncached);
|
||||
results.forEach((result, index) => set(`${prefix}-${uncached[index]}`, result));
|
||||
return [...cached.filter(Boolean), ...results];
|
||||
};
|
||||
};
|
||||
var invalidator = (queryKey, refetch) => {
|
||||
invalidate(queryKey.join("-"));
|
||||
refetch();
|
||||
};
|
||||
return __toCommonJS(cache_exports);
|
||||
})();
|
||||
|
||||
})();
|
||||
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;
|
||||
}
|
||||
6538
config/spicetify/CustomApps/stats/extension.js
Normal file
6538
config/spicetify/CustomApps/stats/extension.js
Normal file
File diff suppressed because it is too large
Load Diff
7635
config/spicetify/CustomApps/stats/index.js
Normal file
7635
config/spicetify/CustomApps/stats/index.js
Normal file
File diff suppressed because it is too large
Load Diff
10
config/spicetify/CustomApps/stats/manifest.json
Normal file
10
config/spicetify/CustomApps/stats/manifest.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "Statistics",
|
||||
"icon": "<svg xmlns=\"http://www.w3.org/2000/svg\"\r\n\t width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" style=\"fill:currentColor\" >\r\n<path d=\"M3,23L3,23c-0.55,0-1-0.45-1-1v-9c0-0.55,0.45-1,1-1h0c0.55,0,1,0.45,1,1v9C4,22.55,3.55,23,3,23z\"/>\r\n<path d=\"M9,23L9,23c-0.55,0-1-0.45-1-1V9c0-0.55,0.45-1,1-1h0c0.55,0,1,0.45,1,1v13C10,22.55,9.55,23,9,23z\"/>\r\n<path d=\"M15,23L15,23c-0.55,0-1-0.45-1-1V11c0-0.55,0.45-1,1-1h0c0.55,0,1,0.45,1,1v11C16,22.55,15.55,23,15,23z\"/>\r\n<path d=\"M21,23L21,23c-0.55,0-1-0.45-1-1V8c0-0.55,0.45-1,1-1h0c0.55,0,1,0.45,1,1v14C22,22.55,21.55,23,21,23z\"/>\r\n<path d=\"M22.86,1.5c-0.28-0.48-0.89-0.64-1.37-0.37l-6.54,3.74l-6-2.33C8.52,2.38,8.22,2.41,7.88,2.73L1.29,7.71\r\n\tC0.86,8.05,0.8,8.68,1.15,9.11s0.98,0.49,1.41,0.14l6.2-4.65l5.69,2.2C15,6.99,15.3,6.96,15.83,6.68l6.67-3.82\r\n\tC22.98,2.59,23.14,1.97,22.86,1.5z\"/>\r\n</svg>\r\n",
|
||||
"active-icon": "<svg xmlns=\"http://www.w3.org/2000/svg\"\r\n\t width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" style=\"fill:currentColor\" >\r\n<path d=\"M3,23L3,23c-0.55,0-1-0.45-1-1v-9c0-0.55,0.45-1,1-1h0c0.55,0,1,0.45,1,1v9C4,22.55,3.55,23,3,23z\"/>\r\n<path d=\"M9,23L9,23c-0.55,0-1-0.45-1-1V9c0-0.55,0.45-1,1-1h0c0.55,0,1,0.45,1,1v13C10,22.55,9.55,23,9,23z\"/>\r\n<path d=\"M15,23L15,23c-0.55,0-1-0.45-1-1V11c0-0.55,0.45-1,1-1h0c0.55,0,1,0.45,1,1v11C16,22.55,15.55,23,15,23z\"/>\r\n<path d=\"M21,23L21,23c-0.55,0-1-0.45-1-1V8c0-0.55,0.45-1,1-1h0c0.55,0,1,0.45,1,1v14C22,22.55,21.55,23,21,23z\"/>\r\n<path d=\"M22.86,1.5c-0.28-0.48-0.89-0.64-1.37-0.37l-6.54,3.74l-6-2.33C8.52,2.38,8.22,2.41,7.88,2.73L1.29,7.71\r\n\tC0.86,8.05,0.8,8.68,1.15,9.11s0.98,0.49,1.41,0.14l6.2-4.65l5.69,2.2C15,6.99,15.3,6.96,15.83,6.68l6.67-3.82\r\n\tC22.98,2.59,23.14,1.97,22.86,1.5z\"/>\r\n</svg>\r\n",
|
||||
"subfiles": [],
|
||||
"subfiles_extension": [
|
||||
"cache.js",
|
||||
"extension.js"
|
||||
]
|
||||
}
|
||||
315
config/spicetify/CustomApps/stats/style.css
Normal file
315
config/spicetify/CustomApps/stats/style.css
Normal file
@@ -0,0 +1,315 @@
|
||||
/* ../../../AppData/Local/Temp/tmp-2132-C1IXGV9b5FdF/191733900bf3/navBar.module.css */
|
||||
.navBar-module__topBarHeaderItem___piw4C_stats {
|
||||
-webkit-app-region: no-drag;
|
||||
display: inline-block;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.navBar-module__topBarHeaderItemLink___xA4uv_stats {
|
||||
margin: 0 8px 0 0;
|
||||
}
|
||||
.navBar-module__topBarActive___XhWpm_stats {
|
||||
background-color: var(--spice-tab-active);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.navBar-module__topBarHeaderItemLink___xA4uv_stats {
|
||||
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_stats {
|
||||
-webkit-app-region: drag;
|
||||
pointer-events: none;
|
||||
width: 100%;
|
||||
}
|
||||
.navBar-module__topBarHeaderItem___piw4C_stats .navBar-module__optionsMenuDropBox___pzfNI_stats {
|
||||
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_stats .navBar-module__optionsMenuDropBox___pzfNI_stats svg {
|
||||
position: absolute;
|
||||
margin-left: 8px;
|
||||
}
|
||||
div.navBar-module__topBarHeaderItemLink___xA4uv_stats {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* ../../../AppData/Local/Temp/tmp-2132-C1IXGV9b5FdF/1917338fb020/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;
|
||||
}
|
||||
#stats-app .grid:nth-child(2) {
|
||||
margin-top: 24px;
|
||||
}
|
||||
#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);
|
||||
}
|
||||
#stats-app [data-scroll=end] {
|
||||
-webkit-mask-image: linear-gradient(to right, transparent, black 10%);
|
||||
mask-image: linear-gradient(to right, transparent, black 10%);
|
||||
}
|
||||
#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-app .stats-libraryOverview {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
align-items: center;
|
||||
}
|
||||
#stats-app .stats-trackPageTitle {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
align-items: center;
|
||||
}
|
||||
#stats-app .stats-scrollButton {
|
||||
width: 40px;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin-right: 5px;
|
||||
background-color: var(--spice-player);
|
||||
color: var(--spice-subtext);
|
||||
}
|
||||
#stats-app .stats-scrollButton:hover {
|
||||
background-color: var(--spice-card);
|
||||
color: var(--spice-text);
|
||||
}
|
||||
#stats-app .stats-tracklistHeader > div {
|
||||
display: flex;
|
||||
-webkit-app-region: no-drag;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
}
|
||||
#stats-app .stats-make-playlist-button {
|
||||
margin-inline-start: 12px;
|
||||
}
|
||||
#stats-app .stats-genreCard {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
background: var(--spice-player);
|
||||
position: relative;
|
||||
}
|
||||
#stats-app .stats-genreRow {
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
#stats-app .stats-genreRowFill {
|
||||
background: var(--spice-button);
|
||||
height: 100%;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
#stats-app .stats-genreText {
|
||||
color: var(--spice-player);
|
||||
font-size: 0.875rem;
|
||||
margin-left: 7px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
#stats-app .stats-genreValue {
|
||||
color: var(--spice-text);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
#stats-app .stats-genreCard + .stats-gridInlineSection {
|
||||
margin-top: 3px;
|
||||
}
|
||||
#stats-app .main-trackList-rowHeartButton,
|
||||
#stats-app .main-trackList-rowMoreButton {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
#stats-app .main-trackList-rowPlayPauseIcon {
|
||||
fill: currentColor;
|
||||
}
|
||||
#stats-app .extend-button {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
bottom: 20px;
|
||||
font-size: 14px;
|
||||
color: var(--spice-subtext);
|
||||
}
|
||||
#stats-app .extend-button:hover {
|
||||
color: var(--spice-text);
|
||||
}
|
||||
#stats-app .main-card-cardContainer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.GenericModal[aria-label="Playlist Stats"] .main-embedWidgetGenerator-container {
|
||||
width: 80vw;
|
||||
height: 80vh;
|
||||
background-color: var(--spice-main);
|
||||
}
|
||||
.GenericModal[aria-label="Playlist Stats"] .main-shelf-title {
|
||||
color: var(--spice-text);
|
||||
}
|
||||
|
||||
/* ../../../AppData/Local/Temp/tmp-2132-C1IXGV9b5FdF/1917338ffe31/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-2132-C1IXGV9b5FdF/1917338fffd2/shared.css */
|
||||
.grid {
|
||||
--grid-gap: 24px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)) !important;
|
||||
}
|
||||
.loadingWrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 60vh;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
.loadingWrapper .status-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
fill: currentColor;
|
||||
}
|
||||
.page-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
.badge {
|
||||
position: absolute;
|
||||
top: 3%;
|
||||
left: 3%;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
border-radius: 50%;
|
||||
background-color: rgb(65, 110, 170);
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
.page-header {
|
||||
align-content: space-between;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 16px 0;
|
||||
}
|
||||
.page-header .header-right,
|
||||
.page-header .header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.page-header .header-right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.page-header .header-left {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.new-update {
|
||||
background-color: var(--spice-player);
|
||||
color: var(--spice-text);
|
||||
border-radius: 8px;
|
||||
padding: 2px 12px;
|
||||
margin: 0 24px;
|
||||
border: 0px;
|
||||
}
|
||||
Reference in New Issue
Block a user