├── .eslintrc.js
├── .gitattributes
├── .github
├── CODEOWNERS
├── ISSUE_TEMPLATE
│ ├── bug_report.yml
│ ├── config.yml
│ ├── feature_request.yml
│ ├── malicious_extension_report.yml
│ └── new_translation.yml
├── dependabot.yml
└── workflows
│ ├── auto_release.yml
│ ├── build.yml
│ ├── codeql-analysis.yml
│ ├── lint.yml
│ ├── lintpr.yml
│ └── push.yml
├── .gitignore
├── .husky
└── pre-commit
├── .npmrc
├── .nvmrc
├── .vscode
├── launch.json
└── settings.json
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── biome.json
├── package.json
├── pnpm-lock.yaml
├── resources
├── assets
│ └── snippets
│ │ ├── Circular-Shadow-fix-for-album-art.png
│ │ ├── Dynamic-Left-Sidebar.gif
│ │ ├── Fix-Listen-Together-Button.png
│ │ ├── Hide-Audiobooks-Button.png
│ │ ├── Hide-Full-Screen-Button.png
│ │ ├── Hide-Made-For-YOU.png
│ │ ├── Hide-Mini-Player-Button.png
│ │ ├── Hide-Podcast-Button.png
│ │ ├── Hide-Recently-Played.png
│ │ ├── Hide-Whats-New-Button.png
│ │ ├── More-Visible-Unplayable-Tracks.png
│ │ ├── Queue-Top-Side-Panel.png
│ │ ├── Remove-Duplicated-Fullscreen-Button.png
│ │ ├── Remove-Playlist-Cover.png
│ │ ├── Rounded-Thicker-Bars.png
│ │ ├── Small-Video-Button.png
│ │ ├── Spinning-CD-Cover-Art.jpg
│ │ ├── always-show-forward.png
│ │ ├── amogus-dancing.png
│ │ ├── auto-hide-friends.png
│ │ ├── be-square.png
│ │ ├── better-lyrics-style.png
│ │ ├── centered-lyrics.png
│ │ ├── circular-album-art.png
│ │ ├── dark-lyrics.png
│ │ ├── declutter-now-playing-bar.png
│ │ ├── default-progress-bar.png
│ │ ├── disable-recommendations.png
│ │ ├── duck.png
│ │ ├── duotone-album-art.png
│ │ ├── fix-DJ-icon.png
│ │ ├── fix-liked-button.png
│ │ ├── fix-liked-icon.png
│ │ ├── fix-listening-on.png
│ │ ├── fix-main-view-width.png
│ │ ├── fix-now-playing-icon.png
│ │ ├── fix-playlist-and-folder-position.png
│ │ ├── fix-playlist-hover.png
│ │ ├── fix-progress-bar.png
│ │ ├── fixed-episodes-icon.png
│ │ ├── fullscreen-hide-next-up.png
│ │ ├── fullscreen-hide-playing-from.png
│ │ ├── hide-download-button.png
│ │ ├── hide-friend-activity-button.png
│ │ ├── hide-likedSongs-card.png
│ │ ├── hide-lyrics-button.png
│ │ ├── hide-now-playing-view-button.png
│ │ ├── hide-play-count.png
│ │ ├── hide-playing-gif.png
│ │ ├── hide-profile-username.png
│ │ ├── hide-recent-searches.png
│ │ ├── hide-sidebar-scrollbar.png
│ │ ├── hover-panels.png
│ │ ├── kirby_progress_bar.png
│ │ ├── left-aligned-heart-icons.png
│ │ ├── modern-scrollbar.png
│ │ ├── new-hover-panel.gif
│ │ ├── nyan-cat-progress-bar.gif
│ │ ├── oneko.png
│ │ ├── pointer.png
│ │ ├── pokemon-adventure.png
│ │ ├── pretty-lyrics.png
│ │ ├── remove-connect-bar.png
│ │ ├── remove-ep-likes.png
│ │ ├── remove-gradient.png
│ │ ├── remove-popular.png
│ │ ├── remove-recently-played.png
│ │ ├── remove-the-artists-and-credits-sections-from-the-sidebar.png
│ │ ├── remove-top-spacing.png
│ │ ├── right-cover-art.png
│ │ ├── rotating-coverart.png
│ │ ├── rounded-buttons.png
│ │ ├── rounded-images.png
│ │ ├── rounded-now-playing.png
│ │ ├── smaller-right-sidebar-cover.png
│ │ ├── smooth-playlist-reveal-gradient.png
│ │ ├── smooth-progress-bar.png
│ │ ├── sonic-dancing.png
│ │ ├── switch-sidebars.png
│ │ ├── thicker-bars.png
│ │ ├── thicker-sticky-list-icons.png
│ │ └── thin-library-sidebar-rows.png
├── blacklist.json
├── color.ini
├── install.ps1
├── install.sh
└── snippets.json
├── src
├── app.tsx
├── assets
│ ├── icon-filled.svg
│ ├── icon-source.svg
│ └── icon.svg
├── components
│ ├── Button.tsx
│ ├── Card
│ │ ├── AuthorsDiv.tsx
│ │ ├── Card.tsx
│ │ └── TagsDiv.tsx
│ ├── Grid.tsx
│ ├── Icons
│ │ ├── DownloadIcon.tsx
│ │ ├── GitHubIcon.tsx
│ │ ├── LoadMoreIcon.tsx
│ │ ├── LoadingIcon.tsx
│ │ ├── SettingsIcon.tsx
│ │ ├── ThemeDeveloperToolsIcon.tsx
│ │ ├── TooltipIcon.tsx
│ │ └── TrashIcon.tsx
│ ├── Modals
│ │ ├── BackupModal
│ │ │ └── index.tsx
│ │ ├── Reload
│ │ │ └── index.tsx
│ │ ├── Settings
│ │ │ ├── ConfigRow.tsx
│ │ │ ├── DnDList.tsx
│ │ │ └── index.tsx
│ │ ├── Snippet
│ │ │ └── index.tsx
│ │ ├── ThemeDevTools
│ │ │ └── index.tsx
│ │ └── Update
│ │ │ └── index.tsx
│ ├── ReadmePage.tsx
│ ├── Sortbox.tsx
│ ├── TabBar.tsx
│ └── Toggle.tsx
├── constants.ts
├── extensions
│ └── extension.tsx
├── logic
│ ├── FetchRemotes.ts
│ ├── LaunchModals.tsx
│ └── Utils.ts
├── resources
│ └── locales
│ │ ├── ar.json
│ │ ├── ca.json
│ │ ├── de-DE.json
│ │ ├── en-US.json
│ │ ├── en.json
│ │ ├── es.json
│ │ ├── et.json
│ │ ├── fr.json
│ │ ├── index.ts
│ │ ├── it.json
│ │ ├── ja.json
│ │ ├── ko.json
│ │ ├── pl.json
│ │ ├── pt-BR.json
│ │ ├── ru.json
│ │ ├── uk.json
│ │ ├── zh-CN.json
│ │ └── zh-TW.json
├── settings.json
├── styles
│ ├── app.module.scss
│ ├── components
│ │ ├── _add-snippet-modal.scss
│ │ ├── _backup.scss
│ │ ├── _card.scss
│ │ ├── _code-editors.scss
│ │ ├── _devtools.scss
│ │ ├── _dropdown.scss
│ │ ├── _fixes.scss
│ │ ├── _grid.scss
│ │ ├── _readme-pages.scss
│ │ ├── _reload-modal.scss
│ │ ├── _settings.scss
│ │ ├── _update-modal.scss
│ │ └── prismjs-themes
│ │ │ └── prism-tomorrow.scss
│ ├── constants.scss
│ ├── modules
│ │ ├── button.module.scss
│ │ └── toggle.module.scss
│ └── styles.scss
└── types
│ ├── css-modules.d.ts
│ ├── marketplace-types.d.ts
│ └── spicetify.d.ts
└── tsconfig.json
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | ignorePatterns: ["node_modules", "dist", ".eslintrc.js", "spicetify.d.ts"],
3 | env: {
4 | browser: true,
5 | es2022: true,
6 | },
7 | extends: [
8 | "eslint:recommended",
9 | "plugin:react/recommended",
10 | "plugin:@typescript-eslint/recommended",
11 | ],
12 | parser: "@typescript-eslint/parser",
13 | parserOptions: {
14 | sourceType: "module",
15 | project: ["tsconfig.json"],
16 | },
17 | plugins: ["react", "@typescript-eslint"],
18 | rules: {
19 | indent: ["error", 2],
20 | "linebreak-style": ["error", "unix"],
21 | quotes: ["error", "double", { allowTemplateLiterals: true }],
22 | semi: ["error", "always"],
23 | "comma-dangle": ["error", "always-multiline"],
24 | "no-var": "error",
25 | "space-before-blocks": "error",
26 | "comma-spacing": ["error", { before: false, after: true }],
27 | "no-trailing-spaces": "error",
28 | "keyword-spacing": "error",
29 | "no-multiple-empty-lines": ["error", { max: 1 }],
30 | "object-curly-spacing": ["error", "always"],
31 | "key-spacing": ["error", { beforeColon: false, afterColon: true }],
32 | },
33 | settings: {
34 | react: {
35 | // This is what Spotify uses
36 | version: "17.0.2",
37 | },
38 | },
39 | };
40 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | dist/* linguist-vendored
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @spicetify/marketplace
2 |
3 | /src/resources/locales/en-US.json @CharlieS1103
4 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
1 | name: 🐛 Bug report
2 | description: Report errors or unexpected behavior
3 | labels: [🐛 bug]
4 | body:
5 | - type: checkboxes
6 | attributes:
7 | label: 🔍 Is there already an issue for your problem?
8 | description: Please make sure you are not creating an already submitted Issue. Check closed issues as well, because your issue may have already been fixed.
9 | options:
10 | - label: I have checked older issues, open and closed
11 | required: true
12 | - type: textarea
13 | attributes:
14 | label: ℹ Environment / Computer Info
15 | description: Please provide the details of the system Spicetify is running on.
16 | value: |
17 | - Spotify version:
18 | - Spicetify version:
19 | placeholder: |
20 | - Spotify version: Spotify for macOS (Apple Silicon) 1.1.77.643.g3c4c6fc6
21 | - Spicetify version: 2.8.5
22 | render: Markdown
23 | validations:
24 | required: true
25 | - type: textarea
26 | attributes:
27 | label: 📝 Description
28 | description: List steps to reproduce the error and details on what happens and what you expected to happen.
29 | validations:
30 | required: true
31 | - type: textarea
32 | attributes:
33 | label: 📸 Screenshots
34 | description: Place any screenshots of the issue here if needed
35 | validations:
36 | required: false
37 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: ✏️ Spicetify Docs
4 | url: https://spicetify.app/
5 | about: Read our documentation before creating an issue.
6 | - name: ⛑️ Spicetify Discord Server
7 | url: https://discord.gg/spicetify-842219447716151306
8 | about: Issues are not for support. If you need it - join our Discord server.
9 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------
1 | name: ✨ Feature Request
2 | description: Request a new feature or enhancement
3 | labels: [✨ feature]
4 | body:
5 | - type: textarea
6 | id: description
7 | attributes:
8 | label: 📝 Provide a description of the new feature
9 | description: What is the expected behavior of the proposed feature? What is the scenario this would be used?
10 | validations:
11 | required: true
12 |
13 | - type: textarea
14 | id: additional-information
15 | attributes:
16 | label: ➕ Additional Information
17 | description: Give us some additional information on the feature request like proposed solutions, links, screenshots, etc.
18 |
19 | - type: markdown
20 | attributes:
21 | value: If you'd like to see this feature implemented, add a 👍 reaction to this post.
22 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/malicious_extension_report.yml:
--------------------------------------------------------------------------------
1 | name: 🦠 Malicious extension report
2 | description: Submit any malicious extensions and we can add them to the denylist.
3 | labels: [🦠 malicious extension]
4 | body:
5 | - type: input
6 | id: extension-link
7 | attributes:
8 | label: 🔗 Extension Link
9 | description: A link to the GitHub repo of the extension in question.
10 | validations:
11 | required: true
12 | - type: textarea
13 | id: extension-description
14 | attributes:
15 | label: 📝 Describe the Issue
16 | description: What does the extension do that is unsafe/malicious?
17 | validations:
18 | required: true
19 | - type: textarea
20 | attributes:
21 | label: 📸 Screenshots
22 | description: If applicable, add screenshots to help explain your problem.
23 | validations:
24 | required: false
25 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/new_translation.yml:
--------------------------------------------------------------------------------
1 | name: 🌎 New translation
2 | description: Add a new translation
3 | labels: [🌎 i18n]
4 | body:
5 | - type: input
6 | id: language
7 | attributes:
8 | label: What language are you adding?
9 | description: What it's called in the Spotify settings
10 | placeholder: e.g. Latin American Spanish
11 | validations:
12 | required: true
13 |
14 | - type: textarea
15 | id: translation-json
16 | attributes:
17 | label: Translation JSON
18 | placeholder: Copy src/resources/locales/en.json and paste updated version here
19 | value: "```json\nCopy src/resources/locales/en.json and paste updated version here\n```"
20 | validations:
21 | required: true
22 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "npm"
4 | directory: "/"
5 | schedule:
6 | interval: "monthly"
7 | ignore:
8 | # ignore all patch updates
9 | - dependency-name: "*"
10 | update-types: ["version-update:semver-patch"]
11 |
--------------------------------------------------------------------------------
/.github/workflows/auto_release.yml:
--------------------------------------------------------------------------------
1 | name: Auto-release
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | check_version:
10 | name: Check the package version
11 | runs-on: ubuntu-latest
12 | outputs:
13 | version_changed: ${{ steps.check_result.outputs.version_changed }}
14 | new_version: ${{ steps.check_result.outputs.new_version }}
15 | steps:
16 | - name: Check out the code
17 | uses: actions/checkout@v4
18 | with:
19 | fetch-depth: 0
20 | - name: Install Node.js
21 | uses: actions/setup-node@v4
22 | with:
23 | node-version-file: .nvmrc
24 | - name: Check the package version
25 | id: check_result
26 | run: |
27 | latest_release_version="$(git for-each-ref --sort=-creatordate --count 1 --format="%(refname:short)" "refs/tags/*")"
28 | current_version="v$(node -p "require('./package.json').version")"
29 | if [[ "$latest_release_version" == "$current_version" ]]; then
30 | echo "version_changed=false" >> $GITHUB_OUTPUT
31 | else
32 | echo "version_changed=true" >> $GITHUB_OUTPUT
33 | echo "new_version=$current_version" >> $GITHUB_OUTPUT
34 | fi
35 |
36 | create_release:
37 | name: Create a release
38 | needs: check_version
39 | if: needs.check_version.outputs.version_changed == 'true'
40 | runs-on: ubuntu-latest
41 | permissions:
42 | contents: write
43 | steps:
44 | - name: Wait for "Push" workflow
45 | uses: lewagon/wait-on-check-action@v1.3.1
46 | with:
47 | ref: ${{ github.ref }}
48 | check-name: Push
49 | repo-token: ${{ secrets.GITHUB_TOKEN }}
50 | wait-interval: 10
51 | - name: Download target branch
52 |
53 | run: curl -s -o marketplace.zip https://codeload.github.com/${{ github.repository }}/zip/refs/heads/dist
54 | - name: Publish the release
55 | uses: softprops/action-gh-release@v1
56 | with:
57 | tag_name: ${{ needs.check_version.outputs.new_version }}
58 | generate_release_notes: true
59 | files: marketplace.zip
60 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | pull_request:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | build:
10 | name: build
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v4
14 |
15 | - name: Install pnpm
16 | uses: pnpm/action-setup@v2
17 | with:
18 | run_install: false
19 |
20 | - name: Install Node.js (v20)
21 | uses: actions/setup-node@v3
22 | with:
23 | node-version: 20
24 | cache: pnpm
25 | cache-dependency-path: pnpm-lock.yaml
26 |
27 | - name: Install dependencies
28 | run: pnpm install --frozen-lockfile
29 |
30 | - name: Build
31 | run: pnpm build:local
32 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ "main" ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ "main" ]
20 | schedule:
21 | - cron: '17 23 * * 2'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 | permissions:
28 | actions: read
29 | contents: read
30 | security-events: write
31 |
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | language: [ 'javascript' ]
36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
38 |
39 | steps:
40 | - name: Checkout repository
41 | uses: actions/checkout@v3
42 |
43 | # Initializes the CodeQL tools for scanning.
44 | - name: Initialize CodeQL
45 | uses: github/codeql-action/init@v2
46 | with:
47 | languages: ${{ matrix.language }}
48 | # If you wish to specify custom queries, you can do so here or in a config file.
49 | # By default, queries listed here will override any specified in a config file.
50 | # Prefix the list here with "+" to use these queries and those in the config file.
51 |
52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
53 | # queries: security-extended,security-and-quality
54 |
55 |
56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
57 | # If this step fails, then you should remove it and run the build manually (see below)
58 | - name: Autobuild
59 | uses: github/codeql-action/autobuild@v2
60 |
61 | # ℹ️ Command-line programs to run using the OS shell.
62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
63 |
64 | # If the Autobuild fails above, remove it and uncomment the following three lines.
65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
66 |
67 | # - run: |
68 | # echo "Run, Build Application using script"
69 | # ./location_of_script_within_repo/buildscript.sh
70 |
71 | - name: Perform CodeQL Analysis
72 | uses: github/codeql-action/analyze@v2
73 | with:
74 | category: "/language:${{matrix.language}}"
75 |
--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
1 | name: Lint code
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | branches:
9 | - main
10 |
11 | jobs:
12 | lint:
13 | name: lint
14 | runs-on: ubuntu-latest
15 | steps:
16 | - name: Check out the code
17 | uses: actions/checkout@v4
18 |
19 | - name: Install pnpm
20 | uses: pnpm/action-setup@v2
21 | with:
22 | run_install: false
23 |
24 | - name: Install Node.js
25 | uses: actions/setup-node@v4
26 | with:
27 | node-version-file: .nvmrc
28 | cache: pnpm
29 | cache-dependency-path: pnpm-lock.yaml
30 |
31 | - name: Install dependencies
32 | run: pnpm install --frozen-lockfile
33 |
34 | - name: Run Lint Checks
35 | run: pnpm lint:ci
36 |
--------------------------------------------------------------------------------
/.github/workflows/lintpr.yml:
--------------------------------------------------------------------------------
1 | name: Lint Pull Request
2 |
3 | on:
4 | pull_request_target:
5 | types:
6 | - opened
7 | - edited
8 | - synchronize
9 |
10 | jobs:
11 | lintpr:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: Lint pull request title
15 | uses: amannn/action-semantic-pull-request@v5
16 | env:
17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
18 | with:
19 | disallowScopes: |
20 | [A-Z]+
21 | subjectPattern: ^(?![A-Z]).+$
22 |
--------------------------------------------------------------------------------
/.github/workflows/push.yml:
--------------------------------------------------------------------------------
1 | name: Push new version to dist branch
2 | on:
3 | push:
4 | branches:
5 | - main
6 |
7 | jobs:
8 | deploy:
9 | name: Push
10 | runs-on: ubuntu-latest
11 | env:
12 | TARGET_BRANCH: dist
13 | permissions:
14 | contents: write
15 | steps:
16 | - name: Check out the code
17 | uses: actions/checkout@v4
18 | with:
19 | fetch-depth: 0
20 |
21 | - name: Install pnpm
22 | uses: pnpm/action-setup@v2
23 | with:
24 | run_install: false
25 |
26 | - name: Install Node.js
27 | uses: actions/setup-node@v4
28 | with:
29 | node-version-file: .nvmrc
30 | cache: pnpm
31 | cache-dependency-path: pnpm-lock.yaml
32 |
33 | - name: Configure Git
34 | run: |
35 | git config --global user.name "Github Actions"
36 | git config --global user.email "github-actions[bot]@users.noreply.github.com"
37 | git pull origin HEAD
38 | git worktree add -B $TARGET_BRANCH dist origin/$TARGET_BRANCH
39 |
40 | - name: Install Dependencies and Build
41 | run: |
42 | pnpm install --frozen-lockfile
43 | pnpm build:prod
44 |
45 | - name: Push New Version
46 | run: |
47 | export SHORT_SHA=$(git rev-parse --short HEAD)
48 | cd dist
49 | git add .
50 | git diff-index --quiet HEAD || git commit -am "Update to output generated at $SHORT_SHA"
51 | git push origin $TARGET_BRANCH
52 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.toptal.com/developers/gitignore/api/node
3 | # Edit at https://www.toptal.com/developers/gitignore?templates=node
4 |
5 | ### Node ###
6 | # Logs
7 | logs
8 | *.log
9 | npm-debug.log*
10 | yarn-debug.log*
11 | yarn-error.log*
12 | lerna-debug.log*
13 | .pnpm-debug.log*
14 |
15 | # Diagnostic reports (https://nodejs.org/api/report.html)
16 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
17 |
18 | # Runtime data
19 | pids
20 | *.pid
21 | *.seed
22 | *.pid.lock
23 |
24 | # Directory for instrumented libs generated by jscoverage/JSCover
25 | lib-cov
26 |
27 | # Coverage directory used by tools like istanbul
28 | coverage
29 | *.lcov
30 |
31 | # nyc test coverage
32 | .nyc_output
33 |
34 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
35 | .grunt
36 |
37 | # Bower dependency directory (https://bower.io/)
38 | bower_components
39 |
40 | # node-waf configuration
41 | .lock-wscript
42 |
43 | # Compiled binary addons (https://nodejs.org/api/addons.html)
44 | build/Release
45 |
46 | # Dependency directories
47 | node_modules/
48 | jspm_packages/
49 |
50 | # Snowpack dependency directory (https://snowpack.dev/)
51 | web_modules/
52 |
53 | # TypeScript cache
54 | *.tsbuildinfo
55 |
56 | # Optional npm cache directory
57 | .npm
58 |
59 | # Optional eslint cache
60 | .eslintcache
61 |
62 | # Optional stylelint cache
63 | .stylelintcache
64 |
65 | # Microbundle cache
66 | .rpt2_cache/
67 | .rts2_cache_cjs/
68 | .rts2_cache_es/
69 | .rts2_cache_umd/
70 |
71 | # Optional REPL history
72 | .node_repl_history
73 |
74 | # Output of 'npm pack'
75 | *.tgz
76 |
77 | # Yarn Integrity file
78 | .yarn-integrity
79 |
80 | # dotenv environment variable files
81 | .env
82 | .env.development.local
83 | .env.test.local
84 | .env.production.local
85 | .env.local
86 |
87 | # parcel-bundler cache (https://parceljs.org/)
88 | .cache
89 | .parcel-cache
90 |
91 | # Next.js build output
92 | .next
93 | out
94 |
95 | # Nuxt.js build / generate output
96 | .nuxt
97 |
98 | # Gatsby files
99 | .cache/
100 | # Comment in the public line in if your project uses Gatsby and not Next.js
101 | # https://nextjs.org/blog/next-9-1#public-directory-support
102 | # public
103 |
104 | # vuepress build output
105 | .vuepress/dist
106 |
107 | # vuepress v2.x temp and cache directory
108 | .temp
109 |
110 | # Docusaurus cache and generated files
111 | .docusaurus
112 |
113 | # Serverless directories
114 | .serverless/
115 |
116 | # FuseBox cache
117 | .fusebox/
118 |
119 | # DynamoDB Local files
120 | .dynamodb/
121 |
122 | # TernJS port file
123 | .tern-port
124 |
125 | # Stores VSCode versions used for testing VSCode extensions
126 | .vscode-test
127 |
128 | # yarn v2
129 | .yarn/cache
130 | .yarn/unplugged
131 | .yarn/build-state.yml
132 | .yarn/install-state.gz
133 | .pnp.*
134 |
135 | ### Node Patch ###
136 | # Serverless Webpack directories
137 | .webpack/
138 |
139 | # Optional stylelint cache
140 |
141 | # SvelteKit build / generate output
142 | .svelte-kit
143 |
144 | # End of https://www.toptal.com/developers/gitignore/api/node
145 |
146 | # macOS cache junk
147 | .DS_Store
148 |
149 | # build folder
150 | dist/
151 |
152 | # vectornator files for snippet previews
153 | *.vectornator
154 |
155 | # package managers
156 | package-lock.json
157 | yarn.lock
158 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | pnpm lint
2 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 20
2 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "node",
9 | "request": "launch",
10 | "name": "pnpm run watch",
11 | "runtimeExecutable": "pnpm",
12 | "runtimeArgs": [
13 | "run",
14 | "watch"
15 | ],
16 | "internalConsoleOptions": "openOnSessionStart"
17 | },
18 | {
19 | "type": "node",
20 | "request": "launch",
21 | "name": "spicetify watch -a",
22 | "runtimeExecutable": "spicetify",
23 | "runtimeArgs": [
24 | "watch",
25 | "-a"
26 | ],
27 | "internalConsoleOptions": "openOnSessionStart"
28 | }
29 | ],
30 | "compounds": [
31 | {
32 | "name": "Watch app build and Spicetify",
33 | "configurations": ["pnpm run watch", "spicetify watch -a"]
34 | }
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.codeActionsOnSave": {
3 | "source.organizeImports.biome": "always",
4 | "quickfix.biome": "always",
5 | "source.organizeImports": "never",
6 | "source.fixAll": "always"
7 | },
8 | "editor.defaultFormatter": "biomejs.biome"
9 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 CharlieS1103 + theRealPadster
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Spicetify Marketplace
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Customize your Spotify client directly from within [Spicetify](https://github.com/spicetify/cli)!
19 |
20 | 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.
21 |
22 | Made with [Spicetify Creator](https://github.com/spicetify/spicetify-creator)
23 |
24 | Head to the [wiki](https://github.com/spicetify/marketplace/wiki) to get started!
25 |
26 | ---
27 |
28 | ## Links
29 | - [Overview](https://github.com/spicetify/marketplace/wiki)
30 | - [Installation](https://github.com/spicetify/marketplace/wiki/Installation)
31 | - [Publishing to Marketplace](https://github.com/spicetify/marketplace/wiki/Publishing-to-Marketplace)
32 | - [Contributions](https://github.com/spicetify/marketplace/wiki/Contributions)
33 | - [Development](https://github.com/spicetify/marketplace/wiki/Development)
34 | - [Translating/Localizing Marketplace](https://github.com/spicetify/marketplace/wiki/Localizing-Marketplace)
35 |
36 |
--------------------------------------------------------------------------------
/biome.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://biomejs.dev/schemas/1.7.3/schema.json",
3 | "organizeImports": {
4 | "enabled": true
5 | },
6 | "linter": {
7 | "enabled": true,
8 | "rules": {
9 | "recommended": true,
10 | "suspicious": {
11 | "noExplicitAny": "off"
12 | },
13 | "a11y": {
14 | "useKeyWithClickEvents": "off",
15 | "useButtonType": "off"
16 | },
17 | "security": {
18 | "noDangerouslySetInnerHtml": "off"
19 | }
20 | }
21 | },
22 | "formatter": {
23 | "enabled": true,
24 | "formatWithErrors": true,
25 | "indentStyle": "space",
26 | "indentWidth": 2,
27 | "lineWidth": 150
28 | },
29 | "javascript": {
30 | "formatter": {
31 | "trailingCommas": "none",
32 | "arrowParentheses": "always"
33 | }
34 | },
35 | "vcs": {
36 | "enabled": true,
37 | "clientKind": "git",
38 | "useIgnoreFile": true,
39 | "defaultBranch": "main"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "marketplace",
3 | "version": "1.0.5",
4 | "homepage": "https://github.com/spicetify/marketplace",
5 | "repository": "github:spicetify/marketplace",
6 | "bugs": {
7 | "url": "https://github.com/spicetify/marketplace/issues"
8 | },
9 | "packageManager": "pnpm@9.0.6",
10 | "scripts": {
11 | "build": "spicetify-creator",
12 | "build:local": "spicetify-creator --out=dist --minify",
13 | "build:prod": "pnpm build:local && pnpm copy:docs",
14 | "copy:docs": "copyfiles README.md dist/",
15 | "lint": "biome check --write ./src",
16 | "lint:ci": "biome check *",
17 | "type-check": "tsc --noEmit",
18 | "watch": "spicetify-creator --watch",
19 | "prepare": "husky install",
20 | "update-types": "curl -s -o src/types/spicetify.d.ts https://raw.githubusercontent.com/spicetify/cli/main/globals.d.ts"
21 | },
22 | "engines": {
23 | "yarn": "please-use-pnpm",
24 | "npm": "please-use-pnpm",
25 | "pnpm": ">=8",
26 | "node": ">=20"
27 | },
28 | "devDependencies": {
29 | "@types/chroma-js": "^2.4.4",
30 | "@types/react": "18.2.0",
31 | "@types/react-dom": "18.2.0",
32 | "@types/semver": "^7.5.8",
33 | "copyfiles": "^2.4.1",
34 | "husky": "^9.1.4",
35 | "spicetify-creator": "^1.0.17",
36 | "typescript": "^5.8.2"
37 | },
38 | "dependencies": {
39 | "@biomejs/biome": "^1.8.3",
40 | "chroma-js": "^3.1.1",
41 | "i18next": "^25.2.1",
42 | "i18next-browser-languagedetector": "^8.1.0",
43 | "prismjs": "^1.30.0",
44 | "react-beautiful-dnd": "^13.1.1",
45 | "react-dropdown": "^1.11.0",
46 | "react-i18next": "^15.5.1",
47 | "react-simple-code-editor": "^0.14.1",
48 | "semver": "^7.7.0"
49 | },
50 | "private": true
51 | }
52 |
--------------------------------------------------------------------------------
/resources/assets/snippets/Circular-Shadow-fix-for-album-art.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/Circular-Shadow-fix-for-album-art.png
--------------------------------------------------------------------------------
/resources/assets/snippets/Dynamic-Left-Sidebar.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/Dynamic-Left-Sidebar.gif
--------------------------------------------------------------------------------
/resources/assets/snippets/Fix-Listen-Together-Button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/Fix-Listen-Together-Button.png
--------------------------------------------------------------------------------
/resources/assets/snippets/Hide-Audiobooks-Button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/Hide-Audiobooks-Button.png
--------------------------------------------------------------------------------
/resources/assets/snippets/Hide-Full-Screen-Button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/Hide-Full-Screen-Button.png
--------------------------------------------------------------------------------
/resources/assets/snippets/Hide-Made-For-YOU.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/Hide-Made-For-YOU.png
--------------------------------------------------------------------------------
/resources/assets/snippets/Hide-Mini-Player-Button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/Hide-Mini-Player-Button.png
--------------------------------------------------------------------------------
/resources/assets/snippets/Hide-Podcast-Button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/Hide-Podcast-Button.png
--------------------------------------------------------------------------------
/resources/assets/snippets/Hide-Recently-Played.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/Hide-Recently-Played.png
--------------------------------------------------------------------------------
/resources/assets/snippets/Hide-Whats-New-Button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/Hide-Whats-New-Button.png
--------------------------------------------------------------------------------
/resources/assets/snippets/More-Visible-Unplayable-Tracks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/More-Visible-Unplayable-Tracks.png
--------------------------------------------------------------------------------
/resources/assets/snippets/Queue-Top-Side-Panel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/Queue-Top-Side-Panel.png
--------------------------------------------------------------------------------
/resources/assets/snippets/Remove-Duplicated-Fullscreen-Button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/Remove-Duplicated-Fullscreen-Button.png
--------------------------------------------------------------------------------
/resources/assets/snippets/Remove-Playlist-Cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/Remove-Playlist-Cover.png
--------------------------------------------------------------------------------
/resources/assets/snippets/Rounded-Thicker-Bars.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/Rounded-Thicker-Bars.png
--------------------------------------------------------------------------------
/resources/assets/snippets/Small-Video-Button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/Small-Video-Button.png
--------------------------------------------------------------------------------
/resources/assets/snippets/Spinning-CD-Cover-Art.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/Spinning-CD-Cover-Art.jpg
--------------------------------------------------------------------------------
/resources/assets/snippets/always-show-forward.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/always-show-forward.png
--------------------------------------------------------------------------------
/resources/assets/snippets/amogus-dancing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/amogus-dancing.png
--------------------------------------------------------------------------------
/resources/assets/snippets/auto-hide-friends.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/auto-hide-friends.png
--------------------------------------------------------------------------------
/resources/assets/snippets/be-square.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/be-square.png
--------------------------------------------------------------------------------
/resources/assets/snippets/better-lyrics-style.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/better-lyrics-style.png
--------------------------------------------------------------------------------
/resources/assets/snippets/centered-lyrics.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/centered-lyrics.png
--------------------------------------------------------------------------------
/resources/assets/snippets/circular-album-art.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/circular-album-art.png
--------------------------------------------------------------------------------
/resources/assets/snippets/dark-lyrics.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/dark-lyrics.png
--------------------------------------------------------------------------------
/resources/assets/snippets/declutter-now-playing-bar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/declutter-now-playing-bar.png
--------------------------------------------------------------------------------
/resources/assets/snippets/default-progress-bar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/default-progress-bar.png
--------------------------------------------------------------------------------
/resources/assets/snippets/disable-recommendations.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/disable-recommendations.png
--------------------------------------------------------------------------------
/resources/assets/snippets/duck.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/duck.png
--------------------------------------------------------------------------------
/resources/assets/snippets/duotone-album-art.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/duotone-album-art.png
--------------------------------------------------------------------------------
/resources/assets/snippets/fix-DJ-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/fix-DJ-icon.png
--------------------------------------------------------------------------------
/resources/assets/snippets/fix-liked-button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/fix-liked-button.png
--------------------------------------------------------------------------------
/resources/assets/snippets/fix-liked-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/fix-liked-icon.png
--------------------------------------------------------------------------------
/resources/assets/snippets/fix-listening-on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/fix-listening-on.png
--------------------------------------------------------------------------------
/resources/assets/snippets/fix-main-view-width.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/fix-main-view-width.png
--------------------------------------------------------------------------------
/resources/assets/snippets/fix-now-playing-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/fix-now-playing-icon.png
--------------------------------------------------------------------------------
/resources/assets/snippets/fix-playlist-and-folder-position.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/fix-playlist-and-folder-position.png
--------------------------------------------------------------------------------
/resources/assets/snippets/fix-playlist-hover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/fix-playlist-hover.png
--------------------------------------------------------------------------------
/resources/assets/snippets/fix-progress-bar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/fix-progress-bar.png
--------------------------------------------------------------------------------
/resources/assets/snippets/fixed-episodes-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/fixed-episodes-icon.png
--------------------------------------------------------------------------------
/resources/assets/snippets/fullscreen-hide-next-up.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/fullscreen-hide-next-up.png
--------------------------------------------------------------------------------
/resources/assets/snippets/fullscreen-hide-playing-from.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/fullscreen-hide-playing-from.png
--------------------------------------------------------------------------------
/resources/assets/snippets/hide-download-button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/hide-download-button.png
--------------------------------------------------------------------------------
/resources/assets/snippets/hide-friend-activity-button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/hide-friend-activity-button.png
--------------------------------------------------------------------------------
/resources/assets/snippets/hide-likedSongs-card.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/hide-likedSongs-card.png
--------------------------------------------------------------------------------
/resources/assets/snippets/hide-lyrics-button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/hide-lyrics-button.png
--------------------------------------------------------------------------------
/resources/assets/snippets/hide-now-playing-view-button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/hide-now-playing-view-button.png
--------------------------------------------------------------------------------
/resources/assets/snippets/hide-play-count.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/hide-play-count.png
--------------------------------------------------------------------------------
/resources/assets/snippets/hide-playing-gif.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/hide-playing-gif.png
--------------------------------------------------------------------------------
/resources/assets/snippets/hide-profile-username.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/hide-profile-username.png
--------------------------------------------------------------------------------
/resources/assets/snippets/hide-recent-searches.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/hide-recent-searches.png
--------------------------------------------------------------------------------
/resources/assets/snippets/hide-sidebar-scrollbar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/hide-sidebar-scrollbar.png
--------------------------------------------------------------------------------
/resources/assets/snippets/hover-panels.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/hover-panels.png
--------------------------------------------------------------------------------
/resources/assets/snippets/kirby_progress_bar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/kirby_progress_bar.png
--------------------------------------------------------------------------------
/resources/assets/snippets/left-aligned-heart-icons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/left-aligned-heart-icons.png
--------------------------------------------------------------------------------
/resources/assets/snippets/modern-scrollbar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/modern-scrollbar.png
--------------------------------------------------------------------------------
/resources/assets/snippets/new-hover-panel.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/new-hover-panel.gif
--------------------------------------------------------------------------------
/resources/assets/snippets/nyan-cat-progress-bar.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/nyan-cat-progress-bar.gif
--------------------------------------------------------------------------------
/resources/assets/snippets/oneko.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/oneko.png
--------------------------------------------------------------------------------
/resources/assets/snippets/pointer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/pointer.png
--------------------------------------------------------------------------------
/resources/assets/snippets/pokemon-adventure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/pokemon-adventure.png
--------------------------------------------------------------------------------
/resources/assets/snippets/pretty-lyrics.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/pretty-lyrics.png
--------------------------------------------------------------------------------
/resources/assets/snippets/remove-connect-bar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/remove-connect-bar.png
--------------------------------------------------------------------------------
/resources/assets/snippets/remove-ep-likes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/remove-ep-likes.png
--------------------------------------------------------------------------------
/resources/assets/snippets/remove-gradient.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/remove-gradient.png
--------------------------------------------------------------------------------
/resources/assets/snippets/remove-popular.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/remove-popular.png
--------------------------------------------------------------------------------
/resources/assets/snippets/remove-recently-played.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/remove-recently-played.png
--------------------------------------------------------------------------------
/resources/assets/snippets/remove-the-artists-and-credits-sections-from-the-sidebar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/remove-the-artists-and-credits-sections-from-the-sidebar.png
--------------------------------------------------------------------------------
/resources/assets/snippets/remove-top-spacing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/remove-top-spacing.png
--------------------------------------------------------------------------------
/resources/assets/snippets/right-cover-art.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/right-cover-art.png
--------------------------------------------------------------------------------
/resources/assets/snippets/rotating-coverart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/rotating-coverart.png
--------------------------------------------------------------------------------
/resources/assets/snippets/rounded-buttons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/rounded-buttons.png
--------------------------------------------------------------------------------
/resources/assets/snippets/rounded-images.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/rounded-images.png
--------------------------------------------------------------------------------
/resources/assets/snippets/rounded-now-playing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/rounded-now-playing.png
--------------------------------------------------------------------------------
/resources/assets/snippets/smaller-right-sidebar-cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/smaller-right-sidebar-cover.png
--------------------------------------------------------------------------------
/resources/assets/snippets/smooth-playlist-reveal-gradient.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/smooth-playlist-reveal-gradient.png
--------------------------------------------------------------------------------
/resources/assets/snippets/smooth-progress-bar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/smooth-progress-bar.png
--------------------------------------------------------------------------------
/resources/assets/snippets/sonic-dancing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/sonic-dancing.png
--------------------------------------------------------------------------------
/resources/assets/snippets/switch-sidebars.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/switch-sidebars.png
--------------------------------------------------------------------------------
/resources/assets/snippets/thicker-bars.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/thicker-bars.png
--------------------------------------------------------------------------------
/resources/assets/snippets/thicker-sticky-list-icons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/thicker-sticky-list-icons.png
--------------------------------------------------------------------------------
/resources/assets/snippets/thin-library-sidebar-rows.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spicetify/marketplace/b4303b1bcf01867d136055ed19192668b721524e/resources/assets/snippets/thin-library-sidebar-rows.png
--------------------------------------------------------------------------------
/resources/blacklist.json:
--------------------------------------------------------------------------------
1 | {
2 | "repos": [
3 | "https://github.com/FoxRefire/spiceDL",
4 | "https://github.com/s000ik/Speedify",
5 | "https://github.com/ssatwik975/Speedify",
6 | "https://github.com/hudzax/amai-lyrics"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/resources/color.ini:
--------------------------------------------------------------------------------
1 | [Marketplace]
2 |
3 |
--------------------------------------------------------------------------------
/resources/install.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param(
3 | [Parameter()]
4 | [switch]$BypassAdmin
5 | )
6 |
7 | $ErrorActionPreference = 'Stop'
8 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
9 |
10 | function Invoke-Spicetify {
11 | param (
12 | [Parameter(Mandatory = $true, Position = 0, ValueFromRemainingArguments = $true)]
13 | [string[]]$Arguments
14 | )
15 |
16 | $spicetifyArgs = @()
17 | if ($BypassAdmin) {
18 | $spicetifyArgs += "--bypass-admin"
19 | }
20 | $spicetifyArgs += $Arguments
21 |
22 | & spicetify $spicetifyArgs
23 | return $LASTEXITCODE
24 | }
25 |
26 | function Invoke-SpicetifyWithOutput {
27 | param (
28 | [Parameter(Mandatory = $true, Position = 0, ValueFromRemainingArguments = $true)]
29 | [string[]]$Arguments
30 | )
31 |
32 | $spicetifyArgs = @()
33 | if ($BypassAdmin) {
34 | $spicetifyArgs += "--bypass-admin"
35 | }
36 | $spicetifyArgs += $Arguments
37 |
38 | $output = (& spicetify $spicetifyArgs 2>&1 | Out-String).Trim()
39 | return @{
40 | Output = $output
41 | ExitCode = $LASTEXITCODE
42 | }
43 | }
44 |
45 | Write-Host -Object 'Setting up...' -ForegroundColor 'Cyan'
46 |
47 | if (-not (Get-Command -Name 'spicetify' -ErrorAction 'SilentlyContinue')) {
48 | Write-Host -Object 'Spicetify not found.' -ForegroundColor 'Yellow'
49 | Write-Host -Object 'Installing it for you...' -ForegroundColor 'Cyan'
50 | $Parameters = @{
51 | Uri = 'https://raw.githubusercontent.com/spicetify/cli/main/install.ps1'
52 | UseBasicParsing = $true
53 | }
54 | Invoke-WebRequest @Parameters | Invoke-Expression
55 | }
56 |
57 | try {
58 | $result = Invoke-SpicetifyWithOutput "path" "userdata"
59 | if ($result.ExitCode -ne 0) {
60 | Write-Host -Object "Error from Spicetify:" -ForegroundColor 'Red'
61 | Write-Host -Object $result.Output -ForegroundColor 'Red'
62 | return
63 | }
64 | $spiceUserDataPath = $result.Output
65 | } catch {
66 | Write-Host -Object "Error running Spicetify:" -ForegroundColor 'Red'
67 | Write-Host -Object $_.Exception.Message.Trim() -ForegroundColor 'Red'
68 | return
69 | }
70 |
71 | if (-not (Test-Path -Path $spiceUserDataPath -PathType 'Container' -ErrorAction 'SilentlyContinue')) {
72 | $spiceUserDataPath = "$env:APPDATA\spicetify"
73 | }
74 | $marketAppPath = "$spiceUserDataPath\CustomApps\marketplace"
75 | $marketThemePath = "$spiceUserDataPath\Themes\marketplace"
76 |
77 | $isThemeInstalled = $(
78 | Invoke-Spicetify "path" "-s" | Out-Null
79 | -not $LASTEXITCODE
80 | )
81 | $currentTheme = (Invoke-SpicetifyWithOutput "config" "current_theme").Output
82 | $setTheme = $true
83 |
84 | Write-Host -Object 'Removing and creating Marketplace folders...' -ForegroundColor 'Cyan'
85 | try {
86 | $result = Invoke-SpicetifyWithOutput "path" "userdata"
87 | if ($result.ExitCode -ne 0) {
88 | Write-Host -Object "Error: Failed to get Spicetify path. Details:" -ForegroundColor 'Red'
89 | Write-Host -Object $result.Output -ForegroundColor 'Red'
90 | return
91 | }
92 |
93 | Remove-Item -Path $marketAppPath, $marketThemePath -Recurse -Force -ErrorAction 'SilentlyContinue' | Out-Null
94 | if (-not (New-Item -Path $marketAppPath, $marketThemePath -ItemType 'Directory' -Force -ErrorAction 'Stop')) {
95 | Write-Host -Object "Error: Failed to create Marketplace directories." -ForegroundColor 'Red'
96 | return
97 | }
98 | } catch {
99 | Write-Host -Object "Error: $($_.Exception.Message.Trim())" -ForegroundColor 'Red'
100 | return
101 | }
102 |
103 | Write-Host -Object 'Downloading Marketplace...' -ForegroundColor 'Cyan'
104 | $marketArchivePath = "$marketAppPath\marketplace.zip"
105 | $unpackedFolderPath = "$marketAppPath\marketplace-dist"
106 | $Parameters = @{
107 | Uri = 'https://github.com/spicetify/marketplace/releases/latest/download/marketplace.zip'
108 | UseBasicParsing = $true
109 | OutFile = $marketArchivePath
110 | }
111 | Invoke-WebRequest @Parameters
112 |
113 | Write-Host -Object 'Unzipping and installing...' -ForegroundColor 'Cyan'
114 | Expand-Archive -Path $marketArchivePath -DestinationPath $marketAppPath -Force
115 | Move-Item -Path "$unpackedFolderPath\*" -Destination $marketAppPath -Force
116 | Remove-Item -Path $marketArchivePath, $unpackedFolderPath -Force
117 | Invoke-Spicetify "config" "custom_apps" "spicetify-marketplace-" "-q"
118 | Invoke-Spicetify "config" "custom_apps" "marketplace"
119 | Invoke-Spicetify "config" "inject_css" "1" "replace_colors" "1"
120 |
121 | Write-Host -Object 'Downloading placeholder theme...' -ForegroundColor 'Cyan'
122 | $Parameters = @{
123 | Uri = 'https://raw.githubusercontent.com/spicetify/marketplace/main/resources/color.ini'
124 | UseBasicParsing = $true
125 | OutFile = "$marketThemePath\color.ini"
126 | }
127 | Invoke-WebRequest @Parameters
128 |
129 | Write-Host -Object 'Applying...' -ForegroundColor 'Cyan'
130 | if ($isThemeInstalled -and ($currentTheme -ne 'marketplace')) {
131 | $Host.UI.RawUI.Flushinputbuffer()
132 | $choice = $Host.UI.PromptForChoice(
133 | 'Local theme found',
134 | 'Do you want to replace it with a placeholder to install themes from the Marketplace?',
135 | ('&Yes', '&No'),
136 | 0
137 | )
138 | if ($choice = 1) { $setTheme = $false }
139 | }
140 | if ($setTheme) {
141 | Invoke-Spicetify "config" "current_theme" "marketplace"
142 | }
143 | Invoke-Spicetify "backup"
144 | Invoke-Spicetify "apply"
145 |
146 | Write-Host -Object 'Done!' -ForegroundColor 'Green'
147 | Write-Host -Object 'If nothing has happened, check the messages above for errors'
--------------------------------------------------------------------------------
/resources/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # Copyright 2019 khanhas. GPL license.
3 | # Edited from project Denoland install script (https://github.com/denoland/deno_install)
4 |
5 | set -e
6 |
7 |
8 | # download uri
9 | releases_uri=https://github.com/spicetify/marketplace/releases
10 | if [ $# -gt 0 ]; then
11 | tag=$1
12 | else
13 | tag=$(curl -LsH 'Accept: application/json' $releases_uri/latest)
14 | tag=${tag%\,\"update_url*}
15 | tag=${tag##*tag_name\":\"}
16 | tag=${tag%\"}
17 | fi
18 |
19 | tag=${tag#v}
20 |
21 | echo "FETCHING Version $tag"
22 |
23 | download_uri=$releases_uri/download/v$tag/marketplace.zip
24 | default_color_uri="https://raw.githubusercontent.com/spicetify/marketplace/main/resources/color.ini"
25 |
26 | SPICETIFY_CONFIG_DIR="$SPICETIFY_CONFIG"
27 | if [ -z "$SPICETIFY_CONFIG_DIR" ]; then
28 | SPICETIFY_CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/spicetify"
29 | fi
30 | INSTALL_DIR="$SPICETIFY_CONFIG_DIR/CustomApps"
31 |
32 | if [ ! -d "$INSTALL_DIR" ]; then
33 | echo "MAKING FOLDER $INSTALL_DIR";
34 | mkdir -p "$INSTALL_DIR"
35 | fi
36 |
37 | TAR_FILE="$INSTALL_DIR/marketplace-dist.zip"
38 |
39 | echo "DOWNLOADING $download_uri"
40 | curl --fail --location --progress-bar --output "$TAR_FILE" "$download_uri"
41 | cd "$INSTALL_DIR"
42 |
43 | echo "EXTRACTING"
44 | unzip -q -d "$INSTALL_DIR/marketplace-tmp" -o "$TAR_FILE"
45 |
46 | cd "$INSTALL_DIR/marketplace-tmp"
47 | echo "COPYING"
48 | rm -rf "$INSTALL_DIR/marketplace/" "$INSTALL_DIR/marketplace/"
49 | mv "$INSTALL_DIR/marketplace-tmp/marketplace-dist" "$INSTALL_DIR/marketplace"
50 |
51 | echo "INSTALLING"
52 | cd "$INSTALL_DIR/marketplace"
53 |
54 | # Remove old custom app name if exists
55 | spicetify config custom_apps spicetify-marketplace-
56 |
57 | # Color injection fix
58 | spicetify config inject_css 1
59 | spicetify config replace_colors 1
60 |
61 | current_theme=$(spicetify config current_theme)
62 | if [ ${#current_theme} -le 3 ]; then
63 | echo "No theme selected, using placeholder theme"
64 | if [ ! -d "$SPICETIFY_CONFIG_DIR/Themes/marketplace" ]; then
65 | echo "MAKING FOLDER $SPICETIFY_CONFIG_DIR/Themes/marketplace";
66 | mkdir -p "$SPICETIFY_CONFIG_DIR/Themes/marketplace"
67 | fi
68 | curl --fail --location --progress-bar --output "$SPICETIFY_CONFIG_DIR/Themes/marketplace/color.ini" "$default_color_uri"
69 | spicetify config current_theme marketplace;
70 | fi
71 |
72 | if spicetify config custom_apps marketplace ; then
73 | echo "Added to config!"
74 | echo "APPLYING"
75 | spicetify apply
76 | else
77 | echo "Command failed"
78 | echo "Please run \`spicetify config custom_apps marketplace\` manually "
79 | echo "Next run \`spicetify apply\`"
80 | fi
81 |
82 | echo "CLEANING UP"
83 | rm -rf "$TAR_FILE" "$INSTALL_DIR/marketplace-tmp/"
84 |
--------------------------------------------------------------------------------
/src/app.tsx:
--------------------------------------------------------------------------------
1 | import i18n, { t } from "i18next";
2 | import LanguageDetector from "i18next-browser-languagedetector";
3 | import React from "react";
4 | import { initReactI18next, withTranslation } from "react-i18next";
5 |
6 | import "./styles/styles.scss";
7 | import Grid from "./components/Grid";
8 | import ReadmePage from "./components/ReadmePage";
9 | import { ALL_TABS, CUSTOM_APP_PATH, LOCALSTORAGE_KEYS } from "./constants";
10 | import { getLocalStorageDataFromKey } from "./logic/Utils";
11 | import locales from "./resources/locales";
12 | import type { Config, TabItemConfig } from "./types/marketplace-types";
13 |
14 | i18n
15 | .use(initReactI18next) // passes i18n down to react-i18next
16 | .use(LanguageDetector)
17 | .init({
18 | // the translations
19 | resources: locales,
20 | detection: {
21 | order: ["navigator", "htmlTag"]
22 | },
23 | // lng: "en", // if you're using a language detector, do not define the lng option
24 | fallbackLng: "en",
25 | interpolation: {
26 | escapeValue: false // react already safes from xss => https://www.i18next.com/translation-function/interpolation#unescape
27 | }
28 | });
29 |
30 | class App extends React.Component<
31 | {
32 | t: (key: string) => string;
33 | },
34 | {
35 | count: number;
36 | CONFIG: Config;
37 | }
38 | > {
39 | state = {
40 | count: 0,
41 | CONFIG: {} as Config
42 | };
43 |
44 | CONFIG: Config;
45 | constructor(props) {
46 | super(props);
47 |
48 | // Get tabs config from local storage
49 | const tabsData = getLocalStorageDataFromKey(LOCALSTORAGE_KEYS.tabs, null);
50 | let tabs: TabItemConfig[] = [];
51 | try {
52 | tabs = tabsData;
53 |
54 | if (!Array.isArray(tabs)) {
55 | throw new Error("Could not parse marketplace tabs key");
56 | }
57 | if (tabs.length === 0) {
58 | throw new Error("Empty marketplace tabs key");
59 | }
60 | if (tabs.filter((tab) => !tab).length > 0) {
61 | throw new Error("Falsey marketplace tabs key");
62 | }
63 | } catch {
64 | tabs = ALL_TABS;
65 | localStorage.setItem(LOCALSTORAGE_KEYS.tabs, JSON.stringify(tabs));
66 | }
67 |
68 | // Get active theme
69 | let schemes = {};
70 | let activeScheme = null;
71 | try {
72 | const installedThemeKey = getLocalStorageDataFromKey(LOCALSTORAGE_KEYS.themeInstalled, null);
73 | if (installedThemeKey) {
74 | const installedTheme = getLocalStorageDataFromKey(installedThemeKey, null);
75 | if (!installedTheme) throw new Error("No installed theme data");
76 |
77 | schemes = installedTheme.schemes;
78 | activeScheme = installedTheme.activeScheme;
79 | } else {
80 | console.debug("No theme set as installed");
81 | }
82 | } catch (err) {
83 | console.error(err);
84 | }
85 |
86 | this.CONFIG = {
87 | // Fetch the settings and set defaults. Used in Settings.js
88 | visual: {
89 | stars: JSON.parse(getLocalStorageDataFromKey("marketplace:stars", true)),
90 | tags: JSON.parse(getLocalStorageDataFromKey("marketplace:tags", true)),
91 | showArchived: JSON.parse(getLocalStorageDataFromKey("marketplace:showArchived", false)),
92 | hideInstalled: JSON.parse(getLocalStorageDataFromKey("marketplace:hideInstalled", false)),
93 | colorShift: JSON.parse(getLocalStorageDataFromKey("marketplace:colorShift", false)),
94 | themeDevTools: JSON.parse(getLocalStorageDataFromKey("marketplace:themeDevTools", false)),
95 | albumArtBasedColors: JSON.parse(getLocalStorageDataFromKey("marketplace:albumArtBasedColors", false)),
96 | albumArtBasedColorsMode: getLocalStorageDataFromKey("marketplace:albumArtBasedColorsMode") || "monochrome-light",
97 | albumArtBasedColorsVibrancy: getLocalStorageDataFromKey("marketplace:albumArtBasedColorsVibrancy") || "PROMINENT",
98 | // Legacy from reddit app
99 | type: JSON.parse(getLocalStorageDataFromKey("marketplace:type", false)),
100 | // I was considering adding watchers as "followers" but it looks like the value is a duplicate
101 | // of stargazers, and the subscribers_count isn't returned in the main API call we make
102 | // https://github.community/t/bug-watchers-count-is-the-duplicate-of-stargazers-count/140865/4
103 | followers: JSON.parse(getLocalStorageDataFromKey("marketplace:followers", false))
104 | },
105 | tabs,
106 | activeTab: getLocalStorageDataFromKey(LOCALSTORAGE_KEYS.activeTab, tabs[0]),
107 | theme: {
108 | activeThemeKey: getLocalStorageDataFromKey(LOCALSTORAGE_KEYS.themeInstalled, null),
109 | schemes,
110 | activeScheme
111 | },
112 | sort: getLocalStorageDataFromKey(LOCALSTORAGE_KEYS.sort, "stars")
113 | };
114 |
115 | if (!this.CONFIG.activeTab || !this.CONFIG.tabs.filter((tab) => tab.name === this.CONFIG.activeTab).length) {
116 | this.CONFIG.activeTab = this.CONFIG.tabs[0].name;
117 | }
118 | }
119 |
120 | updateConfig = (config: Config) => {
121 | this.CONFIG = { ...config };
122 | console.debug("updated config", this.CONFIG);
123 | this.setState({
124 | CONFIG: { ...config }
125 | });
126 | };
127 |
128 | render() {
129 | const { location, replace } = Spicetify.Platform.History;
130 | // If page state set to display readme, render it
131 | // (This location state data comes from Card.openReadme())
132 | if (location.pathname === `${CUSTOM_APP_PATH}/readme`) {
133 | // If no data, redirect to main page
134 | if (!location.state?.data) {
135 | replace(CUSTOM_APP_PATH);
136 | return null;
137 | }
138 | return ;
139 | }
140 |
141 | return ;
142 | }
143 | }
144 |
145 | export default withTranslation()(App);
146 |
--------------------------------------------------------------------------------
/src/assets/icon-filled.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/Button.tsx:
--------------------------------------------------------------------------------
1 | // biome-ignore lint/style/useImportType: React is not a type
2 | import React from "react";
3 |
4 | import styles from "../styles/modules/button.module.scss";
5 |
6 | // Round is the default style
7 | // Circle is used by the install/remove button
8 | type ButtonType = "round" | "circle";
9 |
10 | const Button = (props: {
11 | onClick: (e: React.MouseEvent) => void;
12 | classes?: string[];
13 | label?: string | null;
14 | type?: ButtonType;
15 | children: React.ReactNode;
16 | disabled?: boolean;
17 | }) => {
18 | const buttonType = props.type || "round";
19 |
20 | const classList = [styles.button];
21 | if (buttonType === "circle") classList.push(styles.circle);
22 | if (props.classes) classList.push(...props.classes);
23 |
24 | return (
25 |
28 | );
29 | };
30 |
31 | export default Button;
32 |
--------------------------------------------------------------------------------
/src/components/Card/AuthorsDiv.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import type { Author } from "../../types/marketplace-types";
3 |
4 | const AuthorsDiv = (props: {
5 | authors: Author[];
6 | }) => {
7 | // Add a div with author links inside
8 | const authorsDiv = (
9 |
28 | );
29 |
30 | return authorsDiv;
31 | };
32 |
33 | export default AuthorsDiv;
34 |
--------------------------------------------------------------------------------
/src/components/Card/TagsDiv.tsx:
--------------------------------------------------------------------------------
1 | import { t } from "i18next";
2 | import React, { type DetailedReactHTMLElement } from "react";
3 |
4 | import { MAX_TAGS } from "../../constants";
5 |
6 | const TagsDiv = (props: {
7 | tags: string[];
8 | showTags: boolean;
9 | }) => {
10 | const [expanded, setExpanded] = React.useState(false);
11 |
12 | // Map of english names for tags so that the css can identify them for colouring
13 | const englishTagMap = {
14 | [t("grid.externalJS")]: "external JS",
15 | [t("grid.archived")]: "archived",
16 | [t("grid.dark")]: "dark",
17 | [t("grid.light")]: "light"
18 | };
19 |
20 | const generateTags = (tags: string[]) => {
21 | // Stop duplicate tags from appearing
22 | const uniqueTags = tags.filter((item, pos, arr) => arr.indexOf(item) === pos);
23 |
24 | return uniqueTags.reduce<
25 | DetailedReactHTMLElement<
26 | {
27 | className: string;
28 | draggable: false;
29 | "data-tag": string;
30 | },
31 | HTMLElement
32 | >[]
33 | >((accum, tag) => {
34 | const englishTag = englishTagMap[tag] || tag;
35 | // Render tags if enabled. Always render external JS and archived tags
36 | if (props.showTags || tag === t("grid.externalJS") || tag === t("grid.archived")) {
37 | accum.push(
38 | React.createElement(
39 | "li",
40 | {
41 | className: "marketplace-card__tag",
42 | draggable: false,
43 | "data-tag": englishTag
44 | },
45 | tag
46 | )
47 | );
48 | }
49 | return accum;
50 | }, []);
51 | };
52 | // Sort tags so that externalJS and archived tags come first
53 | let baseTags = [...(props.tags ?? [])].sort((a) => (a === t("grid.externalJS") || a === t("grid.archived") ? -1 : 1));
54 | let extraTags: string[] = [];
55 | // If there are more than one extra tags, slice them and add an expand button
56 | if (baseTags.length - MAX_TAGS > 1) {
57 | extraTags = props.tags.slice(MAX_TAGS);
58 | baseTags = baseTags.slice(0, MAX_TAGS);
59 | }
60 |
61 | // Render the tags list and add expand button if there are more tags
62 | return (
63 |
64 |
65 | {generateTags(baseTags)}
66 | {extraTags.length && expanded ? generateTags(extraTags) : null}
67 |
68 | {extraTags.length && !expanded ? (
69 |
78 | ) : null}
79 |
80 | );
81 | };
82 |
83 | export default TagsDiv;
84 |
--------------------------------------------------------------------------------
/src/components/Icons/DownloadIcon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const DownloadIcon = () => {
4 | return (
5 |
11 | );
12 | };
13 |
14 | export default DownloadIcon;
15 |
--------------------------------------------------------------------------------
/src/components/Icons/GitHubIcon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | // Export GitHub icon SVG as a React component
4 | const GitHubIcon = () => {
5 | return (
6 |
12 | );
13 | };
14 | export default GitHubIcon;
15 |
--------------------------------------------------------------------------------
/src/components/Icons/LoadMoreIcon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export default class LoadMoreIcon extends React.Component<{ onClick: () => void }> {
4 | render() {
5 | return (
6 |
12 |
18 | »
19 |
20 |
25 | Load more
26 |
27 |
28 | );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/components/Icons/LoadingIcon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const LoadingIcon = () => {
4 | // return React.createElement("svg", {
5 | // width: "100px", height: "100px", viewBox: "0 0 100 100", preserveAspectRatio: "xMidYMid",
6 | // }, React.createElement("circle", {
7 | // cx: "50", cy: "50", r: "0", fill: "none", stroke: "currentColor", "stroke-width": "2",
8 | // }, React.createElement("animate", {
9 | // attributeName: "r", repeatCount: "indefinite", dur: "1s", values: "0;40", keyTimes: "0;1", keySplines: "0 0.2 0.8 1", calcMode: "spline", begin: "0s",
10 | // }), React.createElement("animate", {
11 | // attributeName: "opacity", repeatCount: "indefinite", dur: "1s", values: "1;0", keyTimes: "0;1", keySplines: "0.2 0 0.8 1", calcMode: "spline", begin: "0s",
12 | // })), React.createElement("circle", {
13 | // cx: "50", cy: "50", r: "0", fill: "none", stroke: "currentColor", "stroke-width": "2",
14 | // }, React.createElement("animate", {
15 | // attributeName: "r", repeatCount: "indefinite", dur: "1s", values: "0;40", keyTimes: "0;1", keySplines: "0 0.2 0.8 1", calcMode: "spline", begin: "-0.5s",
16 | // }), React.createElement("animate", {
17 | // attributeName: "opacity", repeatCount: "indefinite", dur: "1s", values: "1;0", keyTimes: "0;1", keySplines: "0.2 0 0.8 1", calcMode: "spline", begin: "-0.5s",
18 | // })));
19 |
20 | return (
21 |
75 | );
76 | };
77 |
78 | export default LoadingIcon;
79 |
--------------------------------------------------------------------------------
/src/components/Icons/SettingsIcon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const SettingsIcon = () => {
4 | return (
5 |
11 | );
12 | };
13 |
14 | export default SettingsIcon;
15 |
--------------------------------------------------------------------------------
/src/components/Icons/ThemeDeveloperToolsIcon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const ThemeDeveloperToolsIcon = () => {
4 | return (
5 |
19 | );
20 | };
21 |
22 | export default ThemeDeveloperToolsIcon;
23 |
--------------------------------------------------------------------------------
/src/components/Icons/TooltipIcon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const TooltipIcon = () => {
4 | return (
5 |
9 | );
10 | };
11 |
12 | export default TooltipIcon;
13 |
--------------------------------------------------------------------------------
/src/components/Icons/TrashIcon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const TrashIcon = () => {
4 | return (
5 |
11 | );
12 | };
13 |
14 | export default TrashIcon;
15 |
--------------------------------------------------------------------------------
/src/components/Modals/BackupModal/index.tsx:
--------------------------------------------------------------------------------
1 | import { t } from "i18next";
2 | import { highlight, languages } from "prismjs/components/prism-core";
3 | import React from "react";
4 | import Editor from "react-simple-code-editor";
5 | import "prismjs/components/prism-json";
6 |
7 | import { exportMarketplace, importMarketplace } from "../../../logic/Utils";
8 | import Button from "../../Button";
9 |
10 | const BackupModal = () => {
11 | const [importText, setImportText] = React.useState("");
12 |
13 | const exportSettings = () => {
14 | const settings = exportMarketplace();
15 | Spicetify.Platform.ClipboardAPI.copy(JSON.stringify(settings));
16 | Spicetify.showNotification(t("backupModal.settingsCopied"));
17 | Spicetify.PopupModal.hide();
18 | };
19 |
20 | /**
21 | * Load in settings from a JSON string, then reload the page.
22 | * If the string is empty or the JSON is invalid, show an error.
23 | * @param settingsString JSON string of settings to import
24 | */
25 | const importSettings = (settingsString: string) => {
26 | // Check if the settings data exists, if not return an error message and exit
27 | if (!settingsString) {
28 | Spicetify.showNotification(t("backupModal.noDataPasted"));
29 | return;
30 | }
31 |
32 | // Check if settings string is valid JSON, if not return an error message and exit
33 | let settings: JSON;
34 | try {
35 | settings = JSON.parse(settingsString);
36 | } catch (e) {
37 | Spicetify.showNotification(t("backupModal.invalidJSON"));
38 | return;
39 | }
40 |
41 | importMarketplace(settings);
42 | location.reload();
43 | };
44 |
45 | /**
46 | * Import settings from the text input
47 | */
48 | const importSettingsFromInput = () => {
49 | importSettings(importText);
50 | };
51 |
52 | /**
53 | * Prompt user to select a file to import and then run importMarketplace
54 | */
55 | const importSettingsFromFile = async () => {
56 | const fileHandle = await window.showOpenFilePicker();
57 | const file = await fileHandle[0].getFile();
58 | const text = await file.text();
59 |
60 | importSettings(text);
61 | };
62 |
63 | return (
64 |
65 |
66 |
67 |
68 | setImportText(text)}
71 | highlight={(text) => highlight(text, languages.css)}
72 | textareaId="marketplace-import-text"
73 | textareaClassName="import-textarea"
74 | readOnly={false}
75 | className="marketplace-code-editor-textarea"
76 | placeholder={t("backupModal.inputPlaceholder")}
77 | style={
78 | {
79 | // fontFamily: "'Fira code', 'Fira Mono', monospace'",
80 | // fontSize: 12,
81 | }
82 | }
83 | />
84 |
85 |
86 | <>
87 |
90 |
93 |
94 |
97 | >
98 |
99 | );
100 | };
101 | export default BackupModal;
102 |
--------------------------------------------------------------------------------
/src/components/Modals/Reload/index.tsx:
--------------------------------------------------------------------------------
1 | import { t } from "i18next";
2 | import React from "react";
3 | import Button from "../../Button";
4 |
5 | const ReloadModal = () => {
6 | return (
7 |
8 |
{t("reloadModal.description")}
9 |
10 |
18 |
25 |
26 |
27 | );
28 | };
29 |
30 | export default ReloadModal;
31 |
--------------------------------------------------------------------------------
/src/components/Modals/Settings/ConfigRow.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import type { Config } from "../../../types/marketplace-types";
3 | import TooltipIcon from "../../Icons/TooltipIcon";
4 | import SortBox from "../../Sortbox";
5 | import Toggle from "../../Toggle";
6 | const Spicetify = window.Spicetify;
7 |
8 | const ConfigRow = (props: {
9 | name: string;
10 | storageKey: string;
11 | modalConfig: Config;
12 | clickable?: boolean;
13 | updateConfig: (CONFIG: Config) => void;
14 | type?: string;
15 | options?: string[];
16 | description?: string | null;
17 | }) => {
18 | const type = props.type;
19 | const componentId = type === "dropdown" ? `dropdown:${props.storageKey}` : `toggle:${props.storageKey}`;
20 | const enabled = !!props.modalConfig.visual[props.storageKey];
21 |
22 | const settingsToggleChange = (e) => {
23 | const state = e.target.checked;
24 | const storageKey = e.target.dataset.storageKey;
25 | props.modalConfig.visual[storageKey] = state;
26 | console.debug(`toggling ${storageKey} to ${state}`);
27 | localStorage.setItem(`marketplace:${storageKey}`, String(state));
28 |
29 | // Saves the config settings to app as well as SettingsModal state
30 | props.updateConfig(props.modalConfig);
31 | // gridUpdatePostsVisual && gridUpdatePostsVisual();
32 | };
33 | const settingsDropdownChange = (value) => {
34 | const state = value;
35 | const storageKey = props.storageKey;
36 | props.modalConfig.visual[storageKey] = state;
37 | localStorage.setItem(`marketplace:${storageKey}`, String(state));
38 | props.updateConfig(props.modalConfig);
39 | };
40 | if (props.description === undefined || props.description === null) {
41 | props.description = "" as string;
42 | }
43 |
44 | if (type === "dropdown" && props.options) {
45 | return (
46 |
47 |
50 |
51 |
{
53 | return {
54 | key: option,
55 | value: option
56 | };
57 | })}
58 | onChange={(value) => settingsDropdownChange(value)}
59 | sortBySelectedFn={(item) => {
60 | return item.key === props.modalConfig.visual[props.storageKey];
61 | }}
62 | />
63 | {
65 | return (
66 |
67 | {line}
68 |
69 |
70 | );
71 | })}
72 | renderInline={true}
73 | showDelay={10}
74 | placement="top"
75 | labelClassName="marketplace-settings-tooltip"
76 | disabled={false}
77 | >
78 |
79 |
80 |
81 |
82 |
83 |
84 | );
85 | }
86 | return (
87 |
88 |
91 |
92 |
93 |
94 |
95 | );
96 | };
97 |
98 | export default ConfigRow;
99 |
--------------------------------------------------------------------------------
/src/components/Modals/Settings/DnDList.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component, useState, useEffect, useRef } from "react";
2 | import { DragDropContext, Draggable, type DropResult, Droppable } from "react-beautiful-dnd";
3 | import { LOCALSTORAGE_KEYS } from "../../../constants";
4 | import type { Config, TabItemConfig } from "../../../types/marketplace-types";
5 |
6 | const DnDList = (props: {
7 | modalConfig: Config;
8 | updateConfig: (CONFIG: Config) => void;
9 | }) => {
10 | const colorVariable = getComputedStyle(document.body).getPropertyValue("--spice-button-disabled");
11 | const [currentSize, setCurrentSize] = useState({ width: window.innerWidth });
12 |
13 | useEffect(() => {
14 | const onResize = () => setCurrentSize({ width: window.innerWidth });
15 | window.addEventListener("resize", onResize);
16 | return () => window.removeEventListener("resize", onResize);
17 | }, []);
18 |
19 | const adjustTransform = (transform: string) => {
20 | const match = transform.match(/translate\(([-\d.]+)px,\s*([-\d.]+)px\)/);
21 | if (!match) return transform;
22 | const isMaximized = currentSize.width >= window.screen.width * 0.95;
23 | const offsetX = isMaximized ? 600 : 430;
24 | const offsetY = isMaximized ? 120 : 70;
25 | const x = Number.parseFloat(match[1]) - offsetX;
26 | const y = Number.parseFloat(match[2]) - offsetY;
27 | return `translate(${x}px, ${y}px)`;
28 | };
29 |
30 | const getItemStyle = (isDragging, draggableStyle, isEnabled) => {
31 | const style = { ...draggableStyle };
32 | if (isDragging && style.transform) {
33 | style.transform = adjustTransform(style.transform);
34 | }
35 | return {
36 | borderRadius: "5px",
37 | border: isEnabled ? `2px solid ${colorVariable}` : "2px solid red",
38 | userSelect: "none",
39 | paddingTop: 12,
40 | paddingBottom: 12,
41 | width: "110px",
42 | display: "flex",
43 | alignItems: "center",
44 | justifyContent: "center",
45 | textDecoration: isEnabled ? "none" : "line-through",
46 | cursor: "pointer",
47 | ...style
48 | };
49 | };
50 |
51 | const getListStyle = () => ({
52 | display: "flex",
53 | paddingTop: 8,
54 | paddingBottom: 8,
55 | gap: 8
56 | });
57 |
58 | const onDragEnd = (result: DropResult) => {
59 | const { source, destination } = result;
60 | if (!destination) return;
61 |
62 | reorder(props.modalConfig.tabs, source.index, destination.index);
63 | };
64 |
65 | function reorder(tabs: TabItemConfig[], start: number, end: number) {
66 | const result = Array.from(tabs);
67 | const [removed] = result.splice(start, 1);
68 | result.splice(end, 0, removed);
69 |
70 | props.modalConfig.tabs = result;
71 |
72 | localStorage.setItem(LOCALSTORAGE_KEYS.tabs, JSON.stringify(props.modalConfig.tabs));
73 |
74 | props.updateConfig(props.modalConfig);
75 | }
76 |
77 | const onToggleEnabled = (name) => {
78 | const updatedTabs = props.modalConfig.tabs.map((tab) => (tab.name === name ? { ...tab, enabled: !tab.enabled } : tab));
79 |
80 | props.modalConfig.tabs = updatedTabs;
81 | localStorage.setItem(LOCALSTORAGE_KEYS.tabs, JSON.stringify(props.modalConfig.tabs));
82 | props.updateConfig(props.modalConfig);
83 | };
84 |
85 | return (
86 |
87 |
88 | {(provided, snapshot) => (
89 |
90 | {props.modalConfig.tabs.map((item, index) => (
91 |
92 | {(provided, snapshot) => (
93 |
98 |
onToggleEnabled(item.name)}>
99 |
114 | {item.name === "Extensions" ? "Extens." : item.name}
115 |
116 |
117 | )}
118 |
119 | ))}
120 | {provided.placeholder}
121 |
122 | )}
123 |
124 |
125 | );
126 | };
127 |
128 | export default DnDList;
129 |
--------------------------------------------------------------------------------
/src/components/Modals/Settings/index.tsx:
--------------------------------------------------------------------------------
1 | import { t } from "i18next";
2 | import React from "react";
3 |
4 | import { LOCALSTORAGE_KEYS, MARKETPLACE_VERSION } from "../../../constants";
5 | import { openModal } from "../../../logic/LaunchModals";
6 | import { getLocalStorageDataFromKey, resetMarketplace, sleep } from "../../../logic/Utils";
7 | import type { Config } from "../../../types/marketplace-types";
8 | import Button from "../../Button";
9 | import ConfigRow from "./ConfigRow";
10 | import DnDList from "./DnDList";
11 |
12 | interface Props {
13 | CONFIG: Config;
14 | updateAppConfig: (CONFIG: Config) => void;
15 | }
16 |
17 | const SettingsModal = ({ CONFIG, updateAppConfig }: Props) => {
18 | // Basically takes in the app's CONFIG and create the initial state,
19 | // and copies it into the SettingsModal state
20 | // then when updating anything in the main state, also updates the SettingsModal state
21 |
22 | const [modalConfig, setModalConfig] = React.useState({ ...CONFIG });
23 | const [versionButtonText, setVersionButtonText] = React.useState(t("settings.versionBtn"));
24 | // TODO: use React.useCallback?
25 | const updateConfig = (CONFIG: Config) => {
26 | updateAppConfig({ ...CONFIG });
27 | setModalConfig({ ...CONFIG });
28 | };
29 |
30 | /** Copy Marketplace version to clipboard and update button text */
31 | const copyVersion = () => {
32 | Spicetify.Platform.ClipboardAPI.copy(MARKETPLACE_VERSION);
33 | setVersionButtonText(t("settings.versionCopied"));
34 | setTimeout(() => setVersionButtonText(t("settings.versionBtn")), 3000);
35 | };
36 |
37 | // Can't use proper event listener here because it's just the DOM outside the component
38 | const closeButton = document.querySelector("body > generic-modal button.main-trackCreditsModal-closeBtn") as HTMLElement;
39 | const modalOverlay = document.querySelector("body > generic-modal > div") as HTMLElement;
40 | if (closeButton && modalOverlay) {
41 | closeButton.onclick = () => location.reload();
42 | closeButton.setAttribute("style", "cursor: pointer;");
43 | modalOverlay.onclick = (e) => {
44 | // If clicked on overlay, also reload
45 | if (e.target === modalOverlay) {
46 | location.reload();
47 | }
48 | };
49 | }
50 |
51 | const AlbumArtColorDropDowns = getLocalStorageDataFromKey(LOCALSTORAGE_KEYS.albumArtBasedColor) ? (
52 | <>
53 |
62 |
71 | >
72 | ) : null;
73 |
74 | return (
75 |
76 |
77 |
{t("settings.optionsHeading")}
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | {AlbumArtColorDropDowns}
86 |
87 |
88 |
89 |
{t("settings.tabsHeading")}
90 |
91 |
({t("settings.tabsDescription")})
92 |
93 |
94 |
95 |
{t("settings.resetHeading")}
96 |
97 |
{t("settings.resetDescription")}
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
{t("settings.backupHeading")}
106 |
107 |
{t("settings.backupLabel")}
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 | {t("grid.spicetifyMarketplace")} {t("settings.versionHeading")} {MARKETPLACE_VERSION}
118 |
119 |
120 |
123 |
124 |
125 |
126 |
127 | );
128 | };
129 |
130 | const onBackupClick = async () => {
131 | // Make a new mutation observer to make sure the modal is gone
132 | const observer = new MutationObserver(async () => {
133 | const settingsModal = document.querySelector(".GenericModal[aria-label='Settings']");
134 | if (!settingsModal) {
135 | await sleep(100);
136 | openModal("BACKUP");
137 | observer.disconnect();
138 | }
139 | });
140 |
141 | // TODO: does it still work if I just attach to the settings modal itself?
142 | observer.observe(document.body, {
143 | childList: true,
144 | subtree: true
145 | });
146 |
147 | Spicetify.PopupModal.hide();
148 | };
149 |
150 | export default SettingsModal;
151 |
--------------------------------------------------------------------------------
/src/components/Modals/ThemeDevTools/index.tsx:
--------------------------------------------------------------------------------
1 | import { t } from "i18next";
2 | import { highlight, languages } from "prismjs/components/prism-core";
3 | import React from "react";
4 | import Editor from "react-simple-code-editor";
5 | import "prismjs/components/prism-ini";
6 |
7 | import { LOCALSTORAGE_KEYS } from "../../../constants";
8 | import { getInvalidCSS, getLocalStorageDataFromKey, parseIni, unparseIni } from "../../../logic/Utils";
9 | import Button from "../../Button";
10 |
11 | const themeKey = localStorage.getItem(LOCALSTORAGE_KEYS.themeInstalled);
12 | const themeManifest = themeKey ? getLocalStorageDataFromKey(themeKey) : null;
13 |
14 | const ThemeDevToolsModal = () => {
15 | const [code, setCode] = React.useState(themeManifest ? unparseIni(themeManifest.schemes) : t("devTools.noThemeInstalled"));
16 |
17 | return (
18 |
57 | );
58 | };
59 |
60 | const saveColorIni = (code: string) => {
61 | if (themeKey) {
62 | const colorIniParsed = parseIni(code);
63 | themeManifest.schemes = colorIniParsed;
64 | localStorage.setItem(themeKey, JSON.stringify(themeManifest));
65 | } else {
66 | Spicetify.showNotification(t("devTools.noThemeManifest"), true);
67 | }
68 | };
69 |
70 | export default ThemeDevToolsModal;
71 |
--------------------------------------------------------------------------------
/src/components/Modals/Update/index.tsx:
--------------------------------------------------------------------------------
1 | import { t } from "i18next";
2 | import React from "react";
3 |
4 | import { LATEST_RELEASE_URL, MARKETPLACE_VERSION, RELEASES_URL } from "../../../constants";
5 | import { getMarkdownHTML } from "../../../logic/Utils";
6 |
7 | async function fetchLatestReleaseInfo(): Promise<{
8 | version: string;
9 | changelog: string | null;
10 | } | null> {
11 | try {
12 | const result = await fetch(LATEST_RELEASE_URL);
13 | const resultJson = await result.json();
14 | const { body, tag_name, message } = resultJson;
15 | return body && tag_name && !message
16 | ? {
17 | version: tag_name.replace("v", ""),
18 | changelog: await getMarkdownHTML(body.match(/## What's Changed([\s\S]*?)(\r\n\r|\n\n##)/)[1], "spicetify", "marketplace")
19 | }
20 | : null;
21 | } catch (error) {
22 | console.error(error);
23 | return null;
24 | }
25 | }
26 |
27 | function UpdateModal(): React.ReactElement {
28 | const [releaseInfo, setReleaseInfo] = React.useState<{
29 | version: string;
30 | changelog: string | null;
31 | } | null>(null);
32 |
33 | React.useEffect(() => {
34 | fetchLatestReleaseInfo().then((releaseInfo) => setReleaseInfo(releaseInfo));
35 | }, []);
36 |
37 | return (
38 |
39 |
44 |
45 |
46 |
{t("updateModal.whatsChanged")}
47 |
48 | {t("updateModal.seeChangelog")}
49 |
50 |
51 |
52 |
53 |
57 |
58 | );
59 | }
60 |
61 | export default UpdateModal;
62 |
--------------------------------------------------------------------------------
/src/components/ReadmePage.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { withTranslation } from "react-i18next";
3 | import { getMarkdownHTML } from "../logic/Utils";
4 | import type { CardType } from "../types/marketplace-types";
5 | import Button from "./Button";
6 | import DownloadIcon from "./Icons/DownloadIcon";
7 | import GitHubIcon from "./Icons/GitHubIcon";
8 | import LoadingIcon from "./Icons/LoadingIcon";
9 | import TrashIcon from "./Icons/TrashIcon";
10 |
11 | class ReadmePage extends React.Component<
12 | {
13 | // props
14 | // TODO: decide what data we want to pass in and how we want to store it
15 | // (this currently comes from Card.openReadme)
16 | data: {
17 | title: string;
18 | user: string;
19 | repo: string;
20 | branch: string;
21 | readmeURL: string;
22 | readmeDir: string;
23 | type: CardType;
24 | install: () => void;
25 | isInstalled: () => boolean;
26 | };
27 | title: string;
28 | // TODO: there's probably a better way to make TS not complain about the withTranslation HOC
29 | t: (key: string) => string;
30 | },
31 | {
32 | isInstalled: boolean;
33 | // state
34 | html: string;
35 | }
36 | > {
37 | state = {
38 | isInstalled: this.props.data.isInstalled(),
39 | html: `${this.props.t("readmePage.loading")}
`
40 | };
41 |
42 | getReadmeHTML = async () => {
43 | return fetch(this.props.data.readmeURL)
44 | .then((res) => {
45 | if (!res.ok) throw Spicetify.showNotification(`${this.props.t("readmePage.errorLoading")} (HTTP ${res.status})`, true);
46 | return res.text();
47 | })
48 | .then((readmeText) => getMarkdownHTML(readmeText, this.props.data.user, this.props.data.repo))
49 | .then((html) => {
50 | if (!html) Spicetify.Platform.History.goBack();
51 | return html;
52 | })
53 | .catch((err) => {
54 | console.error(err);
55 | Spicetify.Platform.History.goBack();
56 | return null;
57 | });
58 | };
59 |
60 | componentDidMount() {
61 | // Get and set readme html once loaded
62 | this.getReadmeHTML().then((html) => {
63 | if (html == null) return;
64 | this.setState({ html });
65 | });
66 | }
67 |
68 | componentDidUpdate() {
69 | // Make the page scrollable
70 | const main = document.querySelector("#marketplace-readme")?.closest("main");
71 | if (main) {
72 | const callScrollbar = setInterval(() => {
73 | if (!document.querySelector("#marketplace-readme")) {
74 | clearInterval(callScrollbar);
75 | main.style.removeProperty("overflow-y");
76 | return;
77 | }
78 | // TODO: see if it's possible to use some load event or mutation observer to do this
79 | main.style.overflowY = "visible";
80 | main.style.overflowY = "auto";
81 | }, 1000);
82 | }
83 |
84 | // Add error handler in attempt to fix broken image urls
85 | // e.g. "screenshot.png" loads https://xpui.app.spotify.com/screenshot.png and breaks
86 | // so I turn it into https://raw.githubusercontent.com/theRealPadster/spicetify-hide-podcasts/main/screenshot.png
87 | // This works for urls relative to the repo readme
88 | // biome-ignore lint/complexity/noForEach: querySelectorAll returns a NodeList, not an array
89 | document.querySelectorAll("#marketplace-readme img").forEach((img) => {
90 | img.addEventListener(
91 | "error",
92 | (e) => {
93 | const element = e.target as HTMLImageElement;
94 | const originalSrc = element.getAttribute("src");
95 | const fixedSrc =
96 | originalSrc?.charAt(0) === "/"
97 | ? `https://raw.githubusercontent.com/${this.props.data.user}/${this.props.data.repo}/${this.props.data.branch}/${originalSrc?.slice(1)}`
98 | : `${this.props.data.readmeURL.substring(0, this.props.data.readmeURL.lastIndexOf("/"))}/${originalSrc}`;
99 | element.setAttribute("src", fixedSrc);
100 | },
101 | { once: true }
102 | );
103 | });
104 | }
105 |
106 | buttonContent() {
107 | if (this.props.data.type === "app") {
108 | return {
109 | icon: ,
110 | text: this.props.t("github")
111 | };
112 | }
113 | if (this.state.isInstalled) {
114 | return {
115 | icon: ,
116 | text: this.props.t("remove")
117 | };
118 | }
119 | return {
120 | icon: ,
121 | text: this.props.t("install")
122 | };
123 | }
124 |
125 | render() {
126 | const expFeatures = JSON.parse(localStorage.getItem("spicetify-exp-features") || "{}");
127 | const isGlobalNav = expFeatures.enableGlobalNavBar?.value !== "control" && true;
128 |
129 | const tabBarMargin = {
130 | marginTop: isGlobalNav ? "60px" : "0px"
131 | };
132 |
133 | return (
134 |
135 |
136 |
137 |
{this.props.title}
138 |
139 |
140 |
151 |
152 |
153 | {this.state.html === "Loading...
" ? (
154 |
157 | ) : (
158 |
159 | )}
160 |
161 | );
162 | }
163 | }
164 |
165 | export default withTranslation()(ReadmePage);
166 |
--------------------------------------------------------------------------------
/src/components/Sortbox.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Dropdown, { type Option } from "react-dropdown";
3 | import type { SortBoxOption } from "../types/marketplace-types";
4 |
5 | interface Props {
6 | sortBoxOptions: SortBoxOption[];
7 | onChange: (value: string) => void;
8 | sortBySelectedFn: (item: SortBoxOption) => boolean;
9 | }
10 |
11 | const SortBox = (props: Props) => {
12 | const _onSelect = (item: Option) => {
13 | props.onChange(item.value);
14 | };
15 |
16 | const options: Option[] = props.sortBoxOptions.map((item) => {
17 | return {
18 | value: item.key,
19 | label: item.value
20 | };
21 | });
22 |
23 | const sortBySelected = props.sortBoxOptions.find(props.sortBySelectedFn);
24 | // console.log(sortBySelected);
25 | return (
26 | // Create a drop down menu
27 |
34 | );
35 | };
36 |
37 | export default SortBox;
38 |
--------------------------------------------------------------------------------
/src/components/TabBar.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import Dropdown, { type Option } from "react-dropdown";
3 | import { withTranslation } from "react-i18next";
4 | import type { TabItemConfig } from "../types/marketplace-types";
5 |
6 | // NOTE: The label and value are the same (e.g. "Extensions")
7 | type TabOptionConfig = Option & {
8 | active: boolean;
9 | enabled: boolean;
10 | };
11 |
12 | class TabBarItem extends React.Component<{
13 | item: TabOptionConfig;
14 | switchTo: (option: Option) => void;
15 | // TODO: there's probably a better way to make TS not complain about the withTranslation HOC
16 | t: (key: string) => string;
17 | }> {
18 | render() {
19 | const { t } = this.props;
20 | if (!this.props.item.enabled) return null;
21 |
22 | return (
23 | {
27 | event.preventDefault();
28 | this.props.switchTo(this.props.item);
29 | }}
30 | >
31 |
37 | {t(`tabs.${this.props.item.value}`)}
38 |
39 |
40 | );
41 | }
42 | }
43 |
44 | const TabBarItemWithTranslation = withTranslation()(TabBarItem);
45 |
46 | interface TabBarMoreProps {
47 | items: TabOptionConfig[];
48 | switchTo: (option: Option) => void;
49 | }
50 | const TabBarMore = React.memo(function TabBarMore({ items, switchTo }: TabBarMoreProps) {
51 | return (
52 |
53 |
54 |
55 | );
56 | });
57 |
58 | export const TopBarContent = (props: {
59 | links: TabItemConfig[];
60 | activeLink: string;
61 | switchCallback: (option: Option) => void;
62 | }) => {
63 | const resizeHost = document.querySelector(
64 | ".Root__main-view .os-resize-observer-host, .Root__main-view .os-size-observer, .Root__main-view .main-view-container__scroll-node"
65 | );
66 | if (!resizeHost) return null;
67 |
68 | const [windowSize, setWindowSize] = useState(resizeHost.clientWidth);
69 | const resizeHandler = () => setWindowSize(resizeHost.clientWidth);
70 | const contextHandler = () => {
71 | // Move the marketplace-tabBar item to the main-topBar-topbarContent div
72 | const tabBar = document.querySelector(".marketplace-tabBar");
73 | const topBarContent = document.querySelector(".main-topBar-topbarContentWrapper");
74 | if (!tabBar || !topBarContent) {
75 | setTimeout(contextHandler, 100);
76 | return;
77 | }
78 | if (tabBar && topBarContent && Spicetify.Platform.History.location.pathname === "/marketplace") {
79 | topBarContent.appendChild(tabBar);
80 | document.querySelector(".main-topBar-container")?.setAttribute("style", "contain: unset;");
81 | }
82 | Spicetify.Platform.History.listen(({ pathname }) => {
83 | if (pathname !== "/marketplace") {
84 | // Delete tabBar from the dom
85 | document.querySelector(".marketplace-tabBar")?.remove();
86 | document.querySelector(".main-topBar-container")?.removeAttribute("style");
87 | }
88 | });
89 | };
90 | useEffect(() => {
91 | const observer = new ResizeObserver(resizeHandler);
92 | observer.observe(resizeHost);
93 | return () => {
94 | observer.disconnect();
95 | };
96 | });
97 | useEffect(() => {
98 | contextHandler();
99 | });
100 | return ;
101 | };
102 |
103 | interface TabBarProps {
104 | links: TabItemConfig[];
105 | activeLink: string;
106 | switchCallback: (option: Option) => void;
107 | windowSize: number;
108 | }
109 | const TabBar = React.memo(function TabBar({ links, activeLink, switchCallback, windowSize = Number.POSITIVE_INFINITY }: TabBarProps) {
110 | const tabBarRef = React.useRef(null);
111 | const [childrenSizes, setChildrenSizes] = useState([] as number[]);
112 | const [availableSpace, setAvailableSpace] = useState(0);
113 | const [droplistItem, setDroplistItems] = useState([] as number[]);
114 |
115 | // Key is the tab name, value is also the tab name, active is if it's active
116 | const options = links.map(({ name, enabled }) => {
117 | const active = name === activeLink;
118 | return { label: name, value: name, active, enabled } as TabOptionConfig;
119 | });
120 |
121 | // biome-ignore lint/correctness/useExhaustiveDependencies: Run when windowSize changes
122 | useEffect(() => {
123 | if (!tabBarRef.current) return;
124 | setAvailableSpace(tabBarRef.current.clientWidth);
125 | }, [windowSize, tabBarRef.current?.clientWidth]);
126 |
127 | // biome-ignore lint/correctness/useExhaustiveDependencies: Run when links change
128 | useEffect(() => {
129 | if (!tabBarRef.current) return;
130 |
131 | const children = Array.from(tabBarRef.current.children);
132 | const tabbarItemSizes = children.map((child) => child.clientWidth);
133 |
134 | setChildrenSizes(tabbarItemSizes);
135 | }, [links]);
136 |
137 | useEffect(() => {
138 | if (!tabBarRef.current) return;
139 |
140 | const totalSize = childrenSizes.reduce((a, b) => a + b, 0);
141 |
142 | // Can we render everything?
143 | if (totalSize <= availableSpace) {
144 | setDroplistItems([]);
145 | return;
146 | }
147 |
148 | // The `More` button can be set to _any_ of the children. So we
149 | // reserve space for the largest item instead of always taking
150 | // the last item.
151 | const viewMoreButtonSize = Math.max(...childrenSizes);
152 |
153 | // Figure out how many children we can render while also showing
154 | // the More button
155 | const itemsToHide = [] as number[];
156 | let stopWidth = viewMoreButtonSize;
157 |
158 | childrenSizes.forEach((childWidth, i) => {
159 | if (availableSpace >= stopWidth + childWidth) {
160 | stopWidth += childWidth;
161 | } else {
162 | itemsToHide.push(i);
163 | }
164 | });
165 |
166 | setDroplistItems(itemsToHide);
167 | }, [availableSpace, childrenSizes]);
168 |
169 | return (
170 |
182 | );
183 | });
184 |
--------------------------------------------------------------------------------
/src/components/Toggle.tsx:
--------------------------------------------------------------------------------
1 | // biome-ignore lint/style/useImportType: React is not a type
2 | import React from "react";
3 |
4 | import styles from "../styles/modules/toggle.module.scss";
5 |
6 | const Toggle = (props: {
7 | name: string;
8 | storageKey: string;
9 | enabled: boolean;
10 | clickable?: boolean;
11 | onChange: (e: React.ChangeEvent) => void;
12 | }) => {
13 | const toggleId = `toggle:${props.storageKey}`;
14 |
15 | const wrapperClassList = [styles["toggle-wrapper"]];
16 | if (props.clickable === false) wrapperClassList.push(styles.disabled);
17 |
18 | return (
19 |
33 | );
34 | };
35 |
36 | export default Toggle;
37 |
--------------------------------------------------------------------------------
/src/constants.ts:
--------------------------------------------------------------------------------
1 | import { version } from "../package.json";
2 | import type { TabItemConfig } from "./types/marketplace-types";
3 |
4 | export const MARKETPLACE_VERSION = version;
5 |
6 | const STORAGE_KEY_PREFIX = "marketplace";
7 | export const LOCALSTORAGE_KEYS = {
8 | installedExtensions: `${STORAGE_KEY_PREFIX}:installed-extensions`,
9 | installedSnippets: `${STORAGE_KEY_PREFIX}:installed-snippets`,
10 | installedThemes: `${STORAGE_KEY_PREFIX}:installed-themes`,
11 | activeTab: `${STORAGE_KEY_PREFIX}:active-tab`,
12 | tabs: `${STORAGE_KEY_PREFIX}:tabs`,
13 | sort: `${STORAGE_KEY_PREFIX}:sort`,
14 | // Theme installed store the localsorage key of the theme (e.g. marketplace:installed:NYRI4/Comfy-spicetify/user.css)
15 | themeInstalled: `${STORAGE_KEY_PREFIX}:theme-installed`,
16 | localTheme: `${STORAGE_KEY_PREFIX}:local-theme`,
17 | albumArtBasedColor: `${STORAGE_KEY_PREFIX}:albumArtBasedColors`,
18 | albumArtBasedColorMode: `${STORAGE_KEY_PREFIX}:albumArtBasedColorsMode`,
19 | albumArtBasedColorVibrancy: `${STORAGE_KEY_PREFIX}:albumArtBasedColorsVibrancy`,
20 | colorShift: `${STORAGE_KEY_PREFIX}:colorShift`
21 | };
22 |
23 | // Initalize topbar tabs
24 | // Data initalized in TabBar.js
25 | export const ALL_TABS: TabItemConfig[] = [
26 | { name: "Extensions", enabled: true },
27 | { name: "Themes", enabled: true },
28 | { name: "Snippets", enabled: true },
29 | { name: "Apps", enabled: true },
30 | { name: "Installed", enabled: true }
31 | ];
32 |
33 | // Max GitHub API items per page
34 | // https://docs.github.com/en/rest/reference/search#search-repositories
35 | export const ITEMS_PER_REQUEST = 100;
36 |
37 | export const CUSTOM_APP_PATH = "/marketplace";
38 |
39 | // Used in Card.tsx
40 | export const MAX_TAGS = 4;
41 |
42 | export const SNIPPETS_PAGE_URL = "https://github.com/spicetify/marketplace/blob/main/resources/snippets.json";
43 |
44 | export const SNIPPETS_URL = "https://raw.githubusercontent.com/spicetify/marketplace/main/resources/snippets.json";
45 |
46 | export const BLACKLIST_URL = "https://raw.githubusercontent.com/spicetify/marketplace/main/resources/blacklist.json";
47 |
48 | export const RELEASES_URL = "https://github.com/spicetify/marketplace/releases";
49 |
50 | export const LATEST_RELEASE_URL = "https://api.github.com/repos/spicetify/marketplace/releases/latest";
51 |
--------------------------------------------------------------------------------
/src/logic/LaunchModals.tsx:
--------------------------------------------------------------------------------
1 | import { t } from "i18next";
2 | import React from "react";
3 | import type { Config } from "../types/marketplace-types";
4 |
5 | import type { CardProps } from "../components/Card/Card";
6 | import BackupModal from "../components/Modals/BackupModal";
7 | import ReloadModal from "../components/Modals/Reload";
8 | import SettingsModal from "../components/Modals/Settings";
9 | import SnippetModal from "../components/Modals/Snippet";
10 | import ThemeDevToolsModal from "../components/Modals/ThemeDevTools";
11 | import UpdateModal from "../components/Modals/Update";
12 |
13 | export type ModalType = "ADD_SNIPPET" | "EDIT_SNIPPET" | "VIEW_SNIPPET" | "RELOAD" | "SETTINGS" | "THEME_DEV_TOOLS" | "BACKUP" | "UPDATE";
14 |
15 | const getModalSettings = (
16 | modalType: ModalType,
17 | CONFIG?: Config,
18 | updateAppConfig?: (CONFIG: Config) => void,
19 | props?: CardProps,
20 | callback?: () => void
21 | ) => {
22 | switch (modalType) {
23 | case "ADD_SNIPPET":
24 | return {
25 | title: t("snippets.addTitle"),
26 | content: ,
27 | isLarge: true
28 | };
29 | case "EDIT_SNIPPET":
30 | return {
31 | title: t("snippets.editTitle"),
32 | content: ,
33 | isLarge: true
34 | };
35 | case "VIEW_SNIPPET":
36 | return {
37 | title: t("snippets.viewTitle"),
38 | content: ,
39 | isLarge: true
40 | };
41 | case "RELOAD":
42 | return {
43 | title: t("reloadModal.title"),
44 | content: ,
45 | isLarge: false
46 | };
47 | case "SETTINGS":
48 | return {
49 | title: t("settings.title"),
50 | // TODO: If I just use {CONFIG}, it nests it inside another object...
51 | content: void} />,
52 | isLarge: true
53 | };
54 | case "THEME_DEV_TOOLS":
55 | return {
56 | title: t("devTools.title"),
57 | content: ,
58 | isLarge: true
59 | };
60 | case "BACKUP":
61 | return {
62 | title: t("backupModal.title"),
63 | content: ,
64 | isLarge: true
65 | };
66 | case "UPDATE":
67 | return {
68 | title: t("updateModal.title"),
69 | content: ,
70 | isLarge: true
71 | };
72 |
73 | default:
74 | return {
75 | title: "",
76 | content: ,
77 | isLarge: false
78 | };
79 | }
80 | };
81 |
82 | export const openModal = (
83 | modal: ModalType,
84 | CONFIG?: Config,
85 | updateAppConfig?: (CONFIG: Config) => void,
86 | props?: CardProps,
87 | callback?: () => void
88 | ) => {
89 | const triggerModal = () => {
90 | const modalSettings = getModalSettings(modal, CONFIG, updateAppConfig, props, callback);
91 | Spicetify.PopupModal.display(modalSettings);
92 | };
93 |
94 | triggerModal();
95 | return;
96 | };
97 |
--------------------------------------------------------------------------------
/src/resources/locales/ar.json:
--------------------------------------------------------------------------------
1 | {
2 | "translation": {
3 | "settings": {
4 | "title": "إعدادات المتجر",
5 | "optionsHeading": "خيارات",
6 | "starCountLabel": "عدد النجوم",
7 | "tagsLabel": "العلامات",
8 | "showArchived": "إظهار المستودعات المؤرشفة",
9 | "devToolsLabel": "أدوات مطوري السمات",
10 | "hideInstalledLabel": "إخفاء المثبتة عند التصفح",
11 | "colourShiftLabel": "تغيير الألوان كل دقيقة",
12 | "albumArtBasedColors": "تغيير الألوان بناءً على غلاف الألبوم",
13 | "albumArtBasedColorsMode": "وضع نظام الألوان (واجهة برمجة التطبيقات الملونة (API))",
14 | "albumArtBasedColorsVibrancy": "اللون المأخوذ من غلاف الألبوم",
15 | "albumArtBasedColorsVibrancyToolTip": "مشبع قليلًا: اللون الأكثر بروزًا ولكن مع سطوع أقل بكثير \n ساطع خفيف: اللون الأكثر حيوية ولكن مع زيادة السطوع قليلًا \n بارز: اللون الأكثر بروزًا في غلاف الألبوم \n حيوي: اللون الأكثر حيوية في غلاف الألبوم",
16 | "almbumArtColorsModeToolTip": "أحادي اللون الداكن: نظام ألوان يعتمد بشكل مباشر على اللون الرئيسي المحدد، باستخدام ظلال مختلفة من اللون الرئيسي ودمج درجات الرمادي لإنشاء نظام ألوان، وهذا هو عكس نظام أحادي اللون الفاتح. \n أحادي اللون الفاتح: نظام ألوان يعتمد بشكل مباشر على اللون الرئيسي المحدد، باستخدام ظلال مختلفة من اللون الرئيسي ودمج درجات الرمادي لإنشاء نظام ألوان. ستكون خلفية أحادي اللون الفاتح هي لون المقدمة أو النص في نظام أحادي اللون الداكن والعكس صحيح. \n تناظري: نظام ألوان يعتمد على اللون الرئيسي المحدد، باستخدام الألوان المجاورة للون الرئيسي على عجلة الألوان. \n تناظري تكميلي: نظام ألوان يعتمد على اللون الرئيسي المحدد، باستخدام الألوان المجاورة للون الرئيسي على عجلة الألوان واللون التكميلي. \n ثلاثي: نظام ألوان يعتمد على اللون الرئيسي المحدد، باستخدام الألوان الموجودة على عجلة الألوان والتي تكون متساوية البعد عن اللون الرئيسي. \n رباعي: نظام ألوان يعتمد على اللون الرئيسي المحدد، باستخدام الألوان الموجودة على عجلة الألوان والتي تبعد 90 درجة عن اللون الرئيسي.",
17 | "tabsHeading": "علامات التبويب",
18 | "tabsDescription": "اسحب وأفلت لإعادة الترتيب، انقر للتمكين/التعطيل",
19 | "resetHeading": "إعادة الضبط",
20 | "resetBtn": "$t(settings.resetHeading)",
21 | "resetDescription": "إلغاء تثبيت جميع الإضافات والسمات، وإعادة ضبط التفضيلات",
22 | "backupHeading": "نسخ احتياطي/استعادة",
23 | "backupLabel": "نسخ احتياطي أو استعادة جميع بيانات المتجر. لا يتضمن ذلك إعدادات أي شيء تم تثبيته عبر المتجر.",
24 | "backupBtn": "فتح",
25 | "versionHeading": "الإصدار",
26 | "versionBtn": "نسخ",
27 | "versionCopied": "تم النسخ"
28 | },
29 | "tabs": {
30 | "Extensions": "الإضافات",
31 | "Themes": "السمات",
32 | "Snippets": "المقتطفات",
33 | "Apps": "التطبيقات",
34 | "Installed": "المثبتة"
35 | },
36 | "snippets": {
37 | "addTitle": "إضافة مقتطف",
38 | "duplicateName": "هذا الاسم مستخدم بالفعل!",
39 | "editTitle": "تعديل مقتطف",
40 | "viewTitle": "عرض مقتطف",
41 | "customCSS": "CSS مخصص",
42 | "customCSSPlaceholder": "أدخل CSS المخصص الخاص بك هنا! يمكنك العثور عليها في علامة التبويب المثبتة للإدارة.",
43 | "snippetName": "اسم المقتطف",
44 | "snippetNamePlaceholder": "أدخل اسمًا للمقتطف المخصص الخاص بك",
45 | "snippetDesc": "وصف المقتطف",
46 | "snippetDescPlaceholder": "أدخل وصفًا للمقتطف المخصص الخاص بك",
47 | "snippetPreview": "معاينة المقتطف",
48 | "optional": "اختياري",
49 | "addImage": "إضافة صورة",
50 | "changeImage": "تغيير الصورة",
51 | "saveCSS": "حفظ CSS"
52 | },
53 | "reloadModal": {
54 | "title": "إعادة التحميل",
55 | "description": "يجب إعادة تحميل الصفحة لإكمال هذه العملية.",
56 | "reloadNow": "إعادة التحميل الآن",
57 | "reloadLater": "إعادة التحميل لاحقًا"
58 | },
59 | "backupModal": {
60 | "title": "$t(settings.backupHeading)",
61 | "settingsCopied": "تم نسخ الإعدادات إلى الحافظة",
62 | "noDataPasted": "لم يتم لصق أي بيانات",
63 | "invalidJSON": "JSON غير صالح",
64 | "inputLabel": "إعدادات المتجر",
65 | "inputPlaceholder": "انسخ/ألصق إعداداتك هنا",
66 | "exportBtn": "تصدير",
67 | "importBtn": "استيراد",
68 | "fileImportBtn": "استيراد من ملف"
69 | },
70 | "devTools": {
71 | "title": "أدوات تطوير السمات",
72 | "noThemeInstalled": "خطأ: لم يتم تثبيت أي سمة من المتجر",
73 | "noThemeManifest": "خطأ: لم يتم العثور على بيان السمة",
74 | "colorIniEditor": "محرر Color.ini",
75 | "colorIniEditorPlaceholder": "[اسم-نظام-الألوان-الخاص-بك]",
76 | "invalidCSS": "CSS غير صالح"
77 | },
78 | "updateModal": {
79 | "title": "تحديث المتجر",
80 | "description": "قم بتحديث متجر سبياسيتيفي لتلقي ميزات جديدة وإصلاح الأخطاء.",
81 | "currentVersion": "الإصدار الحالي: {{إصدار}}",
82 | "latestVersion": "أحدث إصدار: {{إصدار}}",
83 | "whatsChanged": "ما الذي تغير",
84 | "seeChangelog": "عرض سجل التغييرات",
85 | "howToUpgrade": "كيفية الترقية",
86 | "viewGuide": "عرض الدليل"
87 | },
88 | "grid": {
89 | "spicetifyMarketplace": "متجر سبياسيتيفي",
90 | "newUpdate": "تحديث جديد",
91 | "addCSS": "إضافة CSS",
92 | "search": "بحث",
93 | "installed": "مثبتة",
94 | "lastUpdated": "آخر تحديث {{val, datetime}}",
95 | "externalJS": "JS خارجي",
96 | "archived": "مؤرشفة",
97 | "dark": "داكن",
98 | "light": "فاتح",
99 | "sort": {
100 | "label": "فرز حسب:",
101 | "stars": "النجوم",
102 | "newest": "الأحدث",
103 | "oldest": "الأقدم",
104 | "lastUpdated": "آخر تحديث",
105 | "mostStale": "الأكثر تقادمًا",
106 | "aToZ": "أ-ي",
107 | "zToA": "ي-أ"
108 | }
109 | },
110 | "readmePage": {
111 | "title": "$t(grid.spicetifyMarketplace) - اقرأني",
112 | "loading": "جارٍ التحميل...",
113 | "errorLoading": "README خطأ في تحميل ملف اقرأني"
114 | },
115 | "github": "غيثب",
116 | "install": "تثبيت",
117 | "remove": "إزالة",
118 | "save": "حفظ",
119 | "colour_one": "لون",
120 | "colour_other": "ألوان",
121 | "favourite": "مفضل",
122 | "notifications": {
123 | "wrongLocalTheme": "يُرجى ضبط السمة الحالية في config-xpui.ini على ” المتجر“ لتثبيت السمات باستخدام المتجر",
124 | "tooManyRequests": "عدد كبير جدًا من الطلبات، يرجى الانتظار",
125 | "noCdnConnection": "المتجر غير قادر على الاتصال بشبكة CDN. يرجى التحقق من إعدادات الإنترنت الخاصة بك",
126 | "markdownParsingError": "خطأ في تحليل Markdown (HTTP {{status}})",
127 | "noReadmeFile": "لم يتم العثور على ملف اقرأني README",
128 | "themeInstallationError": "حدث خطأ أثناء تثبيت السمة",
129 | "extensionInstallationError": "حدث خطأ أثناء تثبيت الإضافة"
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/resources/locales/ca.json:
--------------------------------------------------------------------------------
1 | {
2 | "translation": {
3 | "settings": {
4 | "title": "Configuració",
5 | "optionsHeading": "Opcions",
6 | "starCountLabel": "Número d'estrelles",
7 | "tagsLabel": "Etiquetes",
8 | "devToolsLabel": "Eines per a desenvolupadors de temes",
9 | "hideInstalledLabel": "Amagar instal·lats",
10 | "colourShiftLabel": "Canviar colors cada minut",
11 | "albumArtBasedColors": "Canviar colors a partir de la portada de l'àlbum",
12 | "albumArtBasedColorsMode": "Mode esquema de colors (ColorApi)",
13 | "albumArtBasedColorsVibrancy": "Colors agafats de la portada de l'àlbum",
14 | "albumArtBasedColorsVibrancyToolTip": "Desaturat:El color més destacat però amb molta menys bror \n Vibrant Clar: El color més villantibrant amb la brillantor augmentada una mica \n Prominent: El color més destacat a la portada de l'Àlbum \n Vibrant: El color més vibrant a la portada de l'Àlbum",
15 | "almbumArtColorsModeToolTip": "Monochrome Dark: Un esquema de colors basat en el color principal seleccionat, emprant diferentes tonalitats i barrejant tons grisos per crear l'esquema de colors, aquest és l'invers de Monochrome Light. \n Monochrome Light: Un esquema de colors basat en el color principal seleccionat, emprant diferentes tonalitats i barrejant tons grisos per crear l'esquema de colors. El colors del fins de Monochrome light seria el color de primer pla en Monochrome Dark i viceversa. \n Analògic: Un esquema de colors basat en el color principal seleccionat, emprant els colors adjacents en la roda de colors. \n Analògic Complementari: Un esquema de colors basat en el color principal seleccionat, emprant els colors adjacents en la roda de colors i el color complementari. \n Tríada: Un esquema de colors basat en el color principal seleccionat, emprant els colors de la roda de colors que estan separats de manera equidistant del color principal. \n Quad: Un esquema de colors basat en el color principal seleccionat, emprant els colors que es troben separats 90 graus entre si en la roda de colors.",
16 | "tabsHeading": "Pestanyes",
17 | "tabsDescription": "Arrossegueu i deixeu anar per canviar l'ordre, feu clic per activar/desactivar",
18 | "resetHeading": "Restablir",
19 | "resetBtn": "$t(settings.resetHeading)",
20 | "resetDescription": "Borrar totes les extensions, temes i preferències",
21 | "backupHeading": "Fer una còpia/Reestablir des d'una còpia",
22 | "backupLabel": "Fer una còpia o restablir totes les dades de Marketplace des d'una còpia. Això no inclou la configuració per els elements instal·lats amb Marketplace.",
23 | "backupBtn": "Obrir",
24 | "versionHeading": "Versió",
25 | "versionBtn": "Copiar",
26 | "versionCopied": "Copiat"
27 | },
28 | "tabs": {
29 | "Extensions": "Extensions",
30 | "Themes": "Temes",
31 | "Snippets": "Fragments",
32 | "Apps": "Aplicacions",
33 | "Installed": "Instal·lats"
34 | },
35 | "snippets": {
36 | "addTitle": "Afegir fragment",
37 | "editTitle": "Editar fragment",
38 | "viewTitle": "Veure fragment",
39 | "customCSS": "CSS personalitzat",
40 | "customCSSPlaceholder": "Crea el teu propi CSS aqui! Pots trobar-los a la pestanya d'instal·lats per administrar-los.",
41 | "snippetName": "Nom del fragment de codi",
42 | "snippetNamePlaceholder": "Afegeix un nom al teu codi personalitzat",
43 | "snippetDesc": "Descripció del codi",
44 | "snippetDescPlaceholder": "Crea una descripció per al teu codi personalitzat",
45 | "snippetPreview": "Vista prèvia del fragment",
46 | "optional": "Opcional",
47 | "addImage": "Afegir imatge",
48 | "changeImage": "Canviar imatge",
49 | "saveCSS": "Guardar CSS"
50 | },
51 | "reloadModal": {
52 | "title": "Recarregar",
53 | "description": "És necessari recarregar la finestra per completar aquesta operació.",
54 | "reloadNow": "Fes-ho ara",
55 | "reloadLater": "Després"
56 | },
57 | "backupModal": {
58 | "title": "$t(settings.backupHeading)",
59 | "settingsCopied": "Configuració copiada al portapapers",
60 | "noDataPasted": "No s'han enganxat dades",
61 | "invalidJSON": "JSON invàlid",
62 | "inputLabel": "Configuració de Marketplace",
63 | "inputPlaceholder": "Còpia/enganxa la teva configuració aquí",
64 | "exportBtn": "Exportar",
65 | "importBtn": "Importar",
66 | "fileImportBtn": "Importar des d'un arxiu"
67 | },
68 | "devTools": {
69 | "title": "Eines de desenvolupador de temes",
70 | "noThemeInstalled": "Error: No hi ha cap tema de Marketplace instal·lat",
71 | "noThemeManifest": "Error: No s'ha trobat el manifest",
72 | "colorIniEditor": "Editor de Color.ini",
73 | "colorIniEditorPlaceholder": "[nom-de-esquema-de-color]",
74 | "invalidCSS": "CSS invàlid"
75 | },
76 | "grid": {
77 | "spicetifyMarketplace": "Marketplace de Spicetify",
78 | "newUpdate": "Nova Actualització",
79 | "addCSS": "Afegir CSS",
80 | "search": "Buscar",
81 | "installed": "Instal·lat",
82 | "lastUpdated": "Última actualizació {{val, datetime}}",
83 | "externalJS": "JS extern",
84 | "dark": "fosc",
85 | "light": "clar"
86 | },
87 | "readmePage": {
88 | "title": "$t(grid.spicetifyMarketplace) - Readme",
89 | "loading": "Carregant...",
90 | "errorLoading": "Error carregant el README"
91 | },
92 | "github": "GitHub",
93 | "install": "Instal·lar",
94 | "remove": "Borrar",
95 | "save": "Guardar",
96 | "colour_one": "color",
97 | "colour_other": "colors",
98 | "favourite": "preferit"
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/resources/locales/en-US.json:
--------------------------------------------------------------------------------
1 | {
2 | "translation": {
3 | "settings": {
4 | "colourShiftLabel": "Shift colors every minute",
5 | "albumArtBasedColors": "Change colors based on album art",
6 | "albumArtBasedColorsMode": "Color scheme (ColorApi) mode",
7 | "albumArtBasedColorsVibrancy": "Color grabbed from album art",
8 | "albumArtBasedColorsVibrancyToolTip": "Desaturated: The color that is the most prominent but with much less brightness \n Light Vibrant: The most Vibrant color but with the brightness amped up a tad \n Prominent: The color that pops the most in the album art \n Vibrant: The most vibrant color in the album art",
9 | "almbumArtColorsModeToolTip": "Monochrome Dark: A color scheme based directly on the main color selected, using different shades of the main color and mixing in greys to create a color scheme, this is the inverse of Monochrome Light. \n Monochrome Light: A color scheme based directly on the main color selected, using different shades of the main color and mixing in greys to create a color scheme. The background of monochrome light would be the foreground or text color on Monochrome Dark and vice versa. \n Analogic: A color scheme based on the main color selected, using the colors adjacent to the main color on the color wheel. \n Analogic Complementary: A color scheme based on the main color selected, using the colors adjacent to the main color on the color wheel and the complementary color. \n Triad: A color scheme based on the main color selected, using the colors on the color wheel that are equidistant from the main color. \n Quad: A color scheme based on the main color selected, using the colors on the color wheel that are 90 degrees from the main color."
10 | },
11 | "devTools": {
12 | "colorIniEditorPlaceholder": "[your-color-scheme-name]"
13 | },
14 | "colour_one": "color",
15 | "colour_other": "colors",
16 | "favourite": "favorite"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/resources/locales/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "translation": {
3 | "settings": {
4 | "title": "Marketplace Settings",
5 | "optionsHeading": "Options",
6 | "starCountLabel": "Stars count",
7 | "tagsLabel": "Tags",
8 | "showArchived": "Show archived repos",
9 | "devToolsLabel": "Theme developer tools",
10 | "hideInstalledLabel": "Hide installed when browsing",
11 | "colourShiftLabel": "Shift colours every minute",
12 | "albumArtBasedColors": "Change colours based on album art",
13 | "albumArtBasedColorsMode": "Colour scheme (ColorApi) mode",
14 | "albumArtBasedColorsVibrancy": "Colour grabbed from album art",
15 | "albumArtBasedColorsVibrancyToolTip": "Desaturated: The colour that is the most prominent but with much less brightness \n Light Vibrant: The most Vibrant colour but with the brightness amped up a tad \n Prominent: The colour that pops the most in the album art \n Vibrant: The most vibrant colour in the album art",
16 | "almbumArtColorsModeToolTip": "Monochrome Dark: A colour scheme based directly on the main colour selected, using different shades of the main colour and mixing in greys to create a colour scheme, this is the inverse of Monochrome Light. \n Monochrome Light: A colour scheme based directly on the main colour selected, using different shades of the main colour and mixing in greys to create a colour scheme. The background of monochrome light would be the foreground or text colour on Monochrome Dark and vice versa. \n Analogic: A colour scheme based on the main colour selected, using the colours adjacent to the main colour on the colour wheel. \n Analogic Complementary: A colour scheme based on the main colour selected, using the colours adjacent to the main colour on the colour wheel and the complementary colour. \n Triad: A colour scheme based on the main colour selected, using the colours on the colour wheel that are equidistant from the main colour. \n Quad: A colour scheme based on the main colour selected, using the colours on the colour wheel that are 90 degrees from the main colour.",
17 | "tabsHeading": "Tabs",
18 | "tabsDescription": "Drag and drop to reorder, click to enable/disable",
19 | "resetHeading": "Reset",
20 | "resetBtn": "$t(settings.resetHeading)",
21 | "resetDescription": "Uninstall all extensions and themes, and reset preferences",
22 | "backupHeading": "Back up/Restore",
23 | "backupLabel": "Back up or restore all Marketplace data. This does not include settings for anything installed via Marketplace.",
24 | "backupBtn": "Open",
25 | "versionHeading": "Version",
26 | "versionBtn": "Copy",
27 | "versionCopied": "Copied"
28 | },
29 | "tabs": {
30 | "Extensions": "Extensions",
31 | "Themes": "Themes",
32 | "Snippets": "Snippets",
33 | "Apps": "Apps",
34 | "Installed": "Installed"
35 | },
36 | "snippets": {
37 | "addTitle": "Add Snippet",
38 | "duplicateName": "That name is already taken!",
39 | "editTitle": "Edit Snippet",
40 | "viewTitle": "View Snippet",
41 | "customCSS": "Custom CSS",
42 | "customCSSPlaceholder": "Input your own custom CSS here! You can find them in the installed tab for management.",
43 | "snippetName": "Snippet Name",
44 | "snippetNamePlaceholder": "Enter a name for your custom snippet",
45 | "snippetDesc": "Snippet Description",
46 | "snippetDescPlaceholder": "Enter a description for your custom snippet",
47 | "snippetPreview": "Snippet Preview",
48 | "optional": "Optional",
49 | "addImage": "Add image",
50 | "changeImage": "Change image",
51 | "saveCSS": "Save CSS"
52 | },
53 | "reloadModal": {
54 | "title": "Reload",
55 | "description": "A page reload is required to complete this operation.",
56 | "reloadNow": "Reload now",
57 | "reloadLater": "Reload later"
58 | },
59 | "backupModal": {
60 | "title": "$t(settings.backupHeading)",
61 | "settingsCopied": "Settings copied to clipboard",
62 | "noDataPasted": "No data pasted",
63 | "invalidJSON": "Invalid JSON",
64 | "inputLabel": "Marketplace Settings",
65 | "inputPlaceholder": "Copy/paste your settings here",
66 | "exportBtn": "Export",
67 | "importBtn": "Import",
68 | "fileImportBtn": "Import from file"
69 | },
70 | "devTools": {
71 | "title": "Theme Dev Tools",
72 | "noThemeInstalled": "Error: No marketplace theme installed",
73 | "noThemeManifest": "Error: No theme manifest found",
74 | "colorIniEditor": "Color.ini Editor",
75 | "colorIniEditorPlaceholder": "[your-colour-scheme-name]",
76 | "invalidCSS": "Invalid CSS"
77 | },
78 | "updateModal": {
79 | "title": "Update the Marketplace",
80 | "description": "Update Spicetify Marketplace to receive new features and bug fixes.",
81 | "currentVersion": "Current version: {{version}}",
82 | "latestVersion": "Latest version: {{version}}",
83 | "whatsChanged": "What's Changed",
84 | "seeChangelog": "See changelog",
85 | "howToUpgrade": "How to upgrade",
86 | "viewGuide": "View guide"
87 | },
88 | "grid": {
89 | "spicetifyMarketplace": "Spicetify Marketplace",
90 | "newUpdate": "New update",
91 | "addCSS": "Add CSS",
92 | "search": "Search",
93 | "installed": "Installed",
94 | "lastUpdated": "Last updated {{val, datetime}}",
95 | "externalJS": "external JS",
96 | "archived": "archived",
97 | "dark": "dark",
98 | "light": "light",
99 | "sort": {
100 | "label": "Sort by:",
101 | "stars": "Stars",
102 | "newest": "Newest",
103 | "oldest": "Oldest",
104 | "lastUpdated": "Last Updated",
105 | "mostStale": "Most Stale",
106 | "aToZ": "A-Z",
107 | "zToA": "Z-A"
108 | }
109 | },
110 | "readmePage": {
111 | "title": "$t(grid.spicetifyMarketplace) - Readme",
112 | "loading": "Loading...",
113 | "errorLoading": "Error loading README"
114 | },
115 | "github": "GitHub",
116 | "install": "Install",
117 | "remove": "Remove",
118 | "save": "Save",
119 | "colour_one": "colour",
120 | "colour_other": "colours",
121 | "favourite": "favourite",
122 | "notifications": {
123 | "wrongLocalTheme": "Please set current_theme in config-xpui.ini to 'marketplace' to install themes using the Marketplace",
124 | "tooManyRequests": "Too many requests, cool down",
125 | "noCdnConnection": "Marketplace is unable to connect to the CDN. Please check your Internet configuration",
126 | "markdownParsingError": "Error parsing markdown (HTTP {{status}})",
127 | "noReadmeFile": "No README was found",
128 | "themeInstallationError": "There was an error installing theme",
129 | "extensionInstallationError": "There was an error installing extension"
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/resources/locales/et.json:
--------------------------------------------------------------------------------
1 | {
2 | "translation": {
3 | "settings": {
4 | "title": "Turu seaded",
5 | "optionsHeading": "Seaded",
6 | "starCountLabel": "Tähtede arv",
7 | "tagsLabel": "Sildid",
8 | "devToolsLabel": "Teema arendaja tööriistad",
9 | "hideInstalledLabel": "Peida sirvimisel paigaldatud",
10 | "colourShiftLabel": "Muutke värve iga minut",
11 | "albumArtBasedColors": "Muutke värve albumipildi põhjal",
12 | "albumArtBasedColorsMode": "Värviskeemi (ColorApi) režiim",
13 | "albumArtBasedColorsVibrancy": "Albumipildilt haaratud värv",
14 | "albumArtBasedColorsVibrancyToolTip": "Desaturated: Värv, mis on kõige silmatorkavam, kuid palju väiksema heledusega \n Light vibrant: Kõige erksam värv, kuid veidi suurendatud heledusega \n Prominent: Värv, mis ilmub albumi kujunduses kõige rohkem \n Vibrant: Albumipildi kõige elavam värv",
15 | "almbumArtColorsModeToolTip": "Monochrome dark: Värvilahendus, mis põhineb otse valitud põhivärvil, kasutades põhivärvi erinevaid toone ja segades värviskeemi loomiseks halle, see on ühevärvlise heleda pöördväärtus. \n Monochrome light: Värvilahendus, mis põhineb otse valitud põhivärvil, kasutades põhivärvi erinevaid toone ja segades värviskeemi loomiseks halle. Ühevärvilise valguse taust oleks ühevärvilise tumeda esiplaani või teksti värv ja vastupidi. \n Analogic: Valitud põhivärvil põhinev värviskeem, kasutades värviratta põhivärviga külgnevaid värve. \n Analogic complement: Valitud põhivärvil põhinev värviskeem, kasutades värviratta põhivärviga külgnevaid värve ja lisavärvi. \n Triad: Valitud põhivärvil põhinev värviskeem, kasutades põhivärvist võrdsel kaugusel asuvaid värviratta värve. \n Quad: Valitud põhivärvil põhinev värviskeem, kasutades värvirattal olevaid värve, mis on põhivärvist 90 kraadi.",
16 | "tabsHeading": "Vahekaardid",
17 | "tabsDescription": "Järjekorra muutmiseks lohista ja eemalda, lubamiseks/välja lülitamiseks klõpsa",
18 | "resetHeading": "Reset",
19 | "resetBtn": "$t(settings.resetHeading)",
20 | "resetDescription": "Uninstall all extensions and themes, and reset preferences",
21 | "backupHeading": "Varunda/Taasta",
22 | "backupLabel": "Varunda või taasta kõik turu andmed. See ei hõlma turu kaudu paigaldatud elementide seadeid.",
23 | "backupBtn": "Ava",
24 | "versionHeading": "Versioon",
25 | "versionBtn": "Kopeeri",
26 | "versionCopied": "Kopeeritud"
27 | },
28 | "tabs": {
29 | "Extensions": "Lisad",
30 | "Themes": "Teemad",
31 | "Snippets": "Katked",
32 | "Apps": "Rakendused",
33 | "Installed": "Paigaldatud"
34 | },
35 | "snippets": {
36 | "addTitle": "Lisa katkend",
37 | "editTitle": "Muuda katkendit",
38 | "viewTitle": "Vaata katkendit",
39 | "customCSS": "Kohandatud CSS",
40 | "customCSSPlaceholder": "Paigalda Kohandatud CSS siia! Haldamiseks leiate need paigaldatud vahekaardilt.",
41 | "snippetName": "Katkendi nimi",
42 | "snippetNamePlaceholder": "Lisa kohandatud katkendi nimi",
43 | "snippetDesc": "Katkendi kirjeldus",
44 | "snippetDescPlaceholder": "Lisa kohandatud katkendi kirjeldus",
45 | "snippetPreview": "Katkendi eelvaade",
46 | "optional": "valikuline",
47 | "addImage": "Lisa pilt",
48 | "changeImage": "Muuda pilti",
49 | "saveCSS": "Salvesta CSS"
50 | },
51 | "reloadModal": {
52 | "title": "Laadi uuesti",
53 | "description": "Selle toimingu lõpuleviimiseks on vaja leht uuesti laadida.",
54 | "reloadNow": "Laadige kohe uuesti",
55 | "reloadLater": "Laadige hiljem uuesti"
56 | },
57 | "backupModal": {
58 | "title": "$t(settings.backupHeading)",
59 | "settingsCopied": "Seaded kopeeriti lõikelauale",
60 | "noDataPasted": "Andmeid pole kleebitud",
61 | "invalidJSON": "Vale JSON",
62 | "inputLabel": "Turu Seaded",
63 | "inputPlaceholder": "Kopeeri/kleebi enda seaded siia",
64 | "exportBtn": "Ekspordi",
65 | "importBtn": "Impordi",
66 | "fileImportBtn": "Impordi failist"
67 | },
68 | "devTools": {
69 | "title": "Teema arendustööriistad",
70 | "noThemeInstalled": "Viga: Turu teemat pole installitud",
71 | "noThemeManifest": "Viga: Teema manifesti ei leitud",
72 | "colorIniEditor": "Color.ini redaktor",
73 | "colorIniEditorPlaceholder": "[teie-värviskeemi-nimi]",
74 | "invalidCSS": "Vigane CSS"
75 | },
76 | "grid": {
77 | "spicetifyMarketplace": "Spicetify Turg",
78 | "newUpdate": "Uus värskendus",
79 | "addCSS": "Lisa CSS",
80 | "search": "Otsi",
81 | "installed": "Paigaldatud",
82 | "lastUpdated": "Viimati uuendatud {{val, datetime}}",
83 | "externalJS": "väline JS",
84 | "dark": "tume",
85 | "light": "hele"
86 | },
87 | "readmePage": {
88 | "title": "$t(grid.spicetifyMarketplace) - Readme",
89 | "loading": "Laadimine...",
90 | "errorLoading": "Viga README laadimisel"
91 | },
92 | "github": "GitHub",
93 | "install": "Paigalda",
94 | "remove": "Eemalda",
95 | "save": "Salvesta",
96 | "colour_one": "värv",
97 | "colour_other": "värvid",
98 | "favourite": "lemmik"
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/resources/locales/fr.json:
--------------------------------------------------------------------------------
1 | {
2 | "translation": {
3 | "settings": {
4 | "title": "Réglages Marché Spicetify",
5 | "optionsHeading": "Options",
6 | "starCountLabel": "Nombres d’étoiles",
7 | "tagsLabel": "Tags",
8 | "devToolsLabel": "Outils pour les développeurs de thèmes",
9 | "hideInstalledLabel": "Masquer ceux étant installés lors de la navigation",
10 | "colourShiftLabel": "Changer de couleur chaque minutes",
11 | "albumArtBasedColors": "Changement des couleurs basé sur les pochettes d'albums",
12 | "albumArtBasedColorsMode": "Mode de schéma de couleur (ColorApi)",
13 | "albumArtBasedColorsVibrancy": "Couleur saisie depuis les pochettes d'albums",
14 | "albumArtBasedColorsVibrancyToolTip": "Désaturé: La couleur qui est la plus proéminente mais avec beaucoup moins de luminosité\nVibrations Claires: La couleur la plus vibrante, mais avec une luminosité un peu plus forte\nPrometteur: La couleur qui ressort le plus dans la pochette de l'album\nVibrations: La couleur la plus vibrante dans la pochette de l'album",
15 | "albumArtColorsModeToolTip": "Monochrome foncé: une palette de couleurs basée directement sur la couleur principale sélectionnée, en utilisant différentes nuances de la couleur principale et en mélangeant des gris pour créer une palette de couleurs, c'est l'inverse du monochrome clair.\nMonochrome clair: Une palette de couleurs basée directement sur la couleur principale sélectionnée, en utilisant différentes nuances de la couleur principale et en mélangeant les gris pour créer une palette de couleurs. L'arrière-plan d'un monochrome clair sera le premier plan ou la couleur du texte d'un monochrome foncé et vice versa.\nAnalogique: Schéma de couleurs basé sur la couleur principale sélectionnée, utilisant les couleurs adjacentes à la couleur principale sur le cercle chromatique.\nAnalogique complémentaire: Un schéma de couleurs basé sur la couleur principale sélectionnée, utilisant les couleurs adjacentes à la couleur principale sur le cercle chromatique et la couleur complémentaire.\nTriade: Un schéma de couleurs basé sur la couleur principale sélectionnée, utilisant les couleurs équidistantes de la couleur principale sur le cercle chromatique.\nQuad: Un schéma de couleurs basé sur la couleur principale sélectionnée, utilisant les couleurs du cercle chromatique qui sont à 90 degrés de la couleur principale.",
16 | "tabsHeading": "Onglets",
17 | "tabsDescription": "Glisser-déposer pour modifier l'ordre, cliquer pour activer/désactiver",
18 | "resetHeading": "Réinitialiser",
19 | "resetBtn": "$t(settings.resetHeading)",
20 | "resetDescription": "Désinstaller toutes les extensions et tous les thèmes, ainsi que l’ensemble des réglages",
21 | "backupHeading": "Sauvegarde/Restauration",
22 | "backupLabel": "Sauvegarder ou restaurer toutes les données du Marché. Celà n'inclue pas les réglages pour quoi que ce soit installé depuis le Marché.",
23 | "backupBtn": "Ouvrir",
24 | "versionHeading": "Version",
25 | "versionBtn": "Copier",
26 | "versionCopied": "Copié"
27 | },
28 | "tabs": {
29 | "Extensions": "Extensions",
30 | "Themes": "Thèmes",
31 | "Snippets": "Bribes",
32 | "Apps": "Applications",
33 | "Installed": "Installé(s)"
34 | },
35 | "snippets": {
36 | "addTitle": "Ajouter Bribe",
37 | "editTitle": "Éditer Bribe",
38 | "viewTitle": "Voir Bribe",
39 | "customCSS": "CSS personnalisé",
40 | "customCSSPlaceholder": "Insérez votre propre CSS personnalisé ici! Vous pouvez les retrouver dans l’onglet Installé pour les gérer.",
41 | "snippetName": "Nom de la bribe",
42 | "snippetNamePlaceholder": "Entrer un nom pour votre bribe personnalisée",
43 | "snippetDesc": "Description de la bribe",
44 | "snippetDescPlaceholder": "Entrez une description pour votre bribe personnalisée",
45 | "snippetPreview": "Prévisualiser la bribe",
46 | "optional": "Optionnel",
47 | "addImage": "Ajouter une image",
48 | "changeImage": "Changer l’image",
49 | "saveCSS": "Enregistrer le CSS"
50 | },
51 | "reloadModal": {
52 | "title": "Recharger",
53 | "description": "Un rechargement de la page est requis pour finaliser cette opération.",
54 | "reloadNow": "Recharger maintenant",
55 | "reloadLater": "Recharger plus tard"
56 | },
57 | "backupModal": {
58 | "title": "Sauvegarder/Restaurer",
59 | "settingsCopied": "Réglages copiés dans le presse-papier",
60 | "noDataPasted": "Aucune donnée collée",
61 | "invalidJSON": "JSON invalide",
62 | "inputLabel": "Réglages du Marché",
63 | "inputPlaceholder": "Copier/coller vos réglages ici",
64 | "exportBtn": "Exporter",
65 | "importBtn": "Importer",
66 | "fileImportBtn": "Importer depuis un fichier"
67 | },
68 | "devTools": {
69 | "title": "Outils de développeurs de thèmes",
70 | "noThemeInstalled": "Erreur: Aucun thème du marché n’est installé",
71 | "noThemeManifest": "Erreur: Aucun manifeste de thème trouvé",
72 | "colorIniEditor": "Éditeur Color.ini",
73 | "colorIniEditorPlaceholder": "[nom-de-votre-schéma-de-couleur]",
74 | "invalidCSS": "CSS invalide"
75 | },
76 | "grid": {
77 | "spicetifyMarketplace": "Marché Spicetify",
78 | "newUpdate": "Nouvelle mise à jour",
79 | "addCSS": "Ajouter CSS",
80 | "search": "Rechercher",
81 | "installed": "Installé",
82 | "lastUpdated": "Dernière mise à jour {{val, datetime}}",
83 | "externalJS": "JS externe",
84 | "dark": "sombre",
85 | "light": "clair",
86 | "sort": {
87 | "label": "Trier par:",
88 | "stars": "Étoiles",
89 | "newest": "Nouveauté",
90 | "oldest": "Ancienneté",
91 | "lastUpdated": "Dernière mise à jour",
92 | "mostStale": "Le plus périmé",
93 | "aToZ": "A-Z",
94 | "zToA": "Z-A"
95 | }
96 | },
97 | "readmePage": {
98 | "title": "$t(grid.spicetifyMarketplace) - Readme",
99 | "loading": "Chargement…",
100 | "errorLoading": "Erreur lors du chargement du README"
101 | },
102 | "github": "GitHub",
103 | "install": "Installer",
104 | "remove": "Supprimer",
105 | "save": "Enregistrer",
106 | "colour_one": "couleur",
107 | "colour_other": "couleurs",
108 | "favourite": "favoris"
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/resources/locales/index.ts:
--------------------------------------------------------------------------------
1 | import ar from "./ar.json";
2 | import ca from "./ca.json";
3 | import deDE from "./de-DE.json";
4 | import enUS from "./en-US.json";
5 | import en from "./en.json";
6 | import es from "./es.json";
7 | import et from "./et.json";
8 | import fr from "./fr.json";
9 | import it from "./it.json";
10 | import ja from "./ja.json";
11 | import ko from "./ko.json";
12 | import pl from "./pl.json";
13 | import ptBR from "./pt-BR.json";
14 | import ru from "./ru.json";
15 | import uk from "./uk.json";
16 | import zhCN from "./zh-CN.json";
17 | import zhTW from "./zh-TW.json";
18 |
19 | export default {
20 | ar,
21 | ca,
22 | "de-DE": deDE,
23 | "en-US": enUS,
24 | en,
25 | es,
26 | et,
27 | fr,
28 | it,
29 | ja,
30 | ko,
31 | pl,
32 | "pt-BR": ptBR,
33 | ru,
34 | uk,
35 | "zh-CN": zhCN,
36 | "zh-TW": zhTW
37 | };
38 |
--------------------------------------------------------------------------------
/src/resources/locales/ja.json:
--------------------------------------------------------------------------------
1 | {
2 | "translation": {
3 | "settings": {
4 | "title": "マーケットプレイスの設定",
5 | "optionsHeading": "オプション",
6 | "starCountLabel": "スターの数",
7 | "tagsLabel": "タグ",
8 | "showArchived": "アーカイブされたリポジトリを表示",
9 | "devToolsLabel": "テーマ開発者ツール",
10 | "hideInstalledLabel": "ブラウジング時にインストール済みを非表示にする",
11 | "colourShiftLabel": "1分ごとに色を変更",
12 | "albumArtBasedColors": "アルバムアートに基づいて色を変更",
13 | "albumArtBasedColorsMode": "カラースキーム(ColorApi)モード",
14 | "albumArtBasedColorsVibrancy": "アルバムアートから取得した色",
15 | "albumArtBasedColorsVibrancyToolTip": "Desaturated: 最も目立つ色だが、明るさがはるかに抑えられています \n Light Vibrant: 最も活気ある色ですが、明るさが少し増しています \n Prominent: アルバムアートで最も目立つ色です \n Vibrant: アルバムアートで最も鮮やかな色です",
16 | "almbumArtColorsModeToolTip": "Monochrome Dark: 選択したメインカラーを基にした色の配色スキームで、メインカラーの異なる濃淡やグレーを混ぜて配色することで、これはMonochrome Lightの反対です。 \n Monochrome Light: 選択したメインカラーを直接基にした色の配色スキームで、メインカラーの異なる濃淡やグレーを混ぜて配色します。Monochrome Lightの背景色は、Monochrome Darkの前景色やテキスト色となり、その逆も同様です。 \n Analogic: 選択されたメインカラーを基に、カラーホイール上でメインカラーに隣接する色を使用した配色スキームです。 \n Analogic Complementary: 選択したメインカラーを基に、カラーホイール上でメインカラーに隣接する色と補色を使用した配色スキームです。 \n Triad: 選択したメインカラーを基に、カラーホイール上でメインカラーから等距離にある色を使用した配色スキームです。 \n Quad: 選択されたメインカラーを基に、カラーホイール上でメインカラーから90度離れた色を使用した配色スキームです。",
17 | "tabsHeading": "タブ",
18 | "tabsDescription": "ドラッグ&ドロップで順序を変更し、クリックで有効/無効を切り替える",
19 | "resetHeading": "リセット",
20 | "resetBtn": "$t(settings.resetHeading)",
21 | "resetDescription": "すべての拡張機能とテーマをアンインストールし、設定をリセットします",
22 | "backupHeading": "バックアップ/リストア",
23 | "backupLabel": "すべてのマーケットプレイスデータのバックアップまたはリストアを行います。これには、マーケットプレイスを介してインストールされた設定は含まれません。",
24 | "backupBtn": "開く",
25 | "versionHeading": "バージョン",
26 | "versionBtn": "コピー",
27 | "versionCopied": "コピーされました"
28 | },
29 | "tabs": {
30 | "Extensions": "拡張機能",
31 | "Themes": "テーマ",
32 | "Snippets": "スニペット",
33 | "Apps": "アプリ",
34 | "Installed": "インストール済み"
35 | },
36 | "snippets": {
37 | "addTitle": "スニペットを追加",
38 | "duplicateName": "その名前は既に使われています!",
39 | "editTitle": "スニペットを編集",
40 | "viewTitle": "スニペットを表示",
41 | "customCSS": "カスタムCSS",
42 | "customCSSPlaceholder": "ここにカスタムCSSを入力してください! 管理用のインストール済みタブで見つけることができます。",
43 | "snippetName": "スニペット名",
44 | "snippetNamePlaceholder": "カスタムスニペットの名前を入力してください",
45 | "snippetDesc": "スニペットの説明",
46 | "snippetDescPlaceholder": "カスタムスニペットの説明を入力してください",
47 | "snippetPreview": "スニペットプレビュー",
48 | "optional": "オプション",
49 | "addImage": "画像を追加",
50 | "changeImage": "画像を変更",
51 | "saveCSS": "CSSを保存"
52 | },
53 | "reloadModal": {
54 | "title": "リロード",
55 | "description": "この操作を完了するにはページのリロードが必要です。",
56 | "reloadNow": "今すぐ読み込む",
57 | "reloadLater": "後で読み込む"
58 | },
59 | "backupModal": {
60 | "title": "$t(settings.backupHeading)",
61 | "settingsCopied": "設定がクリップボードにコピーされました",
62 | "noDataPasted": "データが貼り付けられていません",
63 | "invalidJSON": "無効なJSON",
64 | "inputLabel": "マーケットプレイスの設定",
65 | "inputPlaceholder": "ここに設定をコピー&ペーストしてください",
66 | "exportBtn": "エクスポート",
67 | "importBtn": "インポート",
68 | "fileImportBtn": "ファイルからインポート"
69 | },
70 | "devTools": {
71 | "title": "テーマ開発ツール",
72 | "noThemeInstalled": "エラー:マーケットプレイスのテーマがインストールされていません",
73 | "noThemeManifest": "エラー:テーママニフェストが見つかりませんでした",
74 | "colorIniEditor": "Color.ini Editor",
75 | "colorIniEditorPlaceholder": "[your-colour-scheme-name]",
76 | "invalidCSS": "無効なCSS"
77 | },
78 | "updateModal": {
79 | "title": "マーケットプレイスの更新",
80 | "description": "新機能やバグ修正を受け取るために、Spicetify Marketplaceを更新してください。",
81 | "currentVersion": "現在のバージョン: {{version}}",
82 | "latestVersion": "最新のバージョン: {{version}}",
83 | "whatsChanged": "変更内容",
84 | "seeChangelog": "変更履歴を見る",
85 | "howToUpgrade": "アップグレード方法",
86 | "viewGuide": "ガイドを見る"
87 | },
88 | "grid": {
89 | "spicetifyMarketplace": "Spicetify Marketplace",
90 | "newUpdate": "新しいアップデート",
91 | "addCSS": "CSSを追加",
92 | "search": "検索",
93 | "installed": "インストール済み",
94 | "lastUpdated": "{{val, datetime}}に最終更新",
95 | "externalJS": "外部JS",
96 | "archived": "アーカイブ済み",
97 | "dark": "ダーク",
98 | "light": "ライト",
99 | "sort": {
100 | "label": "並べ替え:",
101 | "stars": "スター",
102 | "newest": "最新",
103 | "oldest": "最古",
104 | "lastUpdated": "最終更新",
105 | "mostStale": "最も古い",
106 | "aToZ": "A-Z",
107 | "zToA": "Z-A"
108 | }
109 | },
110 | "readmePage": {
111 | "title": "$t(grid.spicetifyMarketplace) - README",
112 | "loading": "読み込み中...",
113 | "errorLoading": "READMEの読み込み中にエラーが発生しました"
114 | },
115 | "github": "GitHub",
116 | "install": "インストール",
117 | "remove": "削除",
118 | "save": "保存",
119 | "colour_one": "色",
120 | "colour_other": "色",
121 | "favourite": "お気に入り",
122 | "notifications": {
123 | "wrongLocalTheme": "config-xpui.iniのcurrent_themeを 'marketplace' に設定して、マーケットプレイスを使用してテーマをインストールしてください",
124 | "tooManyRequests": "リクエストが多すぎます。時間をおいて再試行してください",
125 | "noCdnConnection": "マーケットプレイスがCDNに接続できません。インターネットの設定を確認してください",
126 | "markdownParsingError": "Markdownの解析エラー(HTTP {{status}})",
127 | "noReadmeFile": "READMEが見つかりませんでした",
128 | "themeInstallationError": "テーマのインストール中にエラーが発生しました",
129 | "extensionInstallationError": "拡張機能のインストール中にエラーが発生しました"
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/resources/locales/ko.json:
--------------------------------------------------------------------------------
1 | {
2 | "translation": {
3 | "settings": {
4 | "title": "마켓플레이스 설정",
5 | "optionsHeading": "설정",
6 | "starCountLabel": "별점 개수",
7 | "tagsLabel": "태그",
8 | "showArchived": "보관된 리포지스토리 보기",
9 | "devToolsLabel": "테마 개발자 도구",
10 | "hideInstalledLabel": "설치된 기능은 탐색 결과에서 숨기기",
11 | "colourShiftLabel": "매 분마다 색상 변경",
12 | "albumArtBasedColors": "앨범 표지에 따라 색상 변경",
13 | "albumArtBasedColorsMode": "색상 구성표 (색상Api) 모드",
14 | "albumArtBasedColorsVibrancy": "앨범 표지에서 색상 추출",
15 | "albumArtBasedColorsVibrancyToolTip": "탁색 (Desaturated): 가장 두드러지지만 밝기가 낮은 색상입니다.\n쨍한 백색 (Light Vibrant): 가장 생생하게 느껴지면서도 밝기가 조금 더 높아진 색상입니다.\n강조색 (Prominent): 앨범 아트에서 가장 눈에 띄는 색상입니다, 앨범 아트에서 가장 돋보이는 색이라고 할 수 있죠.\n높은 채도색 (Vibrant): 앨범 아트에서 가장 생생한 색상입니다.",
16 | "almbumArtColorsModeToolTip": "모노크롬 다크 (Monochrome Dark): 선택한 메인 색상을 기준으로 다양한 명암들과 회색을 섞어 만든 색상입니다. 이는 Monochrome Light의 반대 개념이죠.\n모노크롬 라이트 (Monochrome Light): 선택한 메인 색상을 기준으로 다양한 명암과 회색을 섞어 만든 색상입니다. Monochrome Light의 배경색은 Monochrome Dark의 전경색(배경색) 또는 텍스트의 색상이 되며, 그 반대도 마찬가지입니다.\n유사색 (Analogic): 선택한 메인 색상을 기준으로 색 팔레트에서 메인 색상에 인접한 색상들을 이용하여 만든 색상입니다.\n유사 보색 (Analogic Complementary): 선택한 메인 색상을 기준으로 색 팔레트에서 메인 색상에 인접한 색상들과 보색들을 함께 사용하여 만든 색입니다.\n트라이드 (Triad): 선택한 메인 색상을 기준으로 색팔레트에서 메인 색상으로부터 동일한 간격으로 떨어져 있는 세 가지 색상을 사용하여 만든 색상 구성표입니다.\n쿼드 (Quad): 선택한 메인 색상을 기준으로 색팔레트에서 메인 색상으로부터 90도 간격으로 떨어져 있는 네 가지 색상을 사용하여 만든 색상 구성표입니다.",
17 | "tabsHeading": "탭",
18 | "tabsDescription": "끌어 놓아 순서를 변경하고, 클릭하여 활성화 및 비활성화 합니다.",
19 | "resetHeading": "\n초기화",
20 | "resetBtn": "$t(settings.resetHeading)",
21 | "resetDescription": "모든 확장 프로그램들과 테마들을 제거하고, 기본 설정으로 되돌립니다.",
22 | "backupHeading": "백업/복구",
23 | "backupLabel": "모든 마켓플레이스 데이터를 백업하거나 복원합니다.여기에는 마켓플레이스를 통해 설치된 항목들의 설정은 포함되지 않습니다.",
24 | "backupBtn": "열기",
25 | "versionHeading": "버전",
26 | "versionBtn": "복사",
27 | "versionCopied": "복사됨"
28 | },
29 | "tabs": {
30 | "Extensions": "확장 프로그램",
31 | "Themes": "테마 목록",
32 | "Snippets": "추가 기능",
33 | "Apps": "앱 목록",
34 | "Installed": "설치 목록"
35 | },
36 | "snippets": {
37 | "addTitle": "추가 기능 등록",
38 | "duplicateName": "이 이름은 이미 사용되고 있습니다!",
39 | "editTitle": "추가 기능 수정",
40 | "viewTitle": "추가 기능",
41 | "customCSS": "Custom CSS",
42 | "customCSSPlaceholder": "자신의 CSS 파일을 이곳에 첨부하세요! 설치 목록 탭에서 찾을 수 있습니다.",
43 | "snippetName": "추가 기능 이름",
44 | "snippetNamePlaceholder": "추가 기능 이름을 이곳에 입력하세요!",
45 | "snippetDesc": "추가 기능 설명",
46 | "snippetDescPlaceholder": "추가 기능 설명을 이곳에 입력하세요!",
47 | "snippetPreview": "추가 기능 미리보기 이미지",
48 | "optional": "선택사항",
49 | "addImage": "이미지 추가",
50 | "changeImage": "이미지 바꾸기",
51 | "saveCSS": "CSS 저장"
52 | },
53 | "reloadModal": {
54 | "title": "번경 사항 적용",
55 | "description": "이 작업을 완료하려면 페이지를 재시작해야 합니다.",
56 | "reloadNow": "지금 재시작",
57 | "reloadLater": "나중에 재시작"
58 | },
59 | "backupModal": {
60 | "title": "$t(settings.backupHeading)",
61 | "settingsCopied": "설정이 클립보드에 저장되었습니다.",
62 | "noDataPasted": "붙여넣어진 데이터가 없습니다",
63 | "invalidJSON": "유효하지 않은 JSON",
64 | "inputLabel": "마켓 플레이스 설정",
65 | "inputPlaceholder": "이곳에 설정을 복사/붙여넣기 하세요",
66 | "exportBtn": "내보내기",
67 | "importBtn": "가져오기",
68 | "fileImportBtn": "파일에서 가져오기"
69 | },
70 | "devTools": {
71 | "title": "테마 개발자 도구",
72 | "noThemeInstalled": "오류: 어떤 마켓플레이스 테마도 설치되지 않았습니다",
73 | "noThemeManifest": "오류: 테마 매니페스트 찾을 수 없음",
74 | "colorIniEditor": "Colour.ini 편집기",
75 | "colorIniEditorPlaceholder": "[당신의-색상-구성표-이름]",
76 | "invalidCSS": "유효치 않은 CSS"
77 | },
78 | "updateModal": {
79 | "title": "마켓플레이스 업데이트",
80 | "description": "spicetify 마켓 플레이스를 업데이트 하여 새로운 기능들과 버그 픽스들을 제공받습니다.",
81 | "currentVersion": "현재 버전: {{version}}",
82 | "latestVersion": "지난 버전: {{version}}",
83 | "whatsChanged": "새로운 기능",
84 | "seeChangelog": "패치노트 보기",
85 | "howToUpgrade": "업데이트 방법",
86 | "viewGuide": "가이드 보기"
87 | },
88 | "grid": {
89 | "spicetifyMarketplace": "Spicetify 마켓플레이스",
90 | "newUpdate": "새로운 업데이트",
91 | "addCSS": "CSS 등록",
92 | "search": "검색",
93 | "installed": "설치됨",
94 | "lastUpdated": "마지막 업데이트일 {{val, datetime}}",
95 | "externalJS": "external JS",
96 | "archived": "만료됨",
97 | "dark": "다크 모드",
98 | "light": "라이트 모드",
99 | "sort": {
100 | "label": "나열 기준:",
101 | "stars": "별점",
102 | "newest": "신규",
103 | "oldest": "오래된 순",
104 | "lastUpdated": "가장 최신 순",
105 | "mostStale": "가장 오래된 순",
106 | "aToZ": "A-Z 까지",
107 | "zToA": "Z-A 까지"
108 | }
109 | },
110 | "readmePage": {
111 | "title": "$t(grid.spicetifyMarketplace) - Readme",
112 | "loading": "로딩중...",
113 | "errorLoading": "README를 불러오는 중 오류 발생"
114 | },
115 | "github": "GitHub",
116 | "install": "설치",
117 | "remove": "제거",
118 | "save": "저장",
119 | "colour_one": "색상",
120 | "colour_other": "색상들",
121 | "favourite": "즐겨찾기",
122 | "notifications": {
123 | "wrongLocalTheme": "마켓플레이스를 사용하여 테마를 설치하려면 config-xpui.ini에서 current_theme를 'marketplace'로 설정하세요.",
124 | "tooManyRequests": "요청이 너무 빈번합니다, 잠시 뒤 시도하세요",
125 | "noCdnConnection": "마켓플레이스가 CDN에 접속할 수 없습니다. 현재 인터넷 구성을 확인해주세요.",
126 | "markdownParsingError": "Markdown 구문 분석 오류 (HTTP {{status}})",
127 | "noReadmeFile": "README 파일이 존재하지 않습니다",
128 | "themeInstallationError": "테마를 설치하는데 오류가 있었습니다",
129 | "extensionInstallationError": "확장 프로그램을 설치하는 데 오류가 발생했습니다"
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/resources/locales/ru.json:
--------------------------------------------------------------------------------
1 | {
2 | "translation": {
3 | "settings": {
4 | "title": "Настройки",
5 | "optionsHeading": "Основные",
6 | "starCountLabel": "Отображать количество звезд",
7 | "tagsLabel": "Отображать теги",
8 | "showArchived": "Отображать архивные репозитории",
9 | "devToolsLabel": "Включить инструменты разработчика тем",
10 | "hideInstalledLabel": "Скрывать установленное в других вкладках",
11 | "colourShiftLabel": "Менять цвета каждую минуту",
12 | "albumArtBasedColors": "Использовать цвета на основе обложки альбома",
13 | "albumArtBasedColorsMode": "Тип цвета",
14 | "albumArtBasedColorsVibrancy": "Тип цветовой схемы на основе обложки альбома",
15 | "albumArtBasedColorsVibrancyToolTip": "Desaturated: наиболее часто встречаемый в обложке цвет с малой яркостью \n Light Vibrant: самый насыщенный цвет с повышенной яркостью \n Prominent: наиболее часто встречаемый цвет в обложке альбома \n Vibrant: самый насыщенный цвет в обложке альбома",
16 | "almbumArtColorsModeToolTip": "Monochrome Dark, Monochrome Light: основаны иcключительно на выбранном цвете, дополнительные цвета создаются путем изменения яркости основого. Противоположны друг другу: цвет, являющийся фоновым в Monochrome Light, в Monochrome Dark будет цветом переднего плана и наоборот. \n Analogic: палитра определяется выбранным и цветами, смежными с ним на цветовом круге. \n Analogic Complementary: схожа c Analogic, но сожержит также дополнительный цвет. \n Triad: палитра определяется основным цветом и цветами, равноудаленными от него. \n Quad: палитра определяется выбранным цветом и цветами, расположенных под углом 90 градусов к нему.",
17 | "tabsHeading": "Вкладки",
18 | "tabsDescription": "Перетаскивание для изменения порядка, щелчок для включения/выключения",
19 | "resetHeading": "Сброс",
20 | "resetBtn": "Сбросить",
21 | "resetDescription": "Удалить все и сбросить настройки",
22 | "backupHeading": "Резервное копирование и восстановление",
23 | "backupLabel": "Сохранить или восстановить все данные Маркетплейса, за исключением настроек установленных тем и расширений.",
24 | "backupBtn": "Открыть",
25 | "versionHeading": "Версия",
26 | "versionBtn": "Копировать",
27 | "versionCopied": "Скопировано"
28 | },
29 | "tabs": {
30 | "Extensions": "Расширения",
31 | "Themes": "Темы",
32 | "Snippets": "Сниппеты",
33 | "Apps": "Приложения",
34 | "Installed": "Установленное"
35 | },
36 | "snippets": {
37 | "addTitle": "Добавление сниппета",
38 | "duplicateName": "Сниппет с таким названием уже существует",
39 | "editTitle": "Редактирование сниппета",
40 | "viewTitle": "Просмотр сниппета",
41 | "customCSS": "CSS",
42 | "customCSSPlaceholder": "Вставьте сюда CSS вашего сниппета",
43 | "snippetName": "Название",
44 | "snippetNamePlaceholder": "Введите название для вашего сниппета",
45 | "snippetDesc": "Описание",
46 | "snippetDescPlaceholder": "Введите описание для вашего сниппета",
47 | "snippetPreview": "Превью",
48 | "optional": "необязательно",
49 | "addImage": "Добавить изображение",
50 | "changeImage": "Изменить изображение",
51 | "saveCSS": "Сохранить"
52 | },
53 | "reloadModal": {
54 | "title": "Перезагрузка",
55 | "description": "Необходима перезагрузка страницы для применения изменений",
56 | "reloadNow": "Перезагрузить сейчас",
57 | "reloadLater": "Перезагрузить позже"
58 | },
59 | "backupModal": {
60 | "title": "$t(settings.backupHeading)",
61 | "settingsCopied": "Настройки скопированы в буфер обмена",
62 | "noDataPasted": "Ничего не вставлено",
63 | "invalidJSON": "Неверный JSON",
64 | "inputLabel": "Настройки Маркетплейса",
65 | "inputPlaceholder": "Вставьте ваши настройки сюда",
66 | "exportBtn": "Экспортировать",
67 | "importBtn": "Импортировать",
68 | "fileImportBtn": "Импортировать из файла"
69 | },
70 | "devTools": {
71 | "title": "Инструменты разработчика тем",
72 | "noThemeInstalled": "Ошибка: Не установлена тема из Маркетплейса",
73 | "noThemeManifest": "Ошибка: Не найден манифест темы",
74 | "colorIniEditor": "Редактор color.ini",
75 | "colorIniEditorPlaceholder": "[название-вашей-цветовой-схемы]",
76 | "invalidCSS": "Неверный CSS"
77 | },
78 | "updateModal": {
79 | "title": "Обновление Маркетплейса",
80 | "description": "Обновите Маркетплейс для получения новых функций и исправлений.",
81 | "currentVersion": "Текущая версия: {{version}}",
82 | "latestVersion": "Последняя версия: {{version}}",
83 | "whatsChanged": "Что нового",
84 | "seeChangelog": "Посмотреть изменения",
85 | "howToUpgrade": "Инструкция по обновлению",
86 | "viewGuide": "Посмотреть инструкцию"
87 | },
88 | "grid": {
89 | "spicetifyMarketplace": "Маркетплейс Spicetify",
90 | "newUpdate": "Доступно обновление",
91 | "addCSS": "Добавить CSS",
92 | "search": "Искать",
93 | "installed": "Установлено",
94 | "lastUpdated": "Обновлено: {{val, datetime}}",
95 | "externalJS": "содержит JS",
96 | "archived": "архивировано",
97 | "dark": "темный",
98 | "light": "светлый",
99 | "sort": {
100 | "label": "Сортировать:",
101 | "stars": "по количеству звезд",
102 | "newest": "сначала новые",
103 | "oldest": "сначала старые",
104 | "lastUpdated": "сначала недавно обновленные",
105 | "mostStale": "сначала давно не обновлявшиеся",
106 | "aToZ": "по названию (A-Z)",
107 | "zToA": "по названию (Z-A)"
108 | }
109 | },
110 | "readmePage": {
111 | "title": "$t(grid.spicetifyMarketplace) - Readme",
112 | "loading": "Загрузка...",
113 | "errorLoading": "Ошибка загрузки README"
114 | },
115 | "github": "GitHub",
116 | "install": "Установить",
117 | "remove": "Удалить",
118 | "save": "Сохранить",
119 | "colour_one": "цвет",
120 | "colour_other": "цвета",
121 | "favourite": "избранное",
122 | "notifications": {
123 | "wrongLocalTheme": "Пожалуйста, измените значение current_theme в config-xpui.ini на 'marketplace', чтобы использовать темы из Маркетплейса",
124 | "tooManyRequests": "Слишком много запросов. Пожалуйста, попробуйте позже",
125 | "noCdnConnection": "Маркетплейс не может подключиться к CDN. Пожалуйста, попробуйте позже",
126 | "markdownParsingError": "Ошибка при парсинге Markdown (HTTP {{status}})",
127 | "noReadmeFile": "README не найден",
128 | "themeInstallationError": "Ошибка при установке темы",
129 | "extensionInstallationError": "Ошибка при установке расширения"
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/resources/locales/zh-CN.json:
--------------------------------------------------------------------------------
1 | {
2 | "translation": {
3 | "settings": {
4 | "title": "设置",
5 | "optionsHeading": "选项",
6 | "starCountLabel": "收藏数",
7 | "tagsLabel": "标签",
8 | "devToolsLabel": "主題开发者工具",
9 | "hideInstalledLabel": "浏览时隐藏已安装项目",
10 | "colourShiftLabel": "每分钟进行色调偏移",
11 | "tabsHeading": "分页",
12 | "tabsDescription": "拖放更改顺序,点击启用/禁用",
13 | "resetHeading": "重置",
14 | "resetBtn": "$t(settings.resetHeading)",
15 | "resetDescription": "卸载所有扩展插件和主题,并重置设置"
16 | },
17 | "tabs": {
18 | "Extensions": "扩展插件",
19 | "Themes": "主题",
20 | "Snippets": "微调片段",
21 | "Apps": "功能模组",
22 | "Installed": "已安裝项目"
23 | },
24 | "snippets": {
25 | "addTitle": "加入微调片段",
26 | "editTitle": "编辑微调片段",
27 | "viewTitle": "检视微调片段",
28 | "customCSS": "自定义 CSS",
29 | "customCSSPlaceholder": "这里可以输入您的自定义 CSS!您可以在「已安裝项目」标签页中看到这些片段,进而进行管理。",
30 | "snippetName": "微调片段名称",
31 | "snippetNamePlaceholder": "输入自定义微调片段的名称",
32 | "snippetDesc": "微调片段描述",
33 | "snippetDescPlaceholder": "输入自定义微调片段的描述",
34 | "snippetPreview": "微调片段预览图",
35 | "optional": "非必要",
36 | "addImage": "加入影像",
37 | "changeImage": "更改影像",
38 | "saveCSS": "保存 CSS"
39 | },
40 | "reloadModal": {
41 | "title": "重新加载",
42 | "description": "需要重新加载页面,才能完成这个操作。",
43 | "reloadNow": "立即重新加载",
44 | "reloadLater": "稍后重新加载"
45 | },
46 | "devTools": {
47 | "title": "主題开发者工具",
48 | "noThemeInstalled": "错误:未安装商场主题",
49 | "noThemeManifest": "错误:找不到主题内容清单",
50 | "colorIniEditor": "Color.ini 编辑器",
51 | "colorIniEditorPlaceholder": "[您的色彩配置名称]",
52 | "invalidCSS": "CSS 无效"
53 | },
54 | "grid": {
55 | "spicetifyMarketplace": "Spicetify 商场",
56 | "newUpdate": "有更新",
57 | "addCSS": "加入 CSS",
58 | "search": "搜索",
59 | "installed": "已安装",
60 | "lastUpdated": "上次更新于 {{val, datetime}}",
61 | "externalJS": "有外部 JS",
62 | "dark": "暗色模式",
63 | "light": "亮色模式"
64 | },
65 | "readmePage": {
66 | "title": "$t(grid.spicetifyMarketplace) – 说明",
67 | "loading": "正在加载……",
68 | "errorLoading": "加载 README 时发生错误"
69 | },
70 | "github": "GitHub",
71 | "install": "安裝",
72 | "remove": "移除",
73 | "save": "保存",
74 | "colour_one": "色彩",
75 | "colour_other": "色彩",
76 | "favourite": "收藏"
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/resources/locales/zh-TW.json:
--------------------------------------------------------------------------------
1 | {
2 | "translation": {
3 | "settings": {
4 | "title": "設定",
5 | "optionsHeading": "選項",
6 | "starCountLabel": "收藏數",
7 | "tagsLabel": "標籤",
8 | "devToolsLabel": "主題開發者工具",
9 | "hideInstalledLabel": "瀏覽時隱藏已安裝項目",
10 | "colourShiftLabel": "每分鐘進行色調偏移",
11 | "tabsHeading": "分頁",
12 | "tabsDescription": "拖放更改顺序,点击启用/禁用",
13 | "resetHeading": "重設",
14 | "resetBtn": "$t(settings.resetHeading)",
15 | "resetDescription": "解除安裝所有擴充套件和主題, 並重設偏好設定",
16 | "backupHeading": "備份與還原",
17 | "backupLabel": "備份或還原所有 Marketplace 中的資料(不包含從 Marketplace 安裝的擴充元件的設定)。",
18 | "backupBtn": "開啟",
19 | "albumArtBasedColors": "根據專輯封面選色",
20 | "albumArtBasedColorsMode": "色彩方案 (ColorApi) 模式",
21 | "albumArtBasedColorsVibrancy": "已從專輯封面抽取顏色",
22 | "albumArtBasedColorsVibrancyToolTip": "Desaturated:最突出但亮度較低的顏色 \n Light Vibrant:最接近 Vibrant 的色彩,但亮度稍微提升一些 \n Prominent:專輯封面裡面出現最多的色彩 \n Vibrant:專輯中最明亮的色彩",
23 | "almbumArtColorsModeToolTip": "Monochrome Dark:這個色彩方案直接以選擇的主色彩為基礎,但使用比較不一樣的色調並且融入灰色。這和 Monochrome Light 正好相反。 \n Monochrome Light:這個色彩方案直接以選擇的主色彩為基礎,但使用比較不一樣的色調並且融入灰色。這和 Monochrome Light 正好相反。Monochrome Light 的背景色會是 Monochrome Dark 的前景或文字顏色,反之亦然。 \n Analogic:這個色彩方案以選擇的主色彩為基礎,使用色環上主色彩鄰近的色彩。 \n Analogic Complementary:這個色彩方案以選擇的主色彩為基礎,使用色環上主色彩鄰近的色彩以及互補色。 \n Triad:這個色彩方案以選擇的主色彩為基礎,使用色環上和主色彩距離相等的顏色。 \n Quad:這個色彩方案以選擇的主色彩為基礎,使用色環上和主色彩差 90 度的顏色。",
24 | "versionHeading": "版本",
25 | "versionBtn": "複製",
26 | "versionCopied": "已複製"
27 | },
28 | "tabs": {
29 | "Extensions": "擴充套件",
30 | "Themes": "主題",
31 | "Snippets": "微調片段",
32 | "Apps": "功能模組",
33 | "Installed": "已安裝項目"
34 | },
35 | "snippets": {
36 | "addTitle": "加入微調片段",
37 | "editTitle": "編輯微調片段",
38 | "viewTitle": "檢視微調片段",
39 | "customCSS": "自訂 CSS",
40 | "customCSSPlaceholder": "這裡可以輸入您的自訂 CSS!您可以在「已安裝項目」分頁中看到這些片段,進而進行管理。",
41 | "snippetName": "微調片段名稱",
42 | "snippetNamePlaceholder": "輸入自訂微調片段的名稱",
43 | "snippetDesc": "微調片段描述",
44 | "snippetDescPlaceholder": "輸入自訂微調片段的描述",
45 | "snippetPreview": "微調片段預覽圖",
46 | "optional": "非必須",
47 | "addImage": "加入影像",
48 | "changeImage": "更改影像",
49 | "saveCSS": "儲存 CSS"
50 | },
51 | "reloadModal": {
52 | "title": "重新載入",
53 | "description": "需要重新載入頁面,才能完成這個操作。",
54 | "reloadNow": "立即重新載入",
55 | "reloadLater": "稍後重新載入"
56 | },
57 | "backupModal": {
58 | "title": "$t(settings.backupHeading)",
59 | "settingsCopied": "已將設定複製至剪貼簿",
60 | "noDataPasted": "沒有貼上資料",
61 | "invalidJSON": "JSON 無效",
62 | "inputLabel": "Marketplace 設定",
63 | "inputPlaceholder": "在此複製或貼上設定",
64 | "exportBtn": "匯出",
65 | "importBtn": "匯入",
66 | "fileImportBtn": "從檔案匯入"
67 | },
68 | "devTools": {
69 | "title": "主題開發者工具",
70 | "noThemeInstalled": "錯誤:沒有安裝 Marketplace 主題",
71 | "noThemeManifest": "錯誤:找不到主題資訊清單",
72 | "colorIniEditor": "Color.ini 編輯器",
73 | "colorIniEditorPlaceholder": "[您的色彩配置名稱]",
74 | "invalidCSS": "CSS 無效"
75 | },
76 | "grid": {
77 | "spicetifyMarketplace": "Spicetify Marketplace",
78 | "newUpdate": "有更新",
79 | "addCSS": "加入 CSS",
80 | "search": "搜尋",
81 | "installed": "已經安裝",
82 | "lastUpdated": "上次更新於 {{val, datetime}}",
83 | "externalJS": "有外部 JS",
84 | "dark": "暗色",
85 | "light": "亮色"
86 | },
87 | "readmePage": {
88 | "title": "$t(grid.spicetifyMarketplace) – 說明",
89 | "loading": "正在載入……",
90 | "errorLoading": "載入 README 時發生錯誤"
91 | },
92 | "github": "GitHub",
93 | "install": "安裝",
94 | "remove": "移除",
95 | "save": "儲存",
96 | "colour_one": "色彩",
97 | "colour_other": "色彩",
98 | "favourite": "收藏"
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "displayName": {
3 | "en": "Marketplace",
4 | "ru": "Маркетплейс"
5 | },
6 | "nameId": "marketplace",
7 | "icon": "assets/icon.svg",
8 | "activeIcon": "assets/icon-filled.svg"
9 | }
10 |
--------------------------------------------------------------------------------
/src/styles/app.module.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | justify-content: center;
4 | align-items: center;
5 | flex-direction: column;
6 |
7 | > * {
8 | padding: 40px 0;
9 | }
10 | }
11 |
12 | .title {
13 | font-size: 50px;
14 | }
15 |
16 | .button {
17 | padding: 10px;
18 | border-radius: 10px;
19 | border:none;
20 | color: black;
21 | background-color: rgb(255, 255, 255);
22 |
23 | &:hover {
24 | background-color: rgb(243, 243, 243);
25 | }
26 |
27 | &:active {
28 | background-color: rgb(228, 228, 228);
29 | }
30 | }
31 |
32 | .counter {
33 | font-size: 50px;
34 | }
--------------------------------------------------------------------------------
/src/styles/components/_add-snippet-modal.scss:
--------------------------------------------------------------------------------
1 | // Entire modal container
2 | #marketplace-add-snippet-container {
3 | display: flex;
4 | flex-direction: column;
5 | gap: 16px;
6 |
7 | // Stop it from adding a blue outline when you tab in the editor
8 | #marketplace-custom-css:focus-visible {
9 | outline: none;
10 | }
11 | }
12 |
13 | // Container for label + input
14 | .marketplace-customCSS-input-container {
15 | display: flex;
16 | flex-direction: column;
17 | }
18 |
19 | // Preview image in the modal
20 | img.marketplace-customCSS-image-preview {
21 | aspect-ratio: 1;
22 | width: 100%;
23 | height: auto;
24 | max-width: 400px;
25 | object-fit: cover;
26 | margin: 0 auto;
27 | }
28 |
29 | // Submit button
30 | #marketplace-customCSS-submit {
31 | margin-left: auto;
32 | }
33 |
--------------------------------------------------------------------------------
/src/styles/components/_backup.scss:
--------------------------------------------------------------------------------
1 | .marketplace-backup-button {
2 | padding: 10px;
3 | margin: 10px;
4 | }
5 |
--------------------------------------------------------------------------------
/src/styles/components/_card.scss:
--------------------------------------------------------------------------------
1 | // e.g. "Extensions" heading
2 | .marketplace-card-type-heading {
3 | margin: 1em 0 0.5em;
4 | color: var(--spice-subtext);
5 | font-size: 1.3em;
6 | }
7 |
8 | .marketplace-grid {
9 | .main-card-draggable {
10 | display: flex;
11 | flex-direction: column;
12 |
13 | .main-card-cardMetadata {
14 | flex-grow: 1;
15 | display: flex;
16 | flex-direction: column;
17 |
18 | .main-cardHeader-link:hover {
19 | text-decoration: underline;
20 | }
21 |
22 | .marketplace-card__author {
23 | // Add comma before authors that have a sibling
24 | & ~ .marketplace-card__author {
25 | &::before {
26 | content: ", ";
27 | }
28 | }
29 | }
30 |
31 | ul.marketplace-card__tags {
32 | display: flex;
33 | flex-wrap: wrap;
34 | gap: 8px;
35 | }
36 |
37 | li.marketplace-card__tag {
38 | background-color: var(--spice-tab-active);
39 | border-radius: 4px;
40 | padding: 0px 9px 2px;
41 |
42 | &[data-tag='external JS'], &[data-tag=archived] {
43 | background-color: hsl(0deg 70% 54%);
44 | color: #fff;
45 | }
46 |
47 | &[data-tag='dark'] {
48 | background-color: #000;
49 | color: #fff;
50 | }
51 |
52 | &[data-tag='light'] {
53 | background-color: #fff;
54 | color: #333;
55 | }
56 | }
57 |
58 | .marketplace-card__tags-more-btn {
59 | background-color: var(--spice-tab-active);
60 | border-radius: 4px;
61 | padding: 0px 9px 2px;
62 | margin-top: 8px;
63 | border: none;
64 |
65 | &:hover, &:focus {
66 | filter: brightness(1.4);
67 | }
68 | }
69 |
70 | .marketplace-card-desc {
71 | font: -webkit-small-control;
72 | margin: 12px 0;
73 | display: -webkit-box;
74 | overflow: hidden;
75 | -webkit-box-orient: vertical;
76 | -webkit-line-clamp: 3;
77 | }
78 |
79 | .marketplace-card__bottom-meta {
80 | margin-top: auto;
81 | margin-bottom: 0;
82 |
83 | // Add space between multiple blurbs
84 | & + .marketplace-card__bottom-meta {
85 | margin-top: 8px;
86 | }
87 | }
88 | }
89 | }
90 |
91 | &[data-tab='Installed'] {
92 | &:empty::after {
93 | content: 'No installed ' attr(data-card-type);
94 | display: block;
95 | }
96 | }
97 | }
98 |
99 | // Card preview image error placeholder
100 | .main-cardImage-imageWrapper--error::before {
101 | content: '';
102 | display: block;
103 | position: absolute;
104 | overflow: hidden;
105 | top: 0;
106 | left: 0;
107 | bottom: 0;
108 | right: 0;
109 | height: 100%;
110 | width: 100%;
111 |
112 | background-color: var(--spice-subtext);
113 | filter: brightness(50%);
114 |
115 | /* https://developer.mozilla.org/en-US/docs/Web/CSS/clip-path */
116 | /* https://icons8.com/icons/set/box */
117 | clip-path: path('M41.20833,21.5c-2.54758,0.00136 -4.90298,1.35492 -6.18685,3.55534l-12.54167,21.5c-0.64062,1.09578 -0.97875,2.34203 -0.97982,3.61133v86c0,7.83362 6.49972,14.33333 14.33333,14.33333h100.33333c7.83362,0 14.33333,-6.49972 14.33333,-14.33333v-86c-0.00107,-1.2693 -0.3392,-2.51555 -0.97982,-3.61133l-12.54167,-21.5c-1.28387,-2.20042 -3.63926,-3.55398 -6.18685,-3.55534zM45.32357,35.83333h81.35286l8.35645,14.33333h-98.06576zM35.83333,64.5h100.33333v71.66667h-100.33333zM64.5,78.83333v14.33333h43v-14.33333z');
118 | clip-rule: nonzero;
119 | transform: scale(0.5);
120 | }
121 |
122 | .main-cardImage-imageWrapper--error {
123 | box-shadow: none;
124 | }
125 |
126 | .main-card-card:hover .main-cardImage-imageWrapper--error::before {
127 | filter: brightness(100%);
128 | }
129 |
130 | .marketplace-cardSubHeader,
131 | .marketplace-card__bottom-meta {
132 | margin-top: 4px;
133 | white-space: normal;
134 | color: var(--spice-subtext);
135 | }
136 |
137 | // author, stars, etc.
138 | .marketplace-cardSubHeader {
139 | display: flex;
140 | flex-direction: column;
141 | gap: 8px;
142 | }
143 |
144 | .marketplace-card--snippet {
145 | // Hide image preview for CSS snippets
146 | // .main-card-imageContainer { display: none; }
147 |
148 | // TODO: what do we do if the snippet has no image?
149 | }
150 |
151 | .marketplace-card--installed {
152 | flex: 0 !important;
153 | -webkit-box-flex: 0 !important;
154 |
155 | border: 1px solid var(--spice-button);
156 | }
157 |
158 | .marketplace-installButton {
159 | cursor: pointer;
160 | --size: 40px;
161 | }
162 |
--------------------------------------------------------------------------------
/src/styles/components/_code-editors.scss:
--------------------------------------------------------------------------------
1 | // Rounded border style for code editor inputs
2 | // e.g. snippets, theme dev tools, etc.
3 | .marketplace-code-editor {
4 | width: 100%;
5 | margin-bottom: 10px;
6 | padding: 3px 5px;
7 | background-color: var(--spice-main);
8 | color: var(--spice-text);
9 | font-size: 14px;
10 | border: 1px solid var(--spice-button);
11 | border-radius: 4px;
12 | }
13 |
--------------------------------------------------------------------------------
/src/styles/components/_devtools.scss:
--------------------------------------------------------------------------------
1 | .devtools-column {
2 | height: 570px;
3 | width: 50%;
4 | position: relative;
5 | background-color: var(--spice-sidebar);
6 | color: var(--spice-text);
7 | overflow: scroll;
8 | padding: 1rem;
9 | }
10 |
11 | .color-ini-editor {
12 | height: 70%;
13 | width: 100%;
14 | background-color: var(--spice-sidebar);
15 | color: var(--spice-text);
16 | overflow: scroll;
17 | padding: 1rem;
18 | resize: none;
19 | font-family: monospace;
20 | }
21 |
22 | .marketplace-theme-dev-tools-container {
23 | justify-content: flex-end;
24 | display: flex;
25 |
26 | .marketplace-code-editor-wrapper {
27 | height: 400px;
28 | margin-bottom: 16px;
29 | overflow: auto;
30 | }
31 | }
32 |
33 | .invalid-css-heading,
34 | .devtools-heading {
35 | font-size: 1.5rem;
36 | font-weight: bold;
37 | text-decoration: underline;
38 | margin-bottom: 1rem;
39 | }
40 |
41 | .invalid-css-text {
42 | margin-bottom: 1.5rem;
43 | font-family: monospace;
44 | -webkit-user-select: text;
45 | user-select: text;
46 | }
47 |
48 | .devtools-icon {
49 | width: 18px;
50 | fill: var(--spice-button)
51 | }
52 |
--------------------------------------------------------------------------------
/src/styles/components/_dropdown.scss:
--------------------------------------------------------------------------------
1 | @use '../constants.scss';
2 |
3 | $dropdown-text: rgba(var(--spice-rgb-text), .7);
4 | $dropdown-text-hover: rgb(var(--spice-rgb-text));
5 | $dropdown-bg: var(--spice-sidebar);
6 | $dropdown-radius: constants.$btn-radius;
7 |
8 | .arrow-closed, .arrow-open {
9 | border: solid $dropdown-text;
10 | border-width: 0 2px 2px 0;
11 | display: inline-block;
12 | padding: 4px;
13 | position: absolute;
14 | right: 10px;
15 | }
16 |
17 | .arrow-closed {
18 | top: 10px;
19 | transform: rotate(45deg);
20 | -webkit-transform: rotate(45deg);
21 | }
22 |
23 | .arrow-open {
24 | top: 14px;
25 | transform: rotate(-135deg);
26 | -webkit-transform: rotate(-135deg);
27 | }
28 |
29 | .Dropdown-root {
30 | position: relative;
31 |
32 | &.is-open {
33 | .Dropdown-control {
34 | border-bottom-left-radius: 0;
35 | border-bottom-right-radius: 0;
36 |
37 | &:hover .Dropdown-arrow {
38 | border-color: transparent transparent $dropdown-text-hover;
39 | }
40 | }
41 | .Dropdown-arrow {
42 | border-color: transparent transparent $dropdown-text;
43 | border-width: 0 5px 5px;
44 | }
45 | .Dropdown-menu {
46 | border-top-left-radius: 0;
47 | border-top-right-radius: 0;
48 | width: max-content;
49 | }
50 | }
51 | }
52 |
53 | // The button (collapsed)
54 | .Dropdown-control {
55 | position: relative;
56 | overflow: hidden;
57 | background-color: $dropdown-bg;
58 | border: 0;
59 | border-radius: $dropdown-radius;
60 | box-sizing: border-box;
61 | color: $dropdown-text;
62 | cursor: default;
63 | outline: none;
64 | padding: 8px 36px 8px 16px;
65 | transition: all constants.$transition-default ease;
66 |
67 | &:hover {
68 | color: $dropdown-text-hover;
69 | background-color: var(--spice-button-disabled);
70 |
71 | .Dropdown-arrow {
72 | border-color: $dropdown-text-hover transparent transparent;
73 | }
74 | }
75 | }
76 |
77 | .Dropdown-arrow {
78 | border-color: $dropdown-text transparent transparent;
79 | border-style: solid;
80 | border-width: 5px 5px 0;
81 | content: ' ';
82 | display: block;
83 | height: 0;
84 | margin-top: -ceil(2.5);
85 | position: absolute;
86 | right: 16px;
87 | top: 18px;
88 | width: 0
89 | }
90 |
91 | // The expanded list
92 | .Dropdown-menu {
93 | background-color: $dropdown-bg;
94 | border: 0;
95 | border-radius: $dropdown-radius;
96 | box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
97 | box-sizing: border-box;
98 | margin-top: -1px;
99 | max-height: 200px;
100 | overflow-y: auto;
101 | position: absolute;
102 | top: 100%;
103 | width: 100%;
104 | z-index: 1000;
105 | -webkit-overflow-scrolling: touch;
106 |
107 | .Dropdown-group > .Dropdown-title {
108 | padding: 8px 10px;
109 | color: rgba(51, 51, 51, 1.2);
110 | font-weight: bold;
111 | text-transform: capitalize;
112 | }
113 | }
114 |
115 | .Dropdown-option {
116 | box-sizing: border-box;
117 | color: $dropdown-text;
118 | cursor: pointer;
119 | display: block;
120 | padding: 8px 10px;
121 |
122 | &:last-child {
123 | border-bottom-right-radius: $dropdown-radius;
124 | border-bottom-left-radius: $dropdown-radius;
125 | }
126 |
127 | &.is-selected {
128 | background-color: $dropdown-text;
129 | color: $dropdown-bg;
130 | }
131 |
132 | &:hover {
133 | background-color: $dropdown-text-hover;
134 | color: $dropdown-bg;
135 | }
136 | }
137 |
138 | .Dropdown-noresults {
139 | box-sizing: border-box;
140 | color: #ccc;
141 | cursor: default;
142 | display: block;
143 | padding: 8px 10px;
144 | }
145 |
--------------------------------------------------------------------------------
/src/styles/components/_fixes.scss:
--------------------------------------------------------------------------------
1 | /*
2 | TODO: can we get this added to the default Spicetify stylesheet?
3 | It should fix broken extension modals
4 | */
5 |
6 | .GenericModal {
7 | background-color: var(--spice-player);
8 | }
9 |
--------------------------------------------------------------------------------
/src/styles/components/_grid.scss:
--------------------------------------------------------------------------------
1 | @use "../constants.scss";
2 |
3 | .marketplace-header {
4 | -webkit-box-pack: justify;
5 | -webkit-box-align: center;
6 | align-content: space-between;
7 | align-items: center;
8 | color: var(--spice-text);
9 | background-color: var(--spice-main);
10 | display: flex;
11 | justify-content: space-between;
12 | padding-bottom: 8px;
13 |
14 | // To position the settings button + colour schemes
15 | position: sticky;
16 | flex-direction: row;
17 | top: 64px;
18 | z-index: 1;
19 |
20 | // Fix positioning on readme pages
21 | .contentSpacing:has(#marketplace-readme) & {
22 | top: 0;
23 | }
24 | }
25 |
26 | .marketplace-header__right,
27 | .marketplace-header__left {
28 | display: flex;
29 | & > :is(div, button) {
30 | margin-left: 8px;
31 | }
32 | }
33 |
34 | .marketplace-header__label {
35 | display: inline-flex;
36 | align-self: center;
37 | }
38 |
39 | .marketplace-grid {
40 | --minimumColumnWidth: 180px;
41 | --column-width: minmax(var(--minimumColumnWidth), 1fr);
42 | --column-count: auto-fill;
43 | --grid-gap: 24px;
44 | }
45 |
46 | .marketplace-sort-bar {
47 | align-items: center;
48 | display: flex;
49 | }
50 |
51 | .marketplace-sort-container {
52 | position: relative;
53 | display: flex;
54 | }
55 |
56 | .marketplace-tabBar-headerItem {
57 | -webkit-app-region: no-drag;
58 | display: inline-block;
59 | pointer-events: auto;
60 |
61 | // Fixes the tab bar alignment going weird when the "more" dropdown menu appears
62 | vertical-align: middle;
63 | }
64 |
65 | .marketplace-tabBar-active {
66 | background-color: var(--spice-tab-active);
67 | border-radius: 4px;
68 | }
69 |
70 | .marketplace-tabBar-headerItemLink {
71 | border-radius: 4px;
72 | color: var(--spice-text);
73 | display: inline-block;
74 | margin: 0 8px 0 0;
75 | padding: 8px 16px;
76 | position: relative;
77 | text-decoration: none !important;
78 | cursor: pointer;
79 | }
80 |
81 | .marketplace-tabBar-nav {
82 | -webkit-app-region: drag;
83 | pointer-events: none;
84 | width: 100%;
85 | }
86 |
87 | .marketplace-tabBar-headerItem .optionsMenu-dropBox {
88 | color: var(--spice-text);
89 | border: 0;
90 | max-width: 150px;
91 | height: 42px;
92 | padding: 0 30px 0 12px;
93 | background-color: initial;
94 | cursor: pointer;
95 | appearance: none;
96 | }
97 |
98 | .marketplace-tabBar-headerItem .optionsMenu-dropBox svg {
99 | position: absolute;
100 | margin-left: 8px;
101 | }
102 |
103 | .marketplace-header-icon-button {
104 | border-radius: constants.$btn-radius;
105 | color: var(--spice-text);
106 | display: inline-block;
107 | padding: 10px 14px 6px;
108 | font-weight: bold;
109 | position: relative;
110 | text-decoration: none !important;
111 | cursor: pointer;
112 | background-color: transparent;
113 | border: none;
114 | background-color: var(--spice-sidebar);
115 | transition-duration: constants.$transition-default;
116 |
117 | &:hover {
118 | background-color: var(--spice-button-disabled);
119 | }
120 | }
121 |
122 | #marketplace-update {
123 | margin-left: 0;
124 | margin-right: 16px;
125 | }
126 |
127 | // Search bar
128 | .searchbar--bar__wrapper {
129 | display: flex;
130 | flex-direction: column;
131 | align-items: flex-end;
132 | flex-grow: 1;
133 | }
134 | .searchbar-bar {
135 | border-style: solid;
136 | border-color: var(--spice-sidebar);
137 | background-color: var(--spice-sidebar) !important;
138 | border-radius: constants.$btn-radius;
139 | padding: 10px 12px;
140 | color: var(--spice-text) !important;
141 | }
142 |
143 | .marketplace-footer {
144 | margin: auto;
145 | text-align: center;
146 | }
147 |
148 | // "Add CSS" button
149 | .marketplace-add-snippet-btn {
150 | position: -webkit-sticky;
151 | position: sticky !important;
152 | bottom: 32px;
153 | left: 100%;
154 | }
155 |
156 | .marketplace-content {
157 | margin-top: 60px;
158 | }
159 |
--------------------------------------------------------------------------------
/src/styles/components/_reload-modal.scss:
--------------------------------------------------------------------------------
1 | /* Need to hijack height because .GenericModal has it set to 90% via style attr... */
2 | .GenericModal[aria-label="Reload required"] {
3 | height: 240px !important;
4 | }
5 |
6 | .marketplace-reload-modal__button-container {
7 | display: flex;
8 | justify-content: center;
9 | padding-top: 18px;
10 | }
11 | .marketplace-reload-modal__button-container button {
12 | margin: 18px;
13 | padding: 8px 24px;
14 | }
15 |
--------------------------------------------------------------------------------
/src/styles/components/_settings.scss:
--------------------------------------------------------------------------------
1 | #marketplace-config-container {
2 | .settings-block {
3 | padding-top: 10px;
4 | padding-bottom: 10px;
5 | }
6 |
7 | .settings-block-top {
8 | padding-bottom: 10px;
9 | }
10 |
11 | .settings-block-bottom {
12 | padding-top: 10px;
13 | }
14 |
15 | .settings-tabs-description {
16 | font-style: italic;
17 | font-size: 12px;
18 | }
19 |
20 |
21 | .settings-row {
22 | display: flex;
23 | justify-content: space-between;
24 |
25 | .col {
26 | display: flex;
27 | padding: 8px 0;
28 | align-items: center;
29 |
30 | &.action {
31 | text-align: right;
32 | margin-inline-start: 8px;
33 |
34 | // The tooltip icon
35 | & .marketplace-sortBox + .marketplace-tooltip-icon {
36 | margin-inline-start: 8px;
37 | }
38 | }
39 | }
40 | }
41 |
42 | .settings-heading {
43 | position: relative;
44 | border-bottom: 2px solid var(--spice-button-disabled);;
45 | padding: 0 0 4px;
46 | margin: 0 0 4px;
47 | }
48 | }
49 |
50 | // The tooltip icon
51 | .marketplace-tooltip-icon {
52 | position: relative;
53 | display: inline-flex;
54 | align-items: center;
55 | justify-content: center;
56 | cursor: pointer;
57 |
58 | // The tooltips themselves
59 | & + [data-tippy-root] {
60 | text-align: start;
61 | }
62 | }
63 |
64 | // The up/down arrows
65 | button.arrow-btn {
66 | align-items: center;
67 | border: 0px;
68 | border-radius: 50%;
69 | background-color: rgba(var(--spice-rgb-shadow), .7);
70 | color: var(--spice-text);
71 | cursor: pointer;
72 | display: flex;
73 | margin-inline-end: 12px;
74 | padding: 8px;
75 |
76 | &.small {
77 | width: 24px;
78 | height: 24px;
79 | padding: 5px;
80 | }
81 |
82 | &.disabled,
83 | &[disabled] {
84 | color: rgba(var(--spice-rgb-text), .3);
85 | cursor: not-allowed;
86 | }
87 | }
88 |
89 | .dnd-box {
90 | display: flex;
91 | align-items: center;
92 | justify-content: center;
93 | position: relative;
94 | width: 100%
95 | }
96 |
97 | .dnd-icon {
98 | position: absolute;
99 | left: 4px;
100 | }
101 |
102 | // Add snippet modal
103 | #marketplace-add-snippet-container input, textarea {
104 | width: 100%;
105 | margin-bottom: 10px;
106 | padding: 3px 5px;
107 | background-color: var(--spice-main);
108 | color: var(--spice-text);
109 | font-size: 14px;
110 | border: 1px solid var(--spice-button);
111 | border-radius: 4px;
112 | }
113 |
114 |
115 |
--------------------------------------------------------------------------------
/src/styles/components/_update-modal.scss:
--------------------------------------------------------------------------------
1 | #marketplace-update-container .marketplace-update-header {
2 | margin-bottom: 0.25rem;
3 | margin-top: 0.5rem;
4 | }
5 |
6 | #marketplace-update-description {
7 | & > h4 {
8 | margin-bottom: 0.5rem;
9 | }
10 |
11 | a {
12 | display: block;
13 | }
14 | }
15 |
16 | #marketplace-update-whats-changed {
17 | li {
18 | list-style-type: disc;
19 |
20 | &::marker {
21 | unicode-bidi: isolate;
22 | font-variant-numeric: tabular-nums;
23 | text-transform: none;
24 | text-indent: 0px !important;
25 | text-align: start !important;
26 | text-align-last: start !important;
27 | }
28 | }
29 |
30 | & > details > ul {
31 | padding-left: 1.25rem;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/styles/components/prismjs-themes/prism-tomorrow.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * prism.js tomorrow night eighties for JavaScript, CoffeeScript, CSS and HTML
3 | * Based on https://github.com/chriskempson/tomorrow-theme
4 | * @author Rose Pritchard
5 | */
6 |
7 | code[class*="language-"],
8 | pre[class*="language-"] {
9 | color: #ccc;
10 | background: none;
11 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
12 | font-size: 1em;
13 | text-align: left;
14 | white-space: pre;
15 | word-spacing: normal;
16 | word-break: normal;
17 | word-wrap: normal;
18 | line-height: 1.5;
19 |
20 | -moz-tab-size: 4;
21 | -o-tab-size: 4;
22 | tab-size: 4;
23 |
24 | -webkit-hyphens: none;
25 | -moz-hyphens: none;
26 | -ms-hyphens: none;
27 | hyphens: none;
28 |
29 | }
30 |
31 | /* Code blocks */
32 | pre[class*="language-"] {
33 | padding: 1em;
34 | margin: .5em 0;
35 | overflow: auto;
36 | }
37 |
38 | :not(pre) > code[class*="language-"],
39 | pre[class*="language-"] {
40 | background: #2d2d2d;
41 | }
42 |
43 | /* Inline code */
44 | :not(pre) > code[class*="language-"] {
45 | padding: .1em;
46 | border-radius: .3em;
47 | white-space: normal;
48 | }
49 |
50 | .token.comment,
51 | .token.block-comment,
52 | .token.prolog,
53 | .token.doctype,
54 | .token.cdata {
55 | color: #999;
56 | }
57 |
58 | .token.punctuation {
59 | color: #ccc;
60 | }
61 |
62 | .token.tag,
63 | .token.attr-name,
64 | .token.namespace,
65 | .token.deleted {
66 | color: #e2777a;
67 | }
68 |
69 | .token.function-name {
70 | color: #6196cc;
71 | }
72 |
73 | .token.boolean,
74 | .token.number,
75 | .token.function {
76 | color: #f08d49;
77 | }
78 |
79 | .token.property,
80 | .token.class-name,
81 | .token.constant,
82 | .token.symbol {
83 | color: #f8c555;
84 | }
85 |
86 | .token.selector,
87 | .token.important,
88 | .token.atrule,
89 | .token.keyword,
90 | .token.builtin {
91 | color: #cc99cd;
92 | }
93 |
94 | .token.string,
95 | .token.char,
96 | .token.attr-value,
97 | .token.regex,
98 | .token.variable {
99 | color: #7ec699;
100 | }
101 |
102 | .token.operator,
103 | .token.entity,
104 | .token.url {
105 | color: #67cdcc;
106 | }
107 |
108 | .token.important,
109 | .token.bold {
110 | font-weight: bold;
111 | }
112 | .token.italic {
113 | font-style: italic;
114 | }
115 |
116 | .token.entity {
117 | cursor: help;
118 | }
119 |
120 | .token.inserted {
121 | color: green;
122 | }
123 |
--------------------------------------------------------------------------------
/src/styles/constants.scss:
--------------------------------------------------------------------------------
1 | $btn-radius: 8px;
2 | $transition-default: 200ms;
3 |
--------------------------------------------------------------------------------
/src/styles/modules/button.module.scss:
--------------------------------------------------------------------------------
1 | // This was taken from the Spotify styles for the button on the change profile pic modal
2 |
3 | .button {
4 | box-sizing: border-box;
5 | font-family: var(--font-family,spotify-circular),Helvetica,Arial,sans-serif;
6 | -webkit-tap-highlight-color: transparent;
7 | font-size: 1rem;
8 | line-height: 1.5rem;
9 | font-weight: 700;
10 | background-color: transparent;
11 | border: 0px;
12 | border-radius: 500px;
13 | display: inline-block;
14 | position: relative;
15 | text-align: center;
16 | text-decoration: none;
17 | text-transform: none;
18 | touch-action: manipulation;
19 | transition-duration: 33ms;
20 | transition-property: background-color, border-color, color, box-shadow, filter, transform;
21 | user-select: none;
22 | vertical-align: middle;
23 | transform: translate3d(0px, 0px, 0px);
24 | padding: 0px;
25 | min-inline-size: 0px;
26 | align-self: center;
27 |
28 | @media screen and (min-width: 768px) {
29 | font-size: 1rem;
30 | line-height: 1.5rem;
31 | text-transform: none;
32 | letter-spacing: normal;
33 | }
34 |
35 | // From the inner div...
36 | position: relative;
37 | background-color: var(--spice-text);
38 | color: var(--spice-main);
39 | border-radius: 500px;
40 | font-size: inherit;
41 | padding-block: 12px;
42 | padding-inline: 32px;
43 |
44 | &:hover {
45 | transform: scale(1.04);
46 | }
47 |
48 | &:active {
49 | background-color: var(--spice-subtext);
50 | box-shadow: none;
51 | transform: scale(1);
52 | }
53 |
54 | &:disabled {
55 | opacity: 0.5;
56 | cursor: not-allowed;
57 | }
58 | }
59 |
60 | .circle {
61 | padding-inline: 16px;
62 | width: 48px;
63 | height: 48px;
64 |
65 | svg {
66 | position: absolute;
67 | top: 50%;
68 | left: 50%;
69 | transform: translate(-50%, -50%);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/styles/modules/toggle.module.scss:
--------------------------------------------------------------------------------
1 | // This was taken from the Spotify styles
2 | .toggle-wrapper {
3 | display: -webkit-inline-box;
4 | display: -ms-inline-flexbox;
5 | display: inline-flex;
6 | position: relative;
7 |
8 | -webkit-box-align: center;
9 | -ms-flex-align: center;
10 | align-items: center;
11 |
12 | cursor: pointer;
13 | }
14 |
15 | // Hide disabled toggle rows (i.e. the "Extensions" one)
16 | .disabled {
17 | opacity: 0;
18 | pointer-events: none;
19 | }
20 |
21 | .toggle-input {
22 | opacity: 0;
23 | pointer-events: none;
24 | position: absolute;
25 |
26 | &:checked ~ .toggle-indicator-wrapper {
27 | // Checked colour
28 | background-color: var(--spice-text);
29 |
30 | .toggle-indicator {
31 | // From xpui.css
32 | // Unchecked colour
33 | background-color: #fff;
34 | left: auto;
35 | right: 2px;
36 |
37 | // From