├── .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 |
10 | {props.authors.map((author) => { 11 | return ( 12 | e.stopPropagation()} 21 | key={author.name + author.url} 22 | > 23 | {author.name} 24 | 25 | ); 26 | })} 27 |
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 | 7 | 11 | 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 | 30 | 31 | 41 | 51 | 52 | 53 | 63 | 73 | 74 | 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 | 14 | 15 | 16 | 17 | 18 | 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 | 6 | 7 | 8 | 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 | 109 | 113 | 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 |
19 |
20 | 23 |
24 | setCode(code)} 27 | highlight={(code) => highlight(code, languages.ini)} 28 | textareaId="color-ini-editor" 29 | textareaClassName="color-ini-editor" 30 | readOnly={!themeManifest} 31 | placeholder={t("devTools.colorIniEditorPlaceholder")} 32 | style={{ 33 | fontFamily: "monospace", 34 | resize: "none" 35 | }} 36 | /> 37 |
38 | 39 |
40 | 41 | {/* Create a box containing the invalid css classnames fetched from "getInvalidCSS()"*/} 42 |
43 |

{t("devTools.invalidCSS")}

44 | 45 |
46 | {getInvalidCSS().map((cssClass, index) => { 47 | return ( 48 | // biome-ignore lint/suspicious/noArrayIndexKey: CSS classname is not unique 49 |
50 | {cssClass} 51 |
52 | ); 53 | })} 54 |
55 |
56 |
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 |
54 |

{t("updateModal.howToUpgrade")}

55 | {t("updateModal.viewGuide")} 56 |
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 |
155 | 156 |
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 |
28 |
29 |
30 | 31 | 32 |
33 |
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