Compare commits
149 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77182042dd | ||
|
|
7492c86124 | ||
|
|
55693078f6 | ||
|
|
2c8dea7f5d | ||
|
|
6d23932d0b | ||
|
|
d7e355bf46 | ||
|
|
c9ec3971ad | ||
|
|
bb7017960b | ||
|
|
1064087b48 | ||
|
|
3edfc3fa6b | ||
|
|
239033ad5e | ||
|
|
145cfe9b53 | ||
|
|
2e55b23b44 | ||
|
|
8c5a45c64a | ||
|
|
8d9cdf3df5 | ||
|
|
9f5a988eca | ||
|
|
167e253a86 | ||
|
|
ba5846bc41 | ||
|
|
d687a13cb7 | ||
|
|
33134709ed | ||
|
|
13e1f64054 | ||
|
|
827c9f3ba0 | ||
|
|
f13a3c1ff9 | ||
|
|
14da00bf74 | ||
|
|
23fe7deb15 | ||
|
|
a146134b1d | ||
|
|
4c134a94a0 | ||
|
|
14902a060f | ||
|
|
c45c5faad0 | ||
|
|
162616c2cb | ||
|
|
301a972a53 | ||
|
|
290df59e02 | ||
|
|
3e2cde1e53 | ||
|
|
eb9a7b1939 | ||
|
|
7d3341b7a7 | ||
|
|
d78ae80ef1 | ||
|
|
316e7581cf | ||
|
|
36fe1f5a2f | ||
|
|
273a5b3e7e | ||
|
|
f9c30d230a | ||
|
|
bc200bfef6 | ||
|
|
13186767e8 | ||
|
|
d69c825555 | ||
|
|
56b109cc01 | ||
|
|
decfe17aa2 | ||
|
|
b55a82bf99 | ||
|
|
c052260a8b | ||
|
|
ee98ffd134 | ||
|
|
305d70fb6d | ||
|
|
e50b8b4bb3 | ||
|
|
ca0d24fd11 | ||
|
|
fa29a12d00 | ||
|
|
0ecd8d6166 | ||
|
|
c5fec9cbd1 | ||
|
|
11eae40857 | ||
|
|
604ddfbf97 | ||
|
|
1b45d68568 | ||
|
|
2bfa6e55db | ||
|
|
48c475ee9a | ||
|
|
8517327ee6 | ||
|
|
e2e6214159 | ||
|
|
7ac95c23a9 | ||
|
|
048d007430 | ||
|
|
0e50a92431 | ||
|
|
4dd2d42ca4 | ||
|
|
ead9615ef3 | ||
|
|
cd325a3f05 | ||
|
|
6c84a848fc | ||
|
|
b3e108f2bf | ||
|
|
61cbee1c1d | ||
|
|
8edba57dd5 | ||
|
|
55e6ef0ca2 | ||
|
|
25b5b771dd | ||
|
|
9e7349712b | ||
|
|
0e946cf05a | ||
|
|
f69afbb882 | ||
|
|
467a971d31 | ||
|
|
55b714dbda | ||
|
|
34bd5fe4f8 | ||
|
|
76fda08a71 | ||
|
|
651896418c | ||
|
|
e6ef0953ea | ||
|
|
8968202a3e | ||
|
|
8747efdd7b | ||
|
|
3278f1fbe0 | ||
|
|
0794a51b56 | ||
|
|
200b112fe1 | ||
|
|
193953dc91 | ||
|
|
1c4ba1a29d | ||
|
|
9ed25390c0 | ||
|
|
164058b875 | ||
|
|
eb87c41128 | ||
|
|
5d5d1bc3c1 | ||
|
|
836c13abec | ||
|
|
7e96398958 | ||
|
|
1a61984656 | ||
|
|
7e59834be4 | ||
|
|
80b4d68835 | ||
|
|
3629e883a5 | ||
|
|
7f559d2a2c | ||
|
|
27cd9a8a0e | ||
|
|
b2cfe07578 | ||
|
|
e18320acb3 | ||
|
|
2d30ff934e | ||
|
|
cd24ce8f06 | ||
|
|
a00193acde | ||
|
|
cf9f6e154b | ||
|
|
6ac36c80a3 | ||
|
|
b60682cac5 | ||
|
|
fdfe690b79 | ||
|
|
83546ad90c | ||
|
|
098a112819 | ||
|
|
7c2fbea06f | ||
|
|
9994713e55 | ||
|
|
d206317f45 | ||
|
|
2e1b7a05b5 | ||
|
|
8e702a49e1 | ||
|
|
5d537336a9 | ||
|
|
bcafa8b5a4 | ||
|
|
6cc4b7d492 | ||
|
|
af5af92b62 | ||
|
|
5e0236dff5 | ||
|
|
ea1ba797f6 | ||
|
|
a5b92ed62c | ||
|
|
3798bc2723 | ||
|
|
61db2917f8 | ||
|
|
89742ae74a | ||
|
|
4328a81a1c | ||
|
|
219c16872c | ||
|
|
1296e13c73 | ||
|
|
c471531e71 | ||
|
|
e4110f8df3 | ||
|
|
38d70581ac | ||
|
|
3f308a2c9e | ||
|
|
328cf2a59a | ||
|
|
4b251dfa8d | ||
|
|
f0a562f70a | ||
|
|
83b2801a36 | ||
|
|
675ff2d94d | ||
|
|
8264eb33dd | ||
|
|
fe0503e9f8 | ||
|
|
9afccf31c3 | ||
|
|
980ed8d747 | ||
|
|
71ce39abdb | ||
|
|
c2d7f851b1 | ||
|
|
61db261115 | ||
|
|
18f0d454be | ||
|
|
f45f570730 | ||
|
|
a6c1a89e5c |
3
.babelrc
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"presets": ["@babel/env"]
|
||||
}
|
||||
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve (After v3.1.1, preferably open this report from inside the dribbblish settings `About > Report Bugs` as it adds additional info automatically)
|
||||
about: Create a report to help us improve (After v3.1.1, preferably open this report from inside the Dribbblish settings `About > Report Bugs` as it adds additional info automatically)
|
||||
title: ""
|
||||
labels: ["bug"]
|
||||
assignees: ""
|
||||
|
|
@ -8,28 +8,32 @@ assignees: ""
|
|||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
<!-- If applicable, add screenshots to help explain your problem. -->
|
||||
|
||||
**Desktop Setup**
|
||||
- Operating System: [e.g. Windows 10, MacOS 10.14]
|
||||
- Spotify version [e.g. 1.1.71.560] from `Menu (Three dots top left) > Help > About`
|
||||
- Spicetify version [e.g. 2.4.1] run `spicetify -v`
|
||||
- Dribbblish version [e.g. 3.0.1] from `Dribblish Settings > About`
|
||||
- Operating System: <!-- [e.g. Windows 10, MacOS 10.14] -->
|
||||
- Spotify: <!-- [e.g. 1.1.71.560] from `Menu (Three dots top left) > Help > About` -->
|
||||
- Spicetify: <!-- [e.g. 2.4.1] run `spicetify -v` -->
|
||||
- Dribbblish: <!-- [e.g. 3.0.1] from `Dribbblish Settings > About` -->
|
||||
|
||||
**Logs**
|
||||
Add logs from console. To do that
|
||||
1. Run `spicetify enable-devtool` in terminal
|
||||
2. Spotify will be restarted
|
||||
3. Hit <kbd>Ctrl + Shift + I</kbd> to open DevTools window
|
||||
4. Navigate to tab Console
|
||||
5. Copy console window content.
|
||||
<!--
|
||||
Add logs from console. To do that
|
||||
1. Run `spicetify enable-devtool` in terminal
|
||||
2. Spotify will be restarted
|
||||
3. Hit <kbd>Ctrl + Shift + I</kbd> to open DevTools window
|
||||
4. Navigate to tab Console
|
||||
5. Copy console window content.
|
||||
-->
|
||||
|
||||
<!-- Paste logs below or attach a screenshot -->
|
||||
```console
|
||||
(Please paste here console logs or attach a screenshot)
|
||||
[paste logs here]
|
||||
```
|
||||
|
|
|
|||
8
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
|
@ -8,13 +8,13 @@ assignees: ""
|
|||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
<!-- A clear and concise description of what you want to happen. -->
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
||||
|
|
|
|||
26
.github/workflows/post-release.yaml
vendored
|
|
@ -12,12 +12,38 @@ jobs:
|
|||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: ${{ github.event.repository.full_name }}
|
||||
ref: ${{ github.event.repository.default_branch }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Put version in env
|
||||
run: echo "DRIBBBLISH_VERSION=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV
|
||||
|
||||
- name: Get commit SHA
|
||||
run: echo "LATEST_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "14"
|
||||
|
||||
- name: Build Webpack
|
||||
run: |
|
||||
npm install
|
||||
npm run build
|
||||
echo "${{ env.DRIBBBLISH_VERSION }}-${{ env.LATEST_SHA }}" > dist/VERSION
|
||||
env:
|
||||
DRIBBBLISH_VERSION: ${{ env.DRIBBBLISH_VERSION }}
|
||||
COMMIT_HASH: ${{ env.LATEST_SHA }}
|
||||
|
||||
- name: Push to release branch
|
||||
uses: s0/git-publish-subdir-action@develop
|
||||
env:
|
||||
REPO: self
|
||||
BRANCH: release-stable
|
||||
FOLDER: dist
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set package.json version
|
||||
run: |
|
||||
sed -i 's/"version":.*",/"version": "${{ env.DRIBBBLISH_VERSION }}",/' package.json
|
||||
|
|
|
|||
3
.github/workflows/prettier.yml
vendored
|
|
@ -14,11 +14,12 @@ jobs:
|
|||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: ${{ github.event.repository.full_name }}
|
||||
ref: ${{ github.head_ref }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Prettify code
|
||||
uses: creyD/prettier_action@v4.0
|
||||
uses: creyD/prettier_action@v4.1.1
|
||||
with:
|
||||
prettier_options: --write **/*.{js,css}
|
||||
same_commit: True
|
||||
|
|
|
|||
4
.github/workflows/release.yaml
vendored
|
|
@ -28,13 +28,13 @@ jobs:
|
|||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "14"
|
||||
node-version: "15"
|
||||
|
||||
- name: Build Webpack
|
||||
run: |
|
||||
npm install
|
||||
npm run build
|
||||
echo "${{ github.event.inputs.version }}" > dist/VERSION
|
||||
echo "${{ github.event.inputs.version }}-${{ env.LATEST_SHA }}" > dist/VERSION
|
||||
env:
|
||||
DRIBBBLISH_VERSION: ${{ github.event.inputs.version }}
|
||||
COMMIT_HASH: ${{ env.LATEST_SHA }}
|
||||
|
|
|
|||
21
.github/workflows/stale.yml
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
name: 'Close stale issues and PRs'
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 1 * * *'
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v4
|
||||
with:
|
||||
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days.'
|
||||
stale-pr-message: 'This PR is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 15 days.'
|
||||
close-issue-message: 'This issue was closed because it has been stalled for 7 days with no activity.'
|
||||
close-pr-message: 'This PR was closed because it has been stalled for 15 days with no activity.'
|
||||
days-before-issue-stale: 60
|
||||
days-before-pr-stale: 60
|
||||
days-before-issue-close: 7
|
||||
days-before-pr-close: 15
|
||||
operations-per-run: 100
|
||||
ascending: true
|
||||
17
.github/workflows/webpack-test.yml
vendored
|
|
@ -10,6 +10,7 @@ jobs:
|
|||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: ${{ github.event.repository.full_name }}
|
||||
ref: ${{ github.head_ref }}
|
||||
|
||||
- name: Get commit SHA
|
||||
|
|
@ -18,18 +19,28 @@ jobs:
|
|||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "14"
|
||||
node-version: "15"
|
||||
|
||||
- name: Test-Build Webpack
|
||||
run: |
|
||||
npm install
|
||||
npm run build
|
||||
echo "Dev" > dist/VERSION
|
||||
echo "Beta-${{ env.LATEST_SHA }}" > dist/VERSION
|
||||
env:
|
||||
DRIBBBLISH_VERSION: Beta
|
||||
COMMIT_HASH: ${{ env.LATEST_SHA }}
|
||||
|
||||
- name: Push to beta-release branch
|
||||
if: github.event_name == 'push'
|
||||
uses: s0/git-publish-subdir-action@develop
|
||||
env:
|
||||
REPO: self
|
||||
BRANCH: release-beta
|
||||
FOLDER: dist
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: DribbblishDynamic_vDev-${{ env.LATEST_SHA }}
|
||||
name: DribbblishDynamic_vBeta-${{ env.LATEST_SHA }}
|
||||
path: dist/**
|
||||
|
|
|
|||
27
CHANGELOG.md
|
|
@ -1,24 +1,13 @@
|
|||
### Important changes
|
||||
This release changes the color selection algorithm.
|
||||
If you liked the old one better you can just change `Theme > Color Selection Algorithm` to `Vibrant`.
|
||||
|
||||
Added:
|
||||
- `Report Bugs`, `Request Feature` and `Changelog` buttons to `Settings > About`
|
||||
- Markdown parsing for settings descriptions
|
||||
- Option to have a button to open the settings next to your profile picture
|
||||
- Ability to hide premium features in addition to ads
|
||||
- Ability to show current artist's genres
|
||||
|
||||
Fixed:
|
||||
- Fonts looking blurry
|
||||
- Notification popups are being invisible when the (dribbblish) settings are open
|
||||
- Missing on/off times settings for `Based on Time` dark mode (#107)
|
||||
- Playing icon position being wrong when listening to a playlist that is inside a folder ([#106 (comment)](https://github.com/JulienMaille/dribbblish-dynamic-theme/issues/106#issuecomment-967208507))
|
||||
- Alignment of right expanded cover
|
||||
- Slider tooltip is incorrect after a reset (#111)
|
||||
- Custom search input not being focussed after clicking
|
||||
- When changing songs, the color shifts to blue for a second (#179)
|
||||
|
||||
Improved:
|
||||
- The settings UI now better represents grouped items
|
||||
- Settings that have been changed from default will now show a line next to them. Inspired by the [Visual Studio Code settings UI](https://d33wubrfki0l68.cloudfront.net/d1f1ea4def506997ced23d3d912154794e530e1c/063d2/assets/img/blog/2020-09-17-vscode-settings/settings-ui.png)
|
||||
- Checkbox / Switch input styles are now more in line with other input styles
|
||||
- Available updates are now shown as a clickable button next to your user icon instead of having to open the user menu
|
||||
- The "offline" icon is now handled by dribbblish and fits in with the other info icons
|
||||
- Hovering over the release date in the album info now shows the full date
|
||||
- Add `embedWidgetGenerator` modals to custom modal styles. (Things like the [spicetify-marketplace](https://github.com/CharlieS1103/spicetify-marketplace) options)
|
||||
- Changed custom app tab styles to fit in with the theme
|
||||
- Changed notification styles to fit in with other elements of the theme
|
||||
- The visualizer icon when playing a song from a playlist is now colored
|
||||
28
README.md
|
|
@ -1,26 +1,29 @@
|
|||
# Dribbblish Dynamic
|
||||
A theme for [Spicetify](https://github.com/khanhas/spicetify-cli)
|
||||
|
||||
<a href="https://github.com/JulienMaille/dribbblish-dynamic-theme/releases/latest"><img src="https://img.shields.io/github/release/JulienMaille/dribbblish-dynamic-theme/all.svg"></a>
|
||||
<a href="https://github.com/JulienMaille/dribbblish-dynamic-theme/releases"><img src="https://img.shields.io/github/downloads/JulienMaille/dribbblish-dynamic-theme/total.svg"></a>
|
||||
|
||||
### Preview
|
||||
|
||||
<img src="showcase-images/preview.gif" alt="img" width="500px">
|
||||
<img src="https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/main/showcase-images/preview.gif" alt="img" width="500px">
|
||||
|
||||
## Features
|
||||
### Resizable sidebar
|
||||
|
||||
<img src="showcase-images/resize-sidebar.png" alt="img" width="500px">
|
||||
<img src="https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/main/showcase-images/resize-sidebar.png" alt="img" width="500px">
|
||||
|
||||
### Customizable sidebar
|
||||
Rearrange icons positions, stick icons to header or hide unnecessary to save space.
|
||||
Turn on "Sidebar config" mode in Profile menu and hover on icon to show control buttons.
|
||||
After you finish customizing, turn off Config mode in Profile menu to save.
|
||||
|
||||
<img src="showcase-images/customize-sidebar.png" alt="img" width="500px">
|
||||
<img src="https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/main/showcase-images/customize-sidebar.png" alt="img" width="500px">
|
||||
|
||||
### Playlist Folder image
|
||||
Right click at folder and choose images for your playlist folder. Every image formats supported by Chrome can be used, but do keep image size small and in compressed format.
|
||||
|
||||
<img src="showcase-images/playlist-folders.gif" alt="img" width="500px">
|
||||
<img src="https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/main/showcase-images/playlist-folders.gif" alt="img" width="500px">
|
||||
|
||||
### Left/Right expanded cover
|
||||
In profile menu, toggle option "Right expanded cover" to change expanded current track cover image to left or right side, wherever you prefer.
|
||||
|
|
@ -42,7 +45,7 @@ curl -fsSL https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-the
|
|||
1. Download the latest [DribbblishDynamic_vX.X.X.zip](https://github.com/JulienMaille/dribbblish-dynamic-theme/releases/latest)
|
||||
2. Extract the files to your [Spicetify/Themes folder](https://github.com/khanhas/spicetify-cli/wiki/Customization#themes)
|
||||
3. Copy `dribbblish-dynamic.js` to your [Spicetify/Extensions folder](https://github.com/khanhas/spicetify-cli/wiki/Extensions#installing)
|
||||
4. Add the 2 lines in Patch section of the config file (see details below)
|
||||
4. Add the 2 lines in `[Patch]` section of the config file (see details below)
|
||||
5. Run:
|
||||
```
|
||||
spicetify config extensions dribbblish-dynamic.js
|
||||
|
|
@ -62,13 +65,20 @@ xpui.js_repl_8008 = ,${1}58,
|
|||
|
||||
## Hide Window Controls
|
||||
Windows user, please edit your Spotify shortcut and add flag `--transparent-window-controls` after the Spotify.exe:
|
||||
To edit an taskbar shortcut, right click it, then right click Spotify in the list again.
|
||||
To edit a taskbar shortcut, right click it, then right click Spotify in the list again.
|
||||
|
||||
<img src="showcase-images/windows-shortcut-instruction.png" alt="img">
|
||||
<img src="https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/main/showcase-images/windows-shortcut-instruction.png" alt="img">
|
||||
|
||||
In addition to `--transparent-window-controls` you can set `Windows Top Bars` to `Solid` or `Transparent` to look like this:
|
||||
|
||||
<img src="showcase-images/top-bars.png" alt="img" width="500px">
|
||||
<img src="https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/main/showcase-images/top-bars.png" alt="img" width="500px">
|
||||
|
||||
## Follow system dark/light theme (Powershell)
|
||||
Automatic dark mode should work on MacOs and Linux out of the box.
|
||||
From Spotify > v1.1.70, dark mode is forced in Windows builds. You will need to patch Spotify.exe using this script:
|
||||
```powershell
|
||||
Invoke-WebRequest -UseBasicParsing "https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/master/patch-dark-mode.ps1" | Invoke-Expression
|
||||
```
|
||||
|
||||
## Uninstall
|
||||
### Windows (PowerShell)
|
||||
|
|
@ -85,6 +95,6 @@ curl -fsSL https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-the
|
|||
1. Remove Patch lines you added in config file earlier.
|
||||
2. Run:
|
||||
```
|
||||
spicetify config extensions dribbblish-dynamic.js-
|
||||
spicetify config current_theme " " extensions dribbblish-dynamic.js-
|
||||
spicetify apply
|
||||
```
|
||||
|
|
|
|||
|
|
@ -117,12 +117,12 @@ xpui.js_repl_8008=,`${1}58,
|
|||
|
||||
Write-Part "APPLYING";
|
||||
$backupVer = $configFile -match "^version"
|
||||
$version = ConvertFrom-StringData $backupVer[0]
|
||||
if ($version.version.Length -gt 0) {
|
||||
if ($backupVer.Length -gt 0) {
|
||||
spicetify apply
|
||||
} else {
|
||||
spicetify backup apply
|
||||
}
|
||||
Write-Done
|
||||
}
|
||||
else {
|
||||
Write-Part "`nYour Powershell version is less than "; Write-Emphasized "$PSMinVersion";
|
||||
|
|
|
|||
22
manifest.json.rem
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
[
|
||||
{
|
||||
"name": "Dribbblish Dynamic",
|
||||
"description": "A mod of Dribbblish theme for Spicetify with support for light/dark modes and album art based colors. (Beta Release)",
|
||||
"branch": "release-stable",
|
||||
"preview": "https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/main/showcase-images/preview.gif",
|
||||
"readme": "https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/main/README.md",
|
||||
"usercss": "user.css",
|
||||
"schemes": "color.ini",
|
||||
"include": ["https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/release-stable/dribbblish-dynamic.js"]
|
||||
},
|
||||
{
|
||||
"name": "Dribbblish Dynamic (Beta)",
|
||||
"description": "A mod of Dribbblish theme for Spicetify with support for light/dark modes and album art based colors. (Beta Release)",
|
||||
"branch": "release-beta",
|
||||
"preview": "https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/main/showcase-images/preview.gif",
|
||||
"readme": "https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/main/README.md",
|
||||
"usercss": "user.css",
|
||||
"schemes": "color.ini",
|
||||
"include": ["https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/release-beta/dribbblish-dynamic.js"]
|
||||
}
|
||||
]
|
||||
9109
package-lock.json
generated
Normal file
23
package.json
|
|
@ -1,29 +1,34 @@
|
|||
{
|
||||
"name": "dribbblish-dynamic-theme",
|
||||
"version": "3.1.1",
|
||||
"version": "4.1.1",
|
||||
"homepage": "https://github.com/JulienMaille/dribbblish-dynamic-theme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/JulienMaille/dribbblish-dynamic-theme/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.17.0",
|
||||
"@babel/preset-env": "^7.16.5",
|
||||
"@babel/register": "^7.16.5",
|
||||
"@material-icons/svg": "^1.0.22",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"copy-webpack-plugin": "^9.0.1",
|
||||
"sass": "^1.43.4",
|
||||
"sass": "^1.43.5",
|
||||
"sass-loader": "^12.2.0",
|
||||
"webpack": "^5.58.2",
|
||||
"webpack-cli": "^4.9.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack --mode=development"
|
||||
"build": "webpack"
|
||||
},
|
||||
"dependencies": {
|
||||
"chroma-js": "^2.1.2",
|
||||
"colorthief": "^2.3.2",
|
||||
"jquery": "^3.6.0",
|
||||
"markdown-it": "^12.2.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.defaultsdeep": "^4.6.1",
|
||||
"markdown-it": "^12.3.2",
|
||||
"markdown-it-attrs": "^4.1.0",
|
||||
"markdown-it-bracketed-spans": "^1.0.1",
|
||||
"moment": "^2.29.1",
|
||||
"node-vibrant": "^3.1.6"
|
||||
"moment": "^2.29.2",
|
||||
"node-vibrant": "^3.1.6",
|
||||
"svgson": "^5.2.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
39
patch-dark-mode.ps1
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
Get-Process -Name Spotify -ErrorAction SilentlyContinue | Stop-Process -Force
|
||||
Get-Process -Name SpotifyWebHelper -ErrorAction SilentlyContinue | Stop-Process -Force
|
||||
|
||||
$sp = spicetify config spotify_path
|
||||
$sp += "\Spotify.exe"
|
||||
Copy-Item $sp ($sp + ".backup")
|
||||
|
||||
$bytes = [System.IO.File]::ReadAllBytes($sp);
|
||||
$toRemove = [System.Text.Encoding]::UTF8.GetBytes("force-dark-mode");
|
||||
|
||||
$sw = [System.Diagnostics.Stopwatch]::StartNew()
|
||||
for ($i = 0; $i -lt $bytes.Length; $i++) {
|
||||
if ($sw.Elapsed.TotalMilliseconds -ge 1000) {
|
||||
Write-Progress -Activity "Enabling dark mode in Spotify.exe" -status "Patching binary file $i" -percentComplete ($i / $bytes.Length*100);
|
||||
$sw.Reset();
|
||||
$sw.Start();
|
||||
}
|
||||
$found = $true
|
||||
for ($j = 0; $j -lt $toRemove.Length; $j++) {
|
||||
if ($bytes[$i + $j] -ne $toRemove[$j]) {
|
||||
$found = $false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($found -eq $true) {
|
||||
for ($j = 0; $j -lt $toRemove.Length; $j++) {
|
||||
$bytes[$i + $j] = [byte]65;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[System.IO.File]::WriteAllBytes($sp, $bytes);
|
||||
if ( $found ) {
|
||||
Write-Host "The patch is complete." -ForegroundColor "Green"
|
||||
} else {
|
||||
Write-Host "Failed to patch the file." -ForegroundColor "Red"
|
||||
}
|
||||
131
src/icons/equaliser.svg
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22 24'>
|
||||
<defs>
|
||||
<style>
|
||||
@keyframes play {
|
||||
0% {
|
||||
transform: scaleY(1);
|
||||
}
|
||||
|
||||
3.3% {
|
||||
transform: scaleY(0.9583);
|
||||
}
|
||||
|
||||
6.6% {
|
||||
transform: scaleY(0.9166);
|
||||
}
|
||||
|
||||
9.9% {
|
||||
transform: scaleY(0.8333);
|
||||
}
|
||||
|
||||
13.3% {
|
||||
transform: scaleY(0.7083);
|
||||
}
|
||||
|
||||
16.6% {
|
||||
transform: scaleY(0.5416);
|
||||
}
|
||||
|
||||
19.9% {
|
||||
transform: scaleY(0.4166);
|
||||
}
|
||||
|
||||
23.3% {
|
||||
transform: scaleY(0.25);
|
||||
}
|
||||
|
||||
26.6% {
|
||||
transform: scaleY(0.1666);
|
||||
}
|
||||
|
||||
29.9% {
|
||||
transform: scaleY(0.125);
|
||||
}
|
||||
|
||||
33.3% {
|
||||
transform: scaleY(0.125);
|
||||
}
|
||||
|
||||
36.6% {
|
||||
transform: scaleY(0.1666);
|
||||
}
|
||||
39.9% {
|
||||
transform: scaleY(0.1666);
|
||||
}
|
||||
43.3% {
|
||||
transform: scaleY(0.2083);
|
||||
}
|
||||
46.6% {
|
||||
transform: scaleY(0.2916);
|
||||
}
|
||||
49.9% {
|
||||
transform: scaleY(0.375);
|
||||
}
|
||||
53.3% {
|
||||
transform: scaleY(0.5);
|
||||
}
|
||||
56.6% {
|
||||
transform: scaleY(0.5833);
|
||||
}
|
||||
59.9% {
|
||||
transform: scaleY(0.625);
|
||||
}
|
||||
63.3% {
|
||||
transform: scaleY(0.6666);
|
||||
}
|
||||
66.6% {
|
||||
transform: scaleY(0.6666);
|
||||
}
|
||||
69.9% {
|
||||
transform: scaleY(0.6666);
|
||||
}
|
||||
73.3% {
|
||||
transform: scaleY(0.6666);
|
||||
}
|
||||
76.6% {
|
||||
transform: scaleY(0.7083);
|
||||
}
|
||||
79.9% {
|
||||
transform: scaleY(0.75);
|
||||
}
|
||||
83.3% {
|
||||
transform: scaleY(0.8333);
|
||||
}
|
||||
86.6% {
|
||||
transform: scaleY(0.875);
|
||||
}
|
||||
89.9% {
|
||||
transform: scaleY(0.9166);
|
||||
}
|
||||
93.3% {
|
||||
transform: scaleY(0.9583);
|
||||
}
|
||||
96.6% {
|
||||
transform: scaleY(1);
|
||||
}
|
||||
}
|
||||
|
||||
svg[icon-type="dribbblish"][icon-name="equaliser"] > rect[i="0"] {
|
||||
transform-origin: bottom;
|
||||
animation: play 0.9s -0.51s infinite;
|
||||
}
|
||||
svg[icon-type="dribbblish"][icon-name="equaliser"] > rect[i="1"] {
|
||||
transform-origin: bottom;
|
||||
animation: play 0.9s infinite;
|
||||
}
|
||||
svg[icon-type="dribbblish"][icon-name="equaliser"] > rect[i="2"] {
|
||||
transform-origin: bottom;
|
||||
animation: play 0.9s -0.15s infinite;
|
||||
}
|
||||
svg[icon-type="dribbblish"][icon-name="equaliser"] > rect[i="3"] {
|
||||
transform-origin: bottom;
|
||||
animation: play 0.9s -0.75s infinite;
|
||||
}
|
||||
|
||||
</style>
|
||||
</defs>
|
||||
<rect fill="currentColor" i="0" class='cls-1' width='4' height='24'/>
|
||||
<rect fill="currentColor" i="1" class='cls-1' x='6' width='4' height='24'/>
|
||||
<rect fill="currentColor" i="2" class='cls-1' x='12' width='4' height='24'/>
|
||||
<rect fill="currentColor" i="3" class='cls-1' x='18' width='4' height='24'/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
42
src/icons/loading.svg
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<svg viewBox="0 0 66 66" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<style>
|
||||
/* From https://codepen.io/mrrocks/pen/EiplA */
|
||||
@keyframes loader-rotator {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes loader-dash {
|
||||
0% {
|
||||
stroke-dashoffset: 187;
|
||||
}
|
||||
50% {
|
||||
stroke-dashoffset: 46.75;
|
||||
transform: rotate(135deg);
|
||||
}
|
||||
100% {
|
||||
stroke-dashoffset: 187;
|
||||
transform: rotate(450deg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
svg[icon-type="dribbblish"][icon-name="loading"] {
|
||||
animation: loader-rotator 1.4s linear infinite;
|
||||
}
|
||||
|
||||
svg[icon-type="dribbblish"][icon-name="loading"] circle {
|
||||
stroke-dasharray: 187;
|
||||
stroke-dashoffset: 0;
|
||||
transform-origin: center;
|
||||
animation: loader-dash 1.4s ease-in-out infinite;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<circle fill="none" stroke="currentColor" stroke-width="6" stroke-linecap="round" cx="33" cy="33" r="30"></circle>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
3
src/icons/spicetify.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8700 8700">
|
||||
<path d="M3659 8515 c-3 -2 -44 -9 -92 -14 -231 -27 -564 -143 -797 -278 -657 -384 -1069 -1019 -1149 -1773 -6 -52 -15 -126 -20 -165 -24 -162 -8 -434 39 -673 35 -174 149 -498 270 -767 284 -628 398 -939 435 -1180 61 -404 -59 -689 -349 -830 -99 -48 -126 -72 -126 -112 0 -40 49 -60 164 -68 142 -9 209 4 341 68 297 145 495 444 525 792 11 127 24 173 55 187 42 19 69 1 175 -118 142 -160 380 -392 530 -517 155 -128 319 -246 705 -504 425 -285 626 -443 851 -670 191 -193 347 -439 408 -644 34 -113 58 -298 52 -398 -12 -212 -30 -300 -100 -471 -62 -150 -68 -176 -49 -195 19 -20 29 -19 88 9 50 24 169 138 251 241 180 225 320 545 381 870 27 142 28 164 28 430 0 258 -3 293 -28 448 -81 490 -273 1011 -570 1549 -193 350 -241 472 -241 618 -1 89 24 146 76 178 80 49 163 36 240 -36 51 -48 91 -104 199 -284 104 -172 159 -233 292 -319 58 -37 217 -95 332 -120 78 -17 343 -13 407 6 71 21 90 33 86 58 -4 30 -49 53 -160 81 -124 33 -184 57 -262 106 -73 46 -191 165 -241 245 -90 140 -160 360 -191 600 -20 155 -15 430 11 650 23 188 31 549 16 695 -36 352 -122 701 -234 955 -238 537 -689 971 -1247 1197 -153 63 -389 124 -565 147 -78 11 -527 16 -536 6z m1241 -1230 c44 -23 70 -62 70 -107 0 -70 -21 -102 -97 -146 -430 -250 -899 -356 -1468 -332 -221 10 -373 27 -551 64 -66 14 -137 28 -158 31 -50 8 -102 58 -111 107 -10 50 23 121 65 143 39 20 97 17 255 -14 213 -41 587 -77 706 -68 320 25 438 42 621 93 148 40 239 77 430 174 146 74 184 83 238 55z m257 -610 c88 -40 124 -131 85 -216 -27 -61 -47 -77 -212 -162 -289 -150 -581 -245 -920 -301 -390 -64 -657 -68 -1070 -15 -81 10 -297 55 -407 85 -57 16 -106 52 -128 95 -24 45 -19 124 9 166 38 55 72 73 139 73 53 0 235 -35 317 -60 37 -12 132 -23 345 -41 229 -19 541 -1 785 46 283 54 579 153 756 251 112 63 200 102 229 103 11 0 44 -10 72 -24z m276 -696 c49 -13 105 -63 128 -114 38 -83 13 -185 -59 -245 -66 -55 -329 -175 -532 -244 -226 -77 -474 -137 -685 -165 -44 -6 -114 -16 -155 -22 -41 -6 -183 -17 -315 -24 -467 -27 -957 21 -1309 128 -118 36 -166 96 -166 208 0 85 31 138 102 173 61 30 119 33 205 11 110 -29 268 -63 348 -74 44 -6 114 -15 156 -21 95 -14 779 -13 834 0 22 5 60 10 84 10 43 0 104 9 256 36 78 15 127 25 268 59 134 32 367 117 513 186 242 116 253 119 327 98z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
3
src/icons/spotify.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512">
|
||||
<path fill="currentColor" d="M248 8C111.1 8 0 119.1 0 256s111.1 248 248 248 248-111.1 248-248S384.9 8 248 8zm100.7 364.9c-4.2 0-6.8-1.3-10.7-3.6-62.4-37.6-135-39.2-206.7-24.5-3.9 1-9 2.6-11.9 2.6-9.7 0-15.8-7.7-15.8-15.8 0-10.3 6.1-15.2 13.6-16.8 81.9-18.1 165.6-16.5 237 26.2 6.1 3.9 9.7 7.4 9.7 16.5s-7.1 15.4-15.2 15.4zm26.9-65.6c-5.2 0-8.7-2.3-12.3-4.2-62.5-37-155.7-51.9-238.6-29.4-4.8 1.3-7.4 2.6-11.9 2.6-10.7 0-19.4-8.7-19.4-19.4s5.2-17.8 15.5-20.7c27.8-7.8 56.2-13.6 97.8-13.6 64.9 0 127.6 16.1 177 45.5 8.1 4.8 11.3 11 11.3 19.7-.1 10.8-8.5 19.5-19.4 19.5zm31-76.2c-5.2 0-8.4-1.3-12.9-3.9-71.2-42.5-198.5-52.7-280.9-29.7-3.6 1-8.1 2.6-12.9 2.6-13.2 0-23.3-10.3-23.3-23.6 0-13.6 8.4-21.3 17.4-23.9 35.2-10.3 74.6-15.2 117.5-15.2 73 0 149.5 15.2 205.4 47.8 7.8 4.5 12.9 10.7 12.9 22.6 0 13.6-11 23.3-23.2 23.3z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 901 B |
|
|
@ -1,15 +1,15 @@
|
|||
import $ from "jquery";
|
||||
import MarkdownIt from "markdown-it";
|
||||
import MarkdownItAttrs from "markdown-it-attrs";
|
||||
import MarkdownItBracketedSpans from "markdown-it-bracketed-spans";
|
||||
|
||||
import svgUndo from "svg/undo";
|
||||
import { renderMD, defaults } from "./Util";
|
||||
import { icons } from "./Icons";
|
||||
|
||||
export default class ConfigMenu {
|
||||
/** @typedef {"checkbox" | "select" | "button" | "slider" | "number" | "text" | "textarea" | "time" | "color"} DribbblishConfigType */
|
||||
|
||||
/**
|
||||
* @typedef {Object} DribbblishConfigItem
|
||||
* @property {"checkbox" | "select" | "button" | "slider" | "number" | "text" | "textarea" | "time" | "color"} type
|
||||
* @property {String|DribbblishConfigArea} [area={name: "Main Settings", order: 0}]
|
||||
* @property {DribbblishConfigType} type
|
||||
* @property {String | DribbblishConfigArea} [area={name: "Main Settings", order: 0}]
|
||||
* @property {any} [data={}]
|
||||
* @property {Number} [order=0] order < 0 = Higher up | order > 0 = Lower Down
|
||||
* @property {String} key
|
||||
|
|
@ -26,7 +26,7 @@ export default class ConfigMenu {
|
|||
* @property {onAppended} [onAppended]
|
||||
* @property {onChange} [onChange]
|
||||
* @property {DribbblishConfigItem[]} [children=[]]
|
||||
* @property {String} [childOf=null] key of parent (set automatically)
|
||||
* @property {String} [parent=null] key of parent (set automatically)
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
@ -69,30 +69,18 @@ export default class ConfigMenu {
|
|||
/** @type {Spicetify.Menu.Item} */
|
||||
#configButton;
|
||||
|
||||
/** @type {MarkdownIt} */
|
||||
#md;
|
||||
|
||||
constructor() {
|
||||
this.#config = {};
|
||||
this.#configButton = new Spicetify.Menu.Item("Dribbblish Settings", false, () => this.open());
|
||||
this.#configButton.register();
|
||||
this.#md = MarkdownIt({
|
||||
html: true,
|
||||
breaks: true,
|
||||
linkify: true,
|
||||
typographer: true
|
||||
});
|
||||
this.#md.use(MarkdownItBracketedSpans);
|
||||
this.#md.use(MarkdownItAttrs);
|
||||
|
||||
const container = document.createElement("div");
|
||||
container.id = "dribbblish-config";
|
||||
container.innerHTML = /* html */ `
|
||||
<div class="dribbblish-config-container">
|
||||
<button aria-label="Close" class="dribbblish-config-close main-trackCreditsModal-closeBtn">
|
||||
<svg width="18" height="18" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><path d="M31.098 29.794L16.955 15.65 31.097 1.51 29.683.093 15.54 14.237 1.4.094-.016 1.508 14.126 15.65-.016 29.795l1.414 1.414L15.54 17.065l14.144 14.143" fill="currentColor" fill-rule="evenodd"></path></svg>
|
||||
</button>
|
||||
<button aria-label="Close" class="dribbblish-config-close main-trackCreditsModal-closeBtn">${icons.get("close", { size: 24 })}</button>
|
||||
<h1>Dribbblish Settings</h1>
|
||||
<input type="search" placeholder="Search" class="dribbblish-config-search">
|
||||
<div class="dribbblish-config-areas"></div>
|
||||
</div>
|
||||
<div class="dribbblish-config-backdrop"></div>
|
||||
|
|
@ -101,6 +89,7 @@ export default class ConfigMenu {
|
|||
document.body.appendChild(container);
|
||||
$(".dribbblish-config-close").on("click", () => this.close());
|
||||
$(".dribbblish-config-backdrop").on("click", () => this.close());
|
||||
$(".dribbblish-config-search").on("input", (e) => this.#search(e.target.value));
|
||||
}
|
||||
|
||||
open() {
|
||||
|
|
@ -111,67 +100,6 @@ export default class ConfigMenu {
|
|||
$("#dribbblish-config").removeAttr("active");
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {DribbblishConfigItem} options
|
||||
*/
|
||||
#addInputHTML(options) {
|
||||
this.registerArea(options.area);
|
||||
const parent = document.querySelector(`.dribbblish-config-area[name="${options.area.name}"] .dribbblish-config-area-items`);
|
||||
|
||||
const elem = document.createElement("div");
|
||||
if (options.order != 0) elem.style.order = options.order;
|
||||
elem.classList.add("dribbblish-config-item");
|
||||
elem.setAttribute("key", options.key);
|
||||
elem.setAttribute("type", options.type);
|
||||
if (options.hidden) elem.setAttribute("hidden", true);
|
||||
if (options.childOf) elem.setAttribute("parent", options.childOf);
|
||||
if (options.children.length > 0) elem.setAttribute("children", options.children.map((c) => c.key).join(" "));
|
||||
elem.innerHTML = /* html */ `
|
||||
${
|
||||
options.name != null && options.description != null
|
||||
? /* html */ `
|
||||
<div class="dribbblish-config-item-header">
|
||||
<h2 class="x-settings-title main-type-cello" as="h2" empty="${options.name == null}">
|
||||
${options.name}
|
||||
${options.resetButton ? /* html */ `<button aria-label="Reset" class="dribbblish-config-item-reset main-trackCreditsModal-closeBtn">${svgUndo}</button>` : ""}
|
||||
</h2>
|
||||
<label class="main-type-mesto" empty="${options.description == null}" markdown>${this.#md.render(options.description)}</label>
|
||||
</div>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
<div class="dribbblish-config-item-input">
|
||||
<label class="x-toggle-wrapper x-settings-secondColumn">
|
||||
${options.input}
|
||||
</label>
|
||||
</div>
|
||||
`;
|
||||
|
||||
if (options.insertOnTop && parent.children.length > 0) {
|
||||
parent.insertBefore(elem, parent.children[0]);
|
||||
} else {
|
||||
parent.appendChild(elem);
|
||||
}
|
||||
|
||||
const $inputElem = $(elem).find("input, textarea, select");
|
||||
const $resetButton = $(elem).find(".dribbblish-config-item-reset");
|
||||
|
||||
if ($resetButton.length > 0) {
|
||||
$resetButton.on("click", () => {
|
||||
this.reset(options.key);
|
||||
const defaultVal = this.get(options.key);
|
||||
if (options.type == "checkbox") {
|
||||
$inputElem.prop("checked", defaultVal);
|
||||
} else {
|
||||
$inputElem.prop("value", defaultVal);
|
||||
if (options.type == "slider") $inputElem.attr("tooltip", defaultVal);
|
||||
}
|
||||
options.onChange(defaultVal);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DribbblishConfigItem} options
|
||||
*/
|
||||
|
|
@ -194,10 +122,10 @@ export default class ConfigMenu {
|
|||
onAppended: () => {},
|
||||
onChange: () => {},
|
||||
children: [],
|
||||
childOf: null
|
||||
parent: null
|
||||
};
|
||||
// Set Defaults
|
||||
options = { ...defaultOptions, ...options };
|
||||
options = defaults(options, defaultOptions);
|
||||
if (typeof options.area == "string") options.area = { name: options.area, order: 0 };
|
||||
options.description = options.description
|
||||
.split("\n")
|
||||
|
|
@ -206,17 +134,17 @@ export default class ConfigMenu {
|
|||
.join("\n");
|
||||
options._onChange = options.onChange;
|
||||
options.onChange = (val) => {
|
||||
const isValid = validate(val);
|
||||
$(`.dribbblish-config-item[key="${options.key}"]`).attr("changed", isValid === true && val != options.defaultValue ? "" : null);
|
||||
const isValid = validate(val) === true;
|
||||
$(`.dribbblish-config-item[key="${options.key}"]`).attr("changed", options.type != "button" && isValid && val != options.defaultValue ? "" : null);
|
||||
if (!isValid) return;
|
||||
this.set(options.key, val, options.save);
|
||||
|
||||
options._onChange.call(options, val);
|
||||
const show = options.showChildren.call(options, val);
|
||||
options.children.forEach((child) => this.#setHidden(child.key, Array.isArray(show) ? !show.includes(child.key) : !show));
|
||||
options.children.forEach((child) => this.setHidden(child.key, Array.isArray(show) ? !show.includes(child.key) : !show));
|
||||
};
|
||||
options.children = options.children.map((child) => {
|
||||
return { ...child, area: options.area, childOf: options.key };
|
||||
return { ...child, area: options.area, parent: options.key, order: options.order ?? 0 + child.order ?? 0 };
|
||||
});
|
||||
|
||||
this.#config[options.key] = options;
|
||||
|
|
@ -233,33 +161,33 @@ export default class ConfigMenu {
|
|||
return isValid;
|
||||
}
|
||||
|
||||
let elem;
|
||||
let input;
|
||||
if (options.type == "checkbox") {
|
||||
const input = /* html */ `
|
||||
<input id="dribbblish-config-input-${options.key}" class="x-toggle-input" type="checkbox"${this.get(options.key) ? " checked" : ""}>
|
||||
<span class="x-toggle-indicatorWrapper">
|
||||
<span class="x-toggle-indicator"></span>
|
||||
</span>
|
||||
`;
|
||||
this.#addInputHTML({ ...options, input });
|
||||
elem = document.createDocumentFragment();
|
||||
|
||||
$(`#dribbblish-config-input-${options.key}`).on("change", (e) => {
|
||||
input = document.createElement("input");
|
||||
input.classList.add("dribbblish-config-input");
|
||||
input.type = "checkbox";
|
||||
input.classList.add("x-toggle-input");
|
||||
input.checked = this.get(options.key);
|
||||
input.addEventListener("change", (e) => {
|
||||
options.onChange(e.target.checked);
|
||||
});
|
||||
elem.appendChild(input);
|
||||
|
||||
const indicator = document.createElement("span");
|
||||
indicator.classList.add("x-toggle-indicatorWrapper");
|
||||
indicator.innerHTML = /* html */ `<span class="x-toggle-indicator"></span>`;
|
||||
elem.appendChild(indicator);
|
||||
} else if (options.type == "select") {
|
||||
// Validate
|
||||
const val = this.get(options.key);
|
||||
if (!Object.keys(options.data).includes(val)) this.reset(options.key);
|
||||
|
||||
const input = /* html */ `
|
||||
<select class="main-dropDown-dropDown" id="dribbblish-config-input-${options.key}">
|
||||
${Object.entries(options.data)
|
||||
.map(([key, name]) => `<option value="${key}"${this.get(options.key) == key ? " selected" : ""}>${name}</option>`)
|
||||
.join("")}
|
||||
</select>
|
||||
`;
|
||||
this.#addInputHTML({ ...options, input });
|
||||
|
||||
$(`#dribbblish-config-input-${options.key}`).on("change", (e) => {
|
||||
input = document.createElement("select");
|
||||
input.classList.add("dribbblish-config-input");
|
||||
input.classList.add("main-dropDown-dropDown");
|
||||
input.innerHTML = Object.entries(options.data)
|
||||
.map(([key, name]) => `<option value="${key}"${this.get(options.key) == key ? " selected" : ""}>${name}</option>`)
|
||||
.join("");
|
||||
input.addEventListener("change", (e) => {
|
||||
options.onChange(e.target.value);
|
||||
});
|
||||
} else if (options.type == "button") {
|
||||
|
|
@ -268,16 +196,12 @@ export default class ConfigMenu {
|
|||
options.resetButton = false;
|
||||
options.save = false;
|
||||
|
||||
const input = /* html */ `
|
||||
<button class="main-buttons-button main-button-primary" type="button" id="dribbblish-config-input-${options.key}">
|
||||
<div class="x-settings-buttonContainer">
|
||||
<span>${options.data}</span>
|
||||
</div>
|
||||
</button>
|
||||
`;
|
||||
this.#addInputHTML({ ...options, input });
|
||||
|
||||
$(`#dribbblish-config-input-${options.key}`).on("click", (e) => {
|
||||
input = document.createElement("button");
|
||||
input.classList.add("dribbblish-config-input");
|
||||
input.type = "button";
|
||||
input.classList.add("main-buttons-button", "main-button-primary");
|
||||
input.innerHTML = /* html */ `<div class="x-settings-buttonContainer"><span>${options.data}</span></div>`;
|
||||
input.addEventListener("click", (e) => {
|
||||
options.onChange(true);
|
||||
});
|
||||
} else if (options.type == "number") {
|
||||
|
|
@ -287,24 +211,18 @@ export default class ConfigMenu {
|
|||
if (options.data.min != null && _val < options.data.min) this.set(options.key, options.data.min, options.save);
|
||||
if (options.data.max != null && _val > options.data.max) this.set(options.key, options.data.max, options.save);
|
||||
|
||||
const input = /* html */ `
|
||||
<input
|
||||
type="number"
|
||||
id="dribbblish-config-input-${options.key}"
|
||||
${options.data.min != null ? `min="${options.data.min}"` : ""}
|
||||
${options.data.max != null ? `max="${options.data.max}"` : ""}
|
||||
step="${options.data.step ?? 1}"
|
||||
value="${this.get(options.key)}"
|
||||
>
|
||||
`;
|
||||
this.#addInputHTML({ ...options, input });
|
||||
|
||||
// Prevent inputting +, - and e. Why is it even possible in the first place?
|
||||
$(`#dribbblish-config-input-${options.key}`).on("keypress", (e) => {
|
||||
if (["+", "-", "e"].includes(e.key)) e.preventDefault();
|
||||
input = document.createElement("input");
|
||||
input.classList.add("dribbblish-config-input");
|
||||
input.type = "number";
|
||||
input.value = this.get(options.key);
|
||||
input.step = options.data.step ?? 1;
|
||||
if (options.data.min != null) input.min = options.data.min;
|
||||
if (options.data.max != null) input.max = options.data.max;
|
||||
input.addEventListener("keypress", (e) => {
|
||||
// Prevent inputting + and e.
|
||||
if (["+", "e"].includes(e.key)) e.preventDefault();
|
||||
});
|
||||
|
||||
$(`#dribbblish-config-input-${options.key}`).on("input", (e) => {
|
||||
input.addEventListener("input", (e) => {
|
||||
if (options.data.min != null && e.target.value < options.data.min) e.target.value = options.data.min;
|
||||
if (options.data.max != null && e.target.value > options.data.max) e.target.value = options.data.max;
|
||||
|
||||
|
|
@ -313,26 +231,20 @@ export default class ConfigMenu {
|
|||
} else if (options.type == "text") {
|
||||
if (options.defaultValue == null) options.defaultValue = "";
|
||||
|
||||
const input = /* html */ `
|
||||
<input type="text" id="dribbblish-config-input-${options.key}" value="${this.get(options.key)}">
|
||||
`;
|
||||
this.#addInputHTML({ ...options, input });
|
||||
|
||||
$(`#dribbblish-config-input-${options.key}`).on("input", (e) => {
|
||||
const val = e.target.value;
|
||||
if (!validate(val)) return;
|
||||
this.set(options.key, val, options.save);
|
||||
options.onChange(val);
|
||||
input = document.createElement("input");
|
||||
input.classList.add("dribbblish-config-input");
|
||||
input.type = "text";
|
||||
input.value = this.get(options.key);
|
||||
input.addEventListener("input", (e) => {
|
||||
options.onChange(e.target.value);
|
||||
});
|
||||
} else if (options.type == "textarea") {
|
||||
if (options.defaultValue == null) options.defaultValue = "";
|
||||
|
||||
const input = /* html */ `
|
||||
<textarea id="dribbblish-config-input-${options.key}">${this.get(options.key)}</textarea>
|
||||
`;
|
||||
this.#addInputHTML({ ...options, input });
|
||||
|
||||
$(`#dribbblish-config-input-${options.key}`).on("input", (e) => {
|
||||
input = document.createElement("textarea");
|
||||
input.classList.add("dribbblish-config-input");
|
||||
input.value = this.get(options.key);
|
||||
input.addEventListener("input", (e) => {
|
||||
options.onChange(e.target.value);
|
||||
});
|
||||
} else if (options.type == "slider") {
|
||||
|
|
@ -342,54 +254,47 @@ export default class ConfigMenu {
|
|||
if (options.data.min != null && val < options.data.min) this.set(options.key, options.data.min, options.save);
|
||||
if (options.data.max != null && val > options.data.max) this.set(options.key, options.data.max, options.save);
|
||||
|
||||
const input = /* html */ `
|
||||
<input
|
||||
type="range"
|
||||
id="dribbblish-config-input-${options.key}"
|
||||
name="${options.name}"
|
||||
min="${options.data?.min ?? "0"}"
|
||||
max="${options.data?.max ?? "100"}"
|
||||
step="${options.data?.step ?? "1"}"
|
||||
value="${this.get(options.key)}"
|
||||
tooltip="${this.get(options.key)}${options.data?.suffix ?? ""}"
|
||||
>
|
||||
`;
|
||||
this.#addInputHTML({ ...options, input });
|
||||
|
||||
$(`#dribbblish-config-input-${options.key}`).on("input", (e) => {
|
||||
$(`#dribbblish-config-input-${options.key}`).attr("tooltip", `${e.target.value}${options.data?.suffix ?? ""}`);
|
||||
$(`#dribbblish-config-input-${options.key}`).attr("value", e.target.value);
|
||||
|
||||
input = document.createElement("input");
|
||||
input.classList.add("dribbblish-config-input");
|
||||
input.type = "range";
|
||||
input.step = options.data.step ?? 1;
|
||||
input.min = options.data.min ?? 0;
|
||||
input.max = options.data.max ?? 100;
|
||||
input.value = this.get(options.key);
|
||||
input.setAttribute("tooltip", `${this.get(options.key)}${options.data.suffix ?? ""}`);
|
||||
input.addEventListener("input", (e) => {
|
||||
options.onChange(Number(e.target.value));
|
||||
$(`#dribbblish-config-input-${options.key}`).attr("tooltip", `${e.target.value}${options.data?.suffix ?? ""}`);
|
||||
});
|
||||
} else if (options.type == "time") {
|
||||
// Validate
|
||||
if (options.defaultValue == null) options.defaultValue = "00:00";
|
||||
const input = /* html */ `
|
||||
<input type="time" id="dribbblish-config-input-${options.key}" name="${options.name}" value="${this.get(options.key)}">
|
||||
`;
|
||||
this.#addInputHTML({ ...options, input });
|
||||
|
||||
$(`#dribbblish-config-input-${options.key}`).on("input", (e) => {
|
||||
$(`#dribbblish-config-input-${options.key}`).attr("value", e.target.value);
|
||||
|
||||
input = document.createElement("input");
|
||||
input.classList.add("dribbblish-config-input");
|
||||
input.type = "time";
|
||||
input.value = this.get(options.key);
|
||||
input.addEventListener("input", (e) => {
|
||||
options.onChange(e.target.value);
|
||||
});
|
||||
} else if (options.type == "color") {
|
||||
// Validate
|
||||
if (options.defaultValue == null) options.defaultValue = "#000000";
|
||||
const input = /* html */ `
|
||||
<input type="color" id="dribbblish-config-input-${options.key}" name="${options.name}" value="${this.get(options.key)}">
|
||||
`;
|
||||
this.#addInputHTML({ ...options, input });
|
||||
|
||||
$(`#dribbblish-config-input-${options.key}`).on("input", (e) => {
|
||||
input = document.createElement("input");
|
||||
input.classList.add("dribbblish-config-input");
|
||||
input.type = "color";
|
||||
input.value = this.get(options.key);
|
||||
input.addEventListener("input", (e) => {
|
||||
options.onChange(e.target.value);
|
||||
});
|
||||
} else {
|
||||
throw new Error(`Config Type "${options.type}" invalid`);
|
||||
}
|
||||
|
||||
this.#addInputHTML({ ...options, input: elem ?? input });
|
||||
options.input = input;
|
||||
|
||||
// Re-write internal config since some values may have changed
|
||||
this.#config[options.key] = options;
|
||||
|
||||
|
|
@ -414,33 +319,9 @@ export default class ConfigMenu {
|
|||
area = { ...defaultOptions, ...area };
|
||||
|
||||
if (!document.querySelector(`.dribbblish-config-area[name="${area.name}"]`)) {
|
||||
const areaElem = document.createElement("div");
|
||||
areaElem.classList.add("dribbblish-config-area");
|
||||
if (area.order != 0) areaElem.style.order = area.order;
|
||||
const uncollapsedAreas = JSON.parse(localStorage.getItem("dribbblish:config-areas:uncollapsed") ?? "[]");
|
||||
if (area.toggleable && !uncollapsedAreas.includes(area.name)) areaElem.toggleAttribute("collapsed");
|
||||
areaElem.setAttribute("name", area.name);
|
||||
areaElem.innerHTML = /* html */ `
|
||||
<h2 class="dribbblish-config-area-header">
|
||||
${area.name}
|
||||
${!area.toggleable ? "" : /* html */ `<svg height="24" width="24" viewBox="0 0 24 24" class="main-topBar-icon"><polyline points="16 4 7 12 16 20" fill="none" stroke="currentColor"></polyline></svg>`}
|
||||
</h2>
|
||||
<div class="dribbblish-config-area-items"></div>
|
||||
`;
|
||||
document.querySelector(".dribbblish-config-areas").appendChild(areaElem);
|
||||
|
||||
if (area.toggleable) {
|
||||
areaElem.querySelector("h2").addEventListener("click", () => {
|
||||
areaElem.toggleAttribute("collapsed");
|
||||
let uncollapsedAreas = JSON.parse(localStorage.getItem("dribbblish:config-areas:uncollapsed") ?? "[]");
|
||||
if (areaElem.hasAttribute("collapsed")) {
|
||||
uncollapsedAreas = uncollapsedAreas.filter((areaName) => areaName != area.name);
|
||||
} else {
|
||||
uncollapsedAreas.push(area.name);
|
||||
}
|
||||
localStorage.setItem("dribbblish:config-areas:uncollapsed", JSON.stringify(uncollapsedAreas));
|
||||
});
|
||||
}
|
||||
this.#addAreaHTML(area);
|
||||
} else {
|
||||
throw new Error(`Area "${area.name}" already exists`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -451,10 +332,10 @@ export default class ConfigMenu {
|
|||
* @returns {any}
|
||||
*/
|
||||
get(key, defaultValueOverride) {
|
||||
const val = JSON.parse(this.#config[key]?.storageCache ?? localStorage.getItem(`dribbblish:config:${key}`) ?? null); // Turn undefined into null because `JSON.parse()` dosen't like undefined
|
||||
const val = JSON.parse(this.#config[key]?.storageCache ?? localStorage.getItem(`dribbblish:config:${key}`) ?? null); // Turn undefined into null because `JSON.parse()` doesn't like undefined
|
||||
if (val == null || val?.type != this.#config[key]?.type) {
|
||||
localStorage.removeItem(`dribbblish:config:${key}`);
|
||||
return defaultValueOverride ?? this.#config[key].defaultValue;
|
||||
return defaultValueOverride ?? this.#config[key]?.defaultValue;
|
||||
}
|
||||
return val.value;
|
||||
}
|
||||
|
|
@ -478,6 +359,17 @@ export default class ConfigMenu {
|
|||
reset(key) {
|
||||
delete this.#config[key].storageCache;
|
||||
localStorage.removeItem(`dribbblish:config:${key}`);
|
||||
|
||||
const options = this.#config[key];
|
||||
const defaultVal = this.get(options.key);
|
||||
if (options.type == "checkbox") {
|
||||
options.input.checked = defaultVal;
|
||||
} else {
|
||||
options.input.value = defaultVal;
|
||||
if (options.type == "slider") options.input.setAttribute("tooltip", `${defaultVal}${options.data.suffix ?? ""}`);
|
||||
}
|
||||
|
||||
options.onChange(defaultVal);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -486,10 +378,10 @@ export default class ConfigMenu {
|
|||
* @param {Boolean} hidden
|
||||
* @private
|
||||
*/
|
||||
#setHidden(key, hidden) {
|
||||
setHidden(key, hidden, search = false) {
|
||||
this.#config[key].hidden = hidden;
|
||||
const $elem = $(`.dribbblish-config-item[key="${key}"]`);
|
||||
$elem.attr("hidden", hidden ? "" : null);
|
||||
$elem.attr(search ? "hidden-override" : "hidden", hidden ? "" : null);
|
||||
|
||||
// If element has children or a parent
|
||||
if ($elem.attr("parent") != null || $elem.attr("children") != null) {
|
||||
|
|
@ -498,7 +390,7 @@ export default class ConfigMenu {
|
|||
const $nextChildren = $parent.nextAll(`[parent="${$parent.attr("key")}"]`);
|
||||
|
||||
// Make parent connect on bottom when children are visible
|
||||
$parent.attr("connect-bottom", $nextChildren.filter(":not([hidden])").length > 0 ? "" : null);
|
||||
$parent.attr("connect-bottom", $nextChildren.filter(":not([hidden]):not([hidden-override])").length > 0 ? "" : null);
|
||||
|
||||
// Reset all children's bottom connection
|
||||
$nextChildren.each(function () {
|
||||
|
|
@ -506,7 +398,7 @@ export default class ConfigMenu {
|
|||
});
|
||||
// Add bottom connection to all but the last visible child
|
||||
$nextChildren
|
||||
.filter(":not([hidden])")
|
||||
.filter(":not([hidden]):not([hidden-override])")
|
||||
.slice(0, -1)
|
||||
.each(function () {
|
||||
$(this).attr("connect-bottom", "");
|
||||
|
|
@ -516,6 +408,10 @@ export default class ConfigMenu {
|
|||
}
|
||||
}
|
||||
|
||||
setDisabled(key, disabled) {
|
||||
this.#config[key].input.disabled = disabled;
|
||||
}
|
||||
|
||||
getOptions(key) {
|
||||
return this.#config[key];
|
||||
}
|
||||
|
|
@ -530,4 +426,136 @@ export default class ConfigMenu {
|
|||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String} text
|
||||
*/
|
||||
#search(text) {
|
||||
text = text.trim().toLowerCase();
|
||||
$(".dribbblish-config-area-header svg").css("display", text != "" ? "none" : "");
|
||||
$(".dribbblish-config-area").attr("search", text != "" ? "" : null);
|
||||
|
||||
const cmp = (s) => s.trim().toLowerCase().includes(text.trim().toLowerCase());
|
||||
/**
|
||||
* @param {DribbblishConfigItem} options
|
||||
*/
|
||||
function cmpOpts(options) {
|
||||
let matches = cmp(options.name) || cmp($(`.dribbblish-config-item[key="${options.key}"] .dribbblish-config-item-header label`).text());
|
||||
if (!matches && options.type == "select") matches = Object.values(options.data).some(cmp);
|
||||
return matches;
|
||||
}
|
||||
|
||||
// Check every item
|
||||
for (const [key, options] of Object.entries(this.#config)) {
|
||||
this.setHidden(key, false, true);
|
||||
if (text == "") continue;
|
||||
|
||||
const show = cmpOpts(options);
|
||||
this.setHidden(key, !show, true);
|
||||
}
|
||||
|
||||
// Check every item's children and show it when any child is visible
|
||||
for (const [key, options] of Object.entries(this.#config)) {
|
||||
if (options.children.length != 0 && $(`.dribbblish-config-item[parent="${options.key}"]`).filter(":not([hidden]):not([hidden-override])").length != 0) this.setHidden(key, false, true);
|
||||
}
|
||||
|
||||
// Hide areas without visible children
|
||||
for (const area of $(".dribbblish-config-area").toArray()) {
|
||||
const $area = $(area);
|
||||
const itemsVisible = $area.children(".dribbblish-config-area-items").children(":not([hidden]):not([hidden-override])").length;
|
||||
$area.css("display", text != "" && itemsVisible == 0 ? "none" : "");
|
||||
}
|
||||
|
||||
$(`.dribbblish-config-item`).filter(":not([hidden]):not([hidden-override])").length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {DribbblishConfigItem} options
|
||||
*/
|
||||
#addInputHTML(options) {
|
||||
if (!document.querySelector(`.dribbblish-config-area[name="${options.area.name}"]`)) this.registerArea(options.area);
|
||||
|
||||
const parent = document.querySelector(`.dribbblish-config-area[name="${options.area.name}"] .dribbblish-config-area-items`);
|
||||
|
||||
const elem = document.createElement("div");
|
||||
if (options.order != 0) elem.style.order = options.order;
|
||||
elem.classList.add("dribbblish-config-item");
|
||||
elem.setAttribute("key", options.key);
|
||||
elem.setAttribute("type", options.type);
|
||||
if (options.hidden) elem.setAttribute("hidden", true);
|
||||
if (options.parent) elem.setAttribute("parent", options.parent);
|
||||
if (options.children.length > 0) elem.setAttribute("children", options.children.map((c) => c.key).join(" "));
|
||||
elem.innerHTML = /* html */ `
|
||||
${
|
||||
options.name != null && options.description != null
|
||||
? /* html */ `
|
||||
<div class="dribbblish-config-item-header">
|
||||
<h2 class="x-settings-title main-type-cello" as="h2" empty="${options.name == null}">${options.name}</h2>
|
||||
<label class="main-type-mesto" empty="${options.description == null}" markdown>${renderMD(options.description)}</label>
|
||||
</div>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
<div class="dribbblish-config-item-input">
|
||||
<label class="x-toggle-wrapper x-settings-secondColumn"></label>
|
||||
</div>
|
||||
`;
|
||||
|
||||
elem.querySelector(".dribbblish-config-item-input > label").appendChild(options.input);
|
||||
|
||||
if (options.resetButton && options.name != null && options.description != null) {
|
||||
const resetBtn = document.createElement("button");
|
||||
resetBtn.ariaLabel = "Reset";
|
||||
resetBtn.className = "dribbblish-config-item-reset main-trackCreditsModal-closeBtn";
|
||||
resetBtn.innerHTML = icons.get("delete-outline", { size: 20, title: "Reset Setting" });
|
||||
resetBtn.addEventListener("click", () => {
|
||||
this.reset(options.key);
|
||||
});
|
||||
|
||||
elem.querySelector(".dribbblish-config-item-header > h2").appendChild(resetBtn);
|
||||
}
|
||||
|
||||
if (options.insertOnTop && parent.children.length > 0) {
|
||||
parent.insertBefore(elem, parent.children[0]);
|
||||
} else {
|
||||
parent.appendChild(elem);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {DribbblishConfigArea} area
|
||||
*/
|
||||
#addAreaHTML(area) {
|
||||
const areaElem = document.createElement("div");
|
||||
areaElem.classList.add("dribbblish-config-area");
|
||||
if (area.order != 0) areaElem.style.order = area.order;
|
||||
const uncollapsedAreas = JSON.parse(localStorage.getItem("dribbblish:config-areas:uncollapsed") ?? "[]");
|
||||
if (area.toggleable && !uncollapsedAreas.includes(area.name)) areaElem.toggleAttribute("collapsed");
|
||||
areaElem.setAttribute("name", area.name);
|
||||
areaElem.innerHTML = /* html */ `
|
||||
<h2 class="dribbblish-config-area-header">
|
||||
${area.name}
|
||||
${!area.toggleable ? "" : icons.get("expand-more", { size: 24, scale: 1.2 })}
|
||||
</h2>
|
||||
<div class="dribbblish-config-area-items"></div>
|
||||
`;
|
||||
document.querySelector(".dribbblish-config-areas").appendChild(areaElem);
|
||||
|
||||
if (area.toggleable) {
|
||||
areaElem.querySelector("h2").addEventListener("click", () => {
|
||||
if (document.querySelector(".dribbblish-config-search").value.trim() != "") return;
|
||||
|
||||
areaElem.toggleAttribute("collapsed");
|
||||
let uncollapsedAreas = JSON.parse(localStorage.getItem("dribbblish:config-areas:uncollapsed") ?? "[]");
|
||||
if (areaElem.hasAttribute("collapsed")) {
|
||||
uncollapsedAreas = uncollapsedAreas.filter((areaName) => areaName != area.name);
|
||||
} else {
|
||||
uncollapsedAreas.push(area.name);
|
||||
}
|
||||
localStorage.setItem("dribbblish:config-areas:uncollapsed", JSON.stringify(uncollapsedAreas));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
80
src/js/Dribbblish.js
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import ConfigMenu from "./ConfigMenu";
|
||||
import Info from "./Info";
|
||||
import Loader from "./Loader";
|
||||
import Overlay from "./Overlay";
|
||||
import Icon, { icons } from "./Icons";
|
||||
|
||||
export default class Dribbblish {
|
||||
/**
|
||||
* @typedef {"ready"} Event
|
||||
*/
|
||||
|
||||
/**
|
||||
* @callback listener
|
||||
* @param {any} [data]
|
||||
* @returns {void}
|
||||
*/
|
||||
|
||||
/** @type {ConfigMenu} */
|
||||
config;
|
||||
|
||||
/** @type {Info} */
|
||||
info;
|
||||
|
||||
/** @type {Loader} */
|
||||
loader;
|
||||
|
||||
/** @type {Overlay} */
|
||||
overlay;
|
||||
|
||||
/** @type {Icon} */
|
||||
icons;
|
||||
|
||||
/** @type {Object.<string, listener[]>} */
|
||||
#listeners = {};
|
||||
|
||||
/** @type {Boolean} */
|
||||
#ready = false;
|
||||
|
||||
constructor() {
|
||||
this.config = new ConfigMenu();
|
||||
this.info = new Info();
|
||||
this.loader = new Loader();
|
||||
this.overlay = new Overlay();
|
||||
this.icons = icons;
|
||||
|
||||
let tries = 0;
|
||||
const interval = setInterval(() => {
|
||||
if (++tries > 50) throw new Error("ready timeout");
|
||||
if (document.querySelector("#main") == null || Spicetify?.showNotification == undefined || !this.info.isReady()) return;
|
||||
this.#ready = true;
|
||||
this.emit("ready");
|
||||
clearInterval(interval);
|
||||
}, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Event} event
|
||||
* @param {any} data
|
||||
*/
|
||||
emit(event, data) {
|
||||
this.#listeners[event]?.forEach((listener) => listener(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Event} event
|
||||
* @param {listener} listener
|
||||
*/
|
||||
on(event, listener) {
|
||||
this.#listeners[event] = [...(this.#listeners[event] ?? []), listener];
|
||||
if (event == "ready" && this.#ready) listener();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Event} event
|
||||
* @param {listener} listener
|
||||
*/
|
||||
off(event, listener) {
|
||||
this.#listeners = this.#listeners[event].filter((f) => f != listener);
|
||||
}
|
||||
}
|
||||
98
src/js/Folders.js
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
import { waitForElement, htmlToNode } from "./Util";
|
||||
import { icons } from "./Icons";
|
||||
|
||||
waitForElement([`.main-rootlist-rootlistPlaylistsScrollNode ul[tabindex="0"]`, `.main-rootlist-rootlistPlaylistsScrollNode ul[tabindex="0"] li`], ([root, firstItem]) => {
|
||||
const listElem = firstItem.parentElement;
|
||||
root.classList.add("dribs-playlist-list");
|
||||
|
||||
/** Replace Playlist name with their pictures */
|
||||
async function loadPlaylistImage() {
|
||||
for (const item of listElem.children) {
|
||||
let link = item.querySelector("a");
|
||||
if (!link) continue;
|
||||
|
||||
let [_, app, uid] = link.pathname.split("/");
|
||||
|
||||
if (link.querySelector("svg")) link.querySelector("svg").remove();
|
||||
if (link.querySelector("img")) link.querySelector("img").remove();
|
||||
const title = link.querySelector("span").innerText;
|
||||
let elem;
|
||||
|
||||
if (app === "playlist") {
|
||||
const {
|
||||
metadata: { picture }
|
||||
} = await Spicetify.CosmosAsync.get(`sp://core-playlist/v1/playlist/${Spicetify.URI.playlistV2URI(uid).toURI()}/metadata`, { policy: { picture: true } });
|
||||
|
||||
if (picture != null && picture.trim() != "") {
|
||||
if (!link.querySelector("img")) elem = document.createElement("img");
|
||||
elem.src = picture;
|
||||
} else {
|
||||
if (!link.querySelector("svg")) elem = htmlToNode(icons.get("note", { title }));
|
||||
}
|
||||
} else if (app === "folder") {
|
||||
const base64 = localStorage.getItem("dribbblish:folder-image:" + uid);
|
||||
if (base64 != null) {
|
||||
if (!link.querySelector("img")) elem = document.createElement("img");
|
||||
elem.src = base64;
|
||||
} else {
|
||||
if (!link.querySelector("svg")) elem = htmlToNode(icons.get("folder", { title }));
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
elem.title = title;
|
||||
elem.classList.add("playlist-picture");
|
||||
link.prepend(elem);
|
||||
}
|
||||
}
|
||||
loadPlaylistImage();
|
||||
new MutationObserver(loadPlaylistImage).observe(listElem, { childList: true });
|
||||
|
||||
const filePickerForm = document.createElement("form");
|
||||
filePickerForm.setAttribute("aria-hidden", true);
|
||||
filePickerForm.innerHTML = '<input type="file" class="hidden-visually" />';
|
||||
document.body.appendChild(filePickerForm);
|
||||
/** @type {HTMLInputElement} */
|
||||
const filePickerInput = filePickerForm.childNodes[0];
|
||||
filePickerInput.accept = ["image/jpeg", "image/apng", "image/avif", "image/gif", "image/png", "image/svg+xml", "image/webp"].join(",");
|
||||
|
||||
filePickerInput.onchange = () => {
|
||||
if (!filePickerInput.files.length) return;
|
||||
|
||||
const file = filePickerInput.files[0];
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
const result = event.target.result;
|
||||
const id = Spicetify.URI.from(filePickerInput.uri).id;
|
||||
try {
|
||||
localStorage.setItem("dribbblish:folder-image:" + id, result);
|
||||
} catch {
|
||||
Spicetify.showNotification("File too large");
|
||||
}
|
||||
loadPlaylistImage();
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
};
|
||||
|
||||
new Spicetify.ContextMenu.Item(
|
||||
"Remove folder image",
|
||||
([uri]) => {
|
||||
const id = Spicetify.URI.from(uri).id;
|
||||
localStorage.removeItem("dribbblish:folder-image:" + id);
|
||||
loadPlaylistImage();
|
||||
},
|
||||
([uri]) => Spicetify.URI.isFolder(uri),
|
||||
"x"
|
||||
).register();
|
||||
new Spicetify.ContextMenu.Item(
|
||||
"Choose folder image",
|
||||
([uri]) => {
|
||||
filePickerInput.uri = uri;
|
||||
filePickerForm.reset();
|
||||
filePickerInput.click();
|
||||
},
|
||||
([uri]) => Spicetify.URI.isFolder(uri),
|
||||
"edit"
|
||||
).register();
|
||||
});
|
||||
124
src/js/Icons.js
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
import { defaults } from "./Util";
|
||||
import { parseSync as parseSVG, stringify as stringifySVG } from "svgson";
|
||||
|
||||
export default class Icons {
|
||||
/** @typedef {"custom" | "material:baseline" | "material:outline" | "material:round" | "material:sharp" | "material:twotone"} IconStyle */
|
||||
|
||||
/**
|
||||
* @typedef {Object} IconOptions
|
||||
* @property {IconStyle} [style="round"]
|
||||
* @property {String} [className=""]
|
||||
* @property {Number} [size=16]
|
||||
* @property {Number} [scale=1]
|
||||
* @property {String} [fill="currentColor"]
|
||||
* @property {Boolean} [base64=false]
|
||||
*/
|
||||
|
||||
/** @type {Object.<String, Object.<IconStyle, String>>} */
|
||||
#icons;
|
||||
|
||||
constructor(icons) {
|
||||
this.#icons = icons ?? process.env.DRIBBBLISH_ICONS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String} name
|
||||
* @param {IconStyle} style
|
||||
* @returns {String}
|
||||
*/
|
||||
getRawSVG(name, style) {
|
||||
if (style == null) style = this.#getDefaultStyle(name);
|
||||
if (!this.#icons.hasOwnProperty(name)) throw new Error(`Icon "${name}" does not exist`);
|
||||
|
||||
if (!this.#icons[name].hasOwnProperty(style)) {
|
||||
const styles = Object.keys(this.#icons[name])
|
||||
.map((s) => `"${s}"`)
|
||||
.join(", ");
|
||||
throw new Error(`Icon "${name}" does not have style "${style}". It is available in styles [${styles}].`);
|
||||
}
|
||||
|
||||
return this.#icons[name][style];
|
||||
}
|
||||
|
||||
getAvailableStyles(name) {
|
||||
if (!this.#icons.hasOwnProperty(name)) throw new Error(`Icon "${name}" does not exist`);
|
||||
return Object.keys(this.#icons[name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String} name
|
||||
* @returns
|
||||
*/
|
||||
#getDefaultStyle(name) {
|
||||
const styles = this.getAvailableStyles(name);
|
||||
for (const s of ["custom", "material:round"]) {
|
||||
if (styles.includes(s)) return s;
|
||||
}
|
||||
|
||||
return styles[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String} name icon name lowercase with dashes like `ac-unit`
|
||||
* @param {IconOptions} options
|
||||
* @see https://fonts.google.com/icons?selected=Material+Icons
|
||||
* @returns
|
||||
*/
|
||||
get(name, options) {
|
||||
/** @type {IconOptions} */
|
||||
const defaultOptions = {
|
||||
style: this.#getDefaultStyle(name),
|
||||
className: "",
|
||||
size: 16,
|
||||
scale: 1,
|
||||
fill: "currentColor",
|
||||
base64: false
|
||||
};
|
||||
options = defaults(options, defaultOptions);
|
||||
|
||||
const svg = parseSVG(this.getRawSVG(name, options.style));
|
||||
|
||||
// Add general / required attributes
|
||||
svg.attributes["icon-type"] = "dribbblish";
|
||||
svg.attributes["icon-name"] = name;
|
||||
svg.attributes["icon-style"] = options.style;
|
||||
svg.attributes.fill = options.fill;
|
||||
svg.attributes.width = options.size;
|
||||
svg.attributes.height = options.size;
|
||||
|
||||
// Add className
|
||||
if (options.className != "") svg.attributes.class = options.className;
|
||||
|
||||
// Add Styles
|
||||
const styles = {};
|
||||
if (options.scale != 1) {
|
||||
styles["transform"] = `scale(${options.scale})`;
|
||||
styles["transform-origin"] = "center";
|
||||
}
|
||||
const styleStr = Object.entries(styles)
|
||||
.map(([prop, val]) => `${prop}: ${val};`)
|
||||
.join(" ");
|
||||
svg.children = svg.children.map((child) => {
|
||||
if (styleStr != "") child.attributes.style = styleStr;
|
||||
return child;
|
||||
});
|
||||
|
||||
// Add title
|
||||
if (options.title != null) {
|
||||
svg.children.push({
|
||||
name: "title",
|
||||
type: "element",
|
||||
value: "",
|
||||
children: [{ name: "", type: "text", value: options.title, attributes: {}, children: [] }]
|
||||
});
|
||||
}
|
||||
|
||||
if (options.base64) {
|
||||
return `data:image/svg+xml;base64,${Buffer.from(stringifySVG(svg)).toString("base64")}`;
|
||||
} else {
|
||||
return stringifySVG(svg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const icons = new Icons();
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import $ from "jquery";
|
||||
import { icons } from "./Icons";
|
||||
|
||||
import { waitForElement } from "./Util";
|
||||
|
||||
|
|
@ -7,7 +8,7 @@ export default class Info {
|
|||
* @typedef {Object} DribbblishInfo
|
||||
* @property {String} [text]
|
||||
* @property {String} [tooltip]
|
||||
* @property {String} [icon]
|
||||
* @property {String} [icon] svg string or icon name
|
||||
* @property {DribbblishInfoColor} [color]
|
||||
* @property {Number} [order=0] order < 0 = More to the Left | order > 0 = More to the Right
|
||||
* @property {onClick} [onClick]
|
||||
|
|
@ -43,6 +44,10 @@ export default class Info {
|
|||
});
|
||||
}
|
||||
|
||||
isReady() {
|
||||
return this.#ready;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String} key
|
||||
* @param {DribbblishInfo} info
|
||||
|
|
@ -54,6 +59,7 @@ export default class Info {
|
|||
}
|
||||
|
||||
this.remove(key);
|
||||
if (info == null) return;
|
||||
if (info.text == null && info.icon == null) throw new Error("invalid info");
|
||||
|
||||
const elem = document.createElement("div");
|
||||
|
|
@ -69,6 +75,7 @@ export default class Info {
|
|||
if (bg != null) elem.style.backgroundColor = bg;
|
||||
}
|
||||
if (info.order != 0) elem.style.order = info.order;
|
||||
if (!info.icon.startsWith("<svg")) info.icon = icons.get(info.icon, { size: 18 });
|
||||
elem.innerHTML = `${info.text ?? ""}${info.icon ?? ""}`;
|
||||
|
||||
this.#container.appendChild(elem);
|
||||
|
|
|
|||
18
src/js/Loader.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import Overlay from "./Overlay";
|
||||
|
||||
export default class Loader {
|
||||
/** @type {Overlay} */
|
||||
#overlay;
|
||||
|
||||
constructor() {
|
||||
this.#overlay = new Overlay();
|
||||
}
|
||||
|
||||
show(text) {
|
||||
this.#overlay.show({ icon: "loading", text });
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.#overlay.hide();
|
||||
}
|
||||
}
|
||||
68
src/js/Overlay.js
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import { defaults } from "./Util";
|
||||
import { icons } from "./Icons";
|
||||
|
||||
export default class Overlay {
|
||||
/**
|
||||
* @typedef {Object} DribbblishOverlayOptions
|
||||
* @property {String} icon
|
||||
* @property {String} text
|
||||
* @property {DribbblishOverlayColor} color
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} DribbblishOverlayColor
|
||||
* @property {String} [icon]
|
||||
* @property {String} [text]
|
||||
*/
|
||||
|
||||
/** @type {HTMLDivElement} */
|
||||
#container;
|
||||
|
||||
/** @type {HTMLElement} */
|
||||
#iconElem;
|
||||
|
||||
/** @type {HTMLSpanElement} */
|
||||
#textElem;
|
||||
|
||||
constructor() {
|
||||
this.#container = document.createElement("div");
|
||||
this.#container.id = "dribbblish-overlay";
|
||||
this.#container.innerHTML = /* html */ `
|
||||
<div>
|
||||
<i></i>
|
||||
<span>Loading...</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.#iconElem = this.#container.querySelector("i");
|
||||
this.#textElem = this.#container.querySelector("span");
|
||||
|
||||
document.body.appendChild(this.#container);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {DribbblishOverlayOptions} options
|
||||
*/
|
||||
show(options) {
|
||||
const defaultOptions = {
|
||||
icon: "",
|
||||
text: "",
|
||||
color: {
|
||||
icon: "var(--spice-text)",
|
||||
text: "var(--spice-subtext)"
|
||||
}
|
||||
};
|
||||
options = defaults(options, defaultOptions);
|
||||
if (options.icon != "" && !options.icon.startsWith("<svg")) options.icon = icons.get(options.icon, { size: 64 });
|
||||
this.#iconElem.innerHTML = options.icon;
|
||||
this.#iconElem.style.setProperty("--color", options.color.icon);
|
||||
this.#textElem.innerHTML = options.text;
|
||||
this.#textElem.style.setProperty("--color", options.color.text);
|
||||
this.#container.setAttribute("active", "");
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.#container.removeAttribute("active");
|
||||
}
|
||||
}
|
||||
13
src/js/SassUtil.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
export function lightOffset(n, offset) {
|
||||
return `calc(${n} + ${offset} * var(--is_light))`;
|
||||
}
|
||||
|
||||
export function spiceColor(key, alpha = 1, _lightOffset = 0) {
|
||||
if (alpha == 1) {
|
||||
return `var(--spice-${key})`;
|
||||
} else if (_lightOffset == 0) {
|
||||
return `rgba(var(--spice-rgb-${key}), ${alpha})`;
|
||||
} else {
|
||||
return `rgba(var(--spice-rgb-${key}), ${lightOffset(alpha, _lightOffset)})`;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,8 @@
|
|||
import MarkdownIt from "markdown-it";
|
||||
import MarkdownItAttrs from "markdown-it-attrs";
|
||||
import defaultsDeep from "lodash.defaultsdeep";
|
||||
import { default as _debounce } from "lodash.debounce";
|
||||
|
||||
/**
|
||||
* @callback waitForElCb
|
||||
* @param {HTMLElement[]} queries
|
||||
|
|
@ -37,3 +42,54 @@ export function capitalizeFirstLetter(string) {
|
|||
export function getClosestToNum(arr, num) {
|
||||
return arr.reduce((prev, curr) => (Math.abs(curr - num) < Math.abs(prev - num) ? curr : prev));
|
||||
}
|
||||
|
||||
export function renderMD(src, env) {
|
||||
const md = MarkdownIt("commonmark", {
|
||||
html: true,
|
||||
breaks: true,
|
||||
linkify: true,
|
||||
typographer: true
|
||||
});
|
||||
md.use(MarkdownItAttrs);
|
||||
|
||||
return md.render(src, env);
|
||||
}
|
||||
|
||||
export function htmlToNode(htmlStr) {
|
||||
var div = document.createElement("div");
|
||||
div.innerHTML = htmlStr.trim();
|
||||
return div.firstChild;
|
||||
}
|
||||
|
||||
export function getRandomArbitrary(min, max) {
|
||||
return Math.random() * (max - min) + min;
|
||||
}
|
||||
|
||||
export function getRandomInt(min, max) {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {T[]} arr
|
||||
* @returns {T}
|
||||
*/
|
||||
export function randomFromArray(arr) {
|
||||
return arr[Math.floor(Math.random() * arr.length)];
|
||||
}
|
||||
|
||||
/** @type {_debounce} */
|
||||
export function debounce(fn, wait, opts) {
|
||||
return _debounce(fn, wait, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {...Object} defaults
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function defaults(options, ...defaults) {
|
||||
return defaultsDeep(options, ...defaults);
|
||||
}
|
||||
|
|
|
|||
1489
src/js/main.js
|
|
@ -3,7 +3,7 @@ $colors: (
|
|||
subtext: #f0f0f0,
|
||||
sidebar-text: #ffffff,
|
||||
main: #000000,
|
||||
sidebar: #1ed760,
|
||||
sidebar: #121212,
|
||||
player: #000000,
|
||||
card: #000000,
|
||||
shadow: #202020,
|
||||
|
|
@ -44,15 +44,3 @@ $props-to-transition: ("sidebar", "main", "text", "button");
|
|||
transition: all var(--song-transition-speed) linear;
|
||||
transition-property: $props;
|
||||
}
|
||||
|
||||
// Color Function
|
||||
// $light-offset is added when in light mode
|
||||
@function spiceColor($key, $alpha: 1, $light-offset: 0) {
|
||||
@if $alpha == 1 {
|
||||
@return var(--spice-#{$key});
|
||||
} @else if $light-offset == 0 {
|
||||
@return rgba(var(--spice-rgb-#{$key}), $alpha);
|
||||
} @else {
|
||||
@return rgba(var(--spice-rgb-#{$key}), calc($alpha + var(--is_light) * $light-offset));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,21 +23,28 @@
|
|||
z-index: 1;
|
||||
position: relative;
|
||||
width: clamp(500px, 50%, 650px);
|
||||
background-color: spiceColor("main", 0.95);
|
||||
backdrop-filter: blur(3px);
|
||||
padding: 20px 15px;
|
||||
border-radius: var(--main-corner-radius);
|
||||
box-shadow: 0 0 10px 3px #0000003b;
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
gap: 8px;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@include spiceGlass();
|
||||
|
||||
> h2 {
|
||||
font-size: 32px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.dribbblish-config-close {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
padding: 0px;
|
||||
top: 24px;
|
||||
right: 24px;
|
||||
}
|
||||
|
||||
.dribbblish-config-search {
|
||||
// TODO: improve styles
|
||||
}
|
||||
|
||||
.dribbblish-config-areas {
|
||||
|
|
@ -47,7 +54,7 @@
|
|||
gap: 8px;
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
padding: 0px 25px;
|
||||
padding: 0px 26px;
|
||||
|
||||
.dribbblish-config-area {
|
||||
display: flex;
|
||||
|
|
@ -55,13 +62,23 @@
|
|||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
&[collapsed] {
|
||||
&[collapsed]:not([search]) {
|
||||
overflow: hidden;
|
||||
min-height: 38px; //for some reason height alone isn't enough
|
||||
height: 38px;
|
||||
|
||||
.dribbblish-config-area-header svg {
|
||||
transform: rotate(270deg);
|
||||
.dribbblish-config-area-header {
|
||||
svg {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
&:hover svg {
|
||||
transform: rotate(0deg) scale(1.1);
|
||||
}
|
||||
|
||||
&:active svg {
|
||||
transform: rotate(0deg) scale(0.9);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -84,7 +101,15 @@
|
|||
padding: 0px;
|
||||
height: 100%;
|
||||
stroke-width: 2px;
|
||||
transform: rotate(90deg);
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
&:hover svg {
|
||||
transform: rotate(180deg) scale(1.1);
|
||||
}
|
||||
|
||||
&:active svg {
|
||||
transform: rotate(180deg) scale(0.9);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -103,8 +128,10 @@
|
|||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 16px;
|
||||
user-select: text;
|
||||
|
||||
&[hidden] {
|
||||
&[hidden],
|
||||
&[hidden-override] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
|
@ -128,8 +155,16 @@
|
|||
border-bottom-right-radius: 0px;
|
||||
}
|
||||
|
||||
&[invalid]::before {
|
||||
border: 2px solid rgba(red, 0.8);
|
||||
&[invalid] {
|
||||
&::before {
|
||||
border: 2px solid rgba(red, 0.8);
|
||||
}
|
||||
|
||||
.dribbblish-config-item-input::before {
|
||||
content: var(--validation-error);
|
||||
margin-right: 8px;
|
||||
color: rgba(red, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
&[changed] {
|
||||
|
|
@ -154,6 +189,10 @@
|
|||
bottom: -4px;
|
||||
border-bottom-left-radius: 0px;
|
||||
}
|
||||
|
||||
.dribbblish-config-item-reset {
|
||||
display: block !important;
|
||||
}
|
||||
}
|
||||
|
||||
.dribbblish-config-item-header {
|
||||
|
|
@ -169,7 +208,7 @@
|
|||
|
||||
.x-settings-title {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
gap: 5px;
|
||||
align-items: center;
|
||||
grid-area: header;
|
||||
margin: 0px;
|
||||
|
|
@ -178,10 +217,11 @@
|
|||
bottom: 0px;
|
||||
|
||||
.dribbblish-config-item-reset {
|
||||
display: none;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
padding: 0px;
|
||||
color: spiceColor("text");
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -190,7 +230,6 @@
|
|||
height: min-content;
|
||||
color: spiceColor("subtext");
|
||||
line-height: calc(1em + 6px); // To have line gaps
|
||||
line-break: anywhere;
|
||||
}
|
||||
|
||||
.x-settings-secondColumn {
|
||||
|
|
@ -200,13 +239,8 @@
|
|||
}
|
||||
|
||||
.dribbblish-config-item-input {
|
||||
display: flex;
|
||||
min-width: fit-content;
|
||||
|
||||
&::before {
|
||||
content: var(--validation-error);
|
||||
margin-right: 8px;
|
||||
color: rgba(red, 0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
41
src/styles/ConnectDeviceList.scss
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
.connect-device-list-container {
|
||||
padding: 8px;
|
||||
@include spiceGlass(lightOffset(0.8, -0.1));
|
||||
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.connect-device-list-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
|
||||
.connect-header {
|
||||
padding: 0px;
|
||||
filter: invert(var(--is_light));
|
||||
}
|
||||
|
||||
.connect-device-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
overflow-y: auto;
|
||||
|
||||
.connect-device-list-item {
|
||||
background-color: transparent;
|
||||
border-radius: var(--main-corner-radius);
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
background-color: spiceColor("subtext", 0.1, 0.1);
|
||||
}
|
||||
|
||||
&--active {
|
||||
background-color: spiceColor("selected-row", 0.2) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
.main-contextMenu-menu {
|
||||
background-color: transparent !important;
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
background-color: transparent !important;
|
||||
border-radius: var(--main-corner-radius);
|
||||
padding: 8px;
|
||||
|
||||
|
|
@ -10,9 +10,7 @@
|
|||
content: "";
|
||||
position: absolute;
|
||||
inset: 0px;
|
||||
background-color: spiceColor("main", calc(0.75 - 0.1 * var(--is_light))) !important;
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: var(--main-corner-radius);
|
||||
@include spiceGlass(lightOffset(0.8, -0.1));
|
||||
}
|
||||
|
||||
.main-contextMenu-menuItem {
|
||||
|
|
@ -21,16 +19,16 @@
|
|||
color: spiceColor("subtext") !important;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active,
|
||||
&[aria-expanded="true"] {
|
||||
background-color: spiceColor("subtext", calc(0.1 + 0.3 * var(--is_light)));
|
||||
background-color: spiceColor("subtext", 0.1, 0.3);
|
||||
}
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
left: 5px;
|
||||
right: 5px;
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
border-color: spiceColor("subtext", 0.1, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
32
src/styles/CustomAppTabBar.scss
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
.main-topBar-topbarContent {
|
||||
height: 100%;
|
||||
|
||||
& > nav[class*="-tabBar"][class*="-tabBar-nav"] {
|
||||
height: 100%;
|
||||
|
||||
& > ul {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
height: 100%;
|
||||
|
||||
li[class*="-tabBar-headerItem"] {
|
||||
height: 100%;
|
||||
|
||||
& > a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0px;
|
||||
height: 100%;
|
||||
padding: 0px 8px;
|
||||
border-radius: var(--sidebar-icons-border-radius);
|
||||
|
||||
& > span {
|
||||
line-height: 13px;
|
||||
@include spiceFont("glue", 14px, "Medium");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ $font-weights: (
|
|||
font-family: $font;
|
||||
font-weight: $weight;
|
||||
font-style: normal;
|
||||
src: url("glue-resources/fonts/#{$font}-#{$style}.ttf") format("truetype");
|
||||
src: url(font64("#{$font}-#{$style}.ttf")) format("truetype");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -49,7 +49,7 @@ $font-weights: (
|
|||
}
|
||||
|
||||
// set font variables
|
||||
body {
|
||||
:root {
|
||||
--glue-font-family: #{map-get($font-names, "glue")}, #{map-get($font-names, "info")}, spotify-circular, spotify-circular-cyrillic, spotify-circular-arabic, spotify-circular-hebrew, Helvetica Neue, helvetica, arial, Hiragino Kaku Gothic Pro, Meiryo, MS Gothic, sans-serif;
|
||||
--info-font-family: #{map-get($font-names, "info")}, spotify-circular, spotify-circular-cyrillic, spotify-circular-arabic, spotify-circular-hebrew, Helvetica Neue, helvetica, arial, Hiragino Kaku Gothic Pro, Meiryo, MS Gothic, sans-serif;
|
||||
font-family: var(--glue-font-family);
|
||||
|
|
|
|||
7
src/styles/Icons.scss
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
svg[icon-type="dribbblish"] {
|
||||
overflow: visible;
|
||||
|
||||
// &[icon-style="custom"] {}
|
||||
|
||||
// &[icon-style^="material:"] {}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
#dribbblish-info-container {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-right: 8px;
|
||||
height: 100%;
|
||||
|
||||
&:empty {
|
||||
|
|
@ -16,7 +15,7 @@
|
|||
height: 100%;
|
||||
padding: 0px 8px;
|
||||
color: spiceColor("sidebar-text");
|
||||
background-color: spiceColor("button");
|
||||
background-color: spiceColor("sidebar");
|
||||
border-radius: var(--sidebar-icons-border-radius);
|
||||
line-height: 13px;
|
||||
@include spiceFont("glue", 14px, "Medium");
|
||||
|
|
@ -29,10 +28,5 @@
|
|||
padding: 0px;
|
||||
aspect-ratio: 1/1;
|
||||
}
|
||||
|
||||
& svg {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,49 @@
|
|||
button.main-button-primary {
|
||||
background-color: spiceColor("selected-row", 0.4) !important;
|
||||
.main-button {
|
||||
&-primary {
|
||||
$color: spiceColor("subtext");
|
||||
color: $color;
|
||||
background-color: spiceColor("selected-row", 0.4) !important;
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
background-color: spiceColor("selected-row", 0.6) !important;
|
||||
&:hover,
|
||||
&:active {
|
||||
background-color: spiceColor("selected-row", 0.6) !important;
|
||||
}
|
||||
|
||||
span {
|
||||
color: $color !important;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
color: spiceColor("subtext") !important;
|
||||
}
|
||||
}
|
||||
// ? Don't know if this exists
|
||||
// &-secondary {
|
||||
// $color: spiceColor("subtext");
|
||||
// color: $color;
|
||||
// background-color: spiceColor("selected-row", 0.4) !important;
|
||||
|
||||
// Modals
|
||||
.GenericModal button.main-button-primary {
|
||||
background-color: spiceColor("subtext", 0.6) !important;
|
||||
color: spiceColor("main") !important;
|
||||
// &:hover,
|
||||
// &:active {
|
||||
// background-color: spiceColor("selected-row", 0.6) !important;
|
||||
// }
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
background-color: spiceColor("subtext") !important;
|
||||
// span {
|
||||
// color: $color !important;
|
||||
// }
|
||||
// }
|
||||
|
||||
// ? the `:not(...)` is to fix #137
|
||||
&-tertiary:not(.main-entityHeader-titleButton) {
|
||||
$color: spiceColor("subtext");
|
||||
color: $color;
|
||||
background-color: spiceColor("selected-row", 0.2, 0.05) !important;
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
background-color: spiceColor("selected-row", 0.6) !important;
|
||||
}
|
||||
|
||||
span {
|
||||
color: $color !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -26,7 +51,7 @@ button.main-button-primary {
|
|||
.x-toggle-indicatorWrapper {
|
||||
background-color: spiceColor("subtext", 0.1);
|
||||
|
||||
input:hover ~ & {
|
||||
input:not(:disabled):hover ~ & {
|
||||
background-color: spiceColor("subtext", 0.15) !important;
|
||||
}
|
||||
|
||||
|
|
@ -34,7 +59,7 @@ button.main-button-primary {
|
|||
background-color: spiceColor("selected-row", 0.4) !important;
|
||||
}
|
||||
|
||||
input:hover:checked ~ & {
|
||||
input:not(:disabled):hover:checked ~ & {
|
||||
background-color: spiceColor("selected-row", 0.6) !important;
|
||||
}
|
||||
}
|
||||
|
|
@ -48,9 +73,21 @@ input {
|
|||
outline: none;
|
||||
border: none;
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
background-color: spiceColor("selected-row", 0.6) !important;
|
||||
&:not(:disabled) {
|
||||
&:hover,
|
||||
&:active {
|
||||
background-color: spiceColor("selected-row", 0.6) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: spiceColor("subtext");
|
||||
opacity: lightOffset(0.8, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -58,23 +95,17 @@ textarea {
|
|||
padding: 6px 10px;
|
||||
}
|
||||
|
||||
select {
|
||||
background-color: spiceColor("selected-row", 0.4) !important;
|
||||
color: spiceColor("subtext");
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
background-color: spiceColor("selected-row", 0.6) !important;
|
||||
}
|
||||
|
||||
option {
|
||||
background: spiceColor("main") !important;
|
||||
}
|
||||
select > option {
|
||||
background: spiceColor("main") !important;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 6px 10px 6px 48px;
|
||||
|
||||
&[type="checkbox"] {
|
||||
opacity: 0 !important;
|
||||
}
|
||||
|
||||
&[type="range"] {
|
||||
-webkit-appearance: none;
|
||||
background: transparent;
|
||||
|
|
@ -128,6 +159,7 @@ input {
|
|||
|
||||
&[type="number"],
|
||||
&[type="text"],
|
||||
&[type="search"],
|
||||
&[type="time"] {
|
||||
height: 32px;
|
||||
border-radius: 4px !important;
|
||||
|
|
@ -136,26 +168,12 @@ input {
|
|||
|
||||
&[type="time"] {
|
||||
&::-webkit-calendar-picker-indicator {
|
||||
filter: invert(calc(1 - var(--is_light)));
|
||||
filter: invert(var(--is_dark));
|
||||
}
|
||||
}
|
||||
|
||||
&[type="color"] {
|
||||
position: relative;
|
||||
padding: 0px;
|
||||
|
||||
&::before {
|
||||
z-index: -1;
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: -5px;
|
||||
border-radius: 4px;
|
||||
background-color: spiceColor("selected-row", 0.4);
|
||||
}
|
||||
|
||||
&:hover::before,
|
||||
&:active::before {
|
||||
background-color: spiceColor("selected-row", 0.6);
|
||||
}
|
||||
padding: 1px 3px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1
src/styles/Lyrics.scss
Normal file
|
|
@ -0,0 +1 @@
|
|||
// TODO: improve default lyrics styles when the classes are mapped `see: https://github.com/JulienMaille/dribbblish-dynamic-theme/issues/144`
|
||||
|
|
@ -1,4 +1,14 @@
|
|||
*[markdown] {
|
||||
ol,
|
||||
ul {
|
||||
list-style: inside;
|
||||
|
||||
> li::marker {
|
||||
content: "• ";
|
||||
font-weight: 900;
|
||||
}
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: spiceColor("subtext", 0.1);
|
||||
padding: 0px 5px;
|
||||
|
|
@ -6,6 +16,6 @@
|
|||
}
|
||||
|
||||
.muted {
|
||||
color: spiceColor("subtext", 0.5);
|
||||
color: spiceColor("subtext", 0.5, 0.2);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
67
src/styles/Modals.scss
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
.GenericModal__overlay {
|
||||
backdrop-filter: blur(3px) brightness(60%);
|
||||
background-color: transparent;
|
||||
|
||||
.GenericModal {
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
padding: 24px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@include spiceGlass();
|
||||
|
||||
// Popups (`Spicetify.PopupModal.display()`)
|
||||
generic-modal & {
|
||||
color: spiceColor("text");
|
||||
width: clamp(500px, 50%, 650px);
|
||||
}
|
||||
|
||||
// Dialogs (Delete Folder)
|
||||
.ReactModalPortal > & {
|
||||
> * {
|
||||
color: spiceColor("subtext");
|
||||
background-color: transparent;
|
||||
padding: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.main- {
|
||||
&trackCreditsModal,
|
||||
&embedWidgetGenerator {
|
||||
&-container {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
background-color: transparent;
|
||||
width: 100%;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&-header {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
padding: 0px;
|
||||
width: 100%;
|
||||
border: none;
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
@include spiceFont("glue", 2em, "Bold");
|
||||
}
|
||||
|
||||
button {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
&-mainSection {
|
||||
padding: 0px 26px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +1,31 @@
|
|||
#main[hide-ads="true"] {
|
||||
/* Remove upgrade button*/
|
||||
.main-topBar-UpgradeButton {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Remove upgrade to premium button in user menu */
|
||||
.main-contextMenu-menuItemButton[href="https://www.spotify.com/premium/"]
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Remove ad placeholder in main screen */
|
||||
// Hide ads
|
||||
#main[hide-ads~="ads"] {
|
||||
// Remove ad placeholder in main screen
|
||||
.main-leaderboardComponent-container {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Hide premium features
|
||||
#main[hide-ads~="premium"] {
|
||||
// Remove upgrade button
|
||||
.main-topBar-UpgradeButton {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Remove upgrade to premium button in user menu
|
||||
// prettier-ignore
|
||||
.main-contextMenu-menuItemButton[href="https://www.spotify.com/premium/"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Hide fullscreen button
|
||||
.volume-bar + .control-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Hide download buttons
|
||||
.main-actionBar-ActionBarRow button[role="switch"] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
42
src/styles/Overlay.scss
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
#dribbblish-overlay {
|
||||
z-index: 999999;
|
||||
position: fixed;
|
||||
inset: 0px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition: opacity 1s ease-in;
|
||||
color: spiceColor("subtext");
|
||||
@include spiceFont("glue", 32px, "Bold");
|
||||
|
||||
&[active] {
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
&::before {
|
||||
z-index: -1;
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0px;
|
||||
background-color: spiceColor("main", 0.9, -0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
& > div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
|
||||
& > * {
|
||||
color: var(--color);
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/styles/Util.scss
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// SASS overwrites the CSS invert function so we overwrite it back
|
||||
@function invert($v) {
|
||||
@return #{"invert("}$v#{")"};
|
||||
}
|
||||
|
||||
@mixin spiceShadow() {
|
||||
box-shadow: 0px 0px 8px spiceColor("subtext", 0.1, 0.1);
|
||||
}
|
||||
|
||||
@mixin spiceGlass($opacity: 0.95) {
|
||||
background-color: spiceColor("main", $opacity);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid spiceColor("subtext", 0.1, 0.1);
|
||||
border-radius: var(--main-corner-radius);
|
||||
@include spiceShadow();
|
||||
}
|
||||
|
|
@ -1,16 +1,18 @@
|
|||
// SASS overwrites the CSS invert function so we overwrite it back
|
||||
@function invert($v) {
|
||||
@return #{"invert("}$v#{")"};
|
||||
}
|
||||
|
||||
@import "Util";
|
||||
@import "Colors";
|
||||
@import "Fonts";
|
||||
@import "Inputs";
|
||||
@import "ConfigMenu";
|
||||
@import "ContextMenu.scss";
|
||||
@import "ContextMenu";
|
||||
@import "NoAds";
|
||||
@import "Markdown";
|
||||
@import "Info";
|
||||
@import "Modals";
|
||||
@import "ConnectDeviceList";
|
||||
@import "Lyrics";
|
||||
@import "Icons";
|
||||
@import "Overlay";
|
||||
@import "CustomAppTabBar";
|
||||
|
||||
:root {
|
||||
--bar-height: 70px;
|
||||
|
|
@ -20,31 +22,46 @@
|
|||
--scrollbar-vertical-size: 8px;
|
||||
--cover-border-radius: 8px;
|
||||
--playbar-movement-anim-speed: 0.5s;
|
||||
--image-radius: 10px;
|
||||
--image-radius: 4px;
|
||||
--sidebar-icons-border-radius: 50vh; // 50vh = round / pill
|
||||
--song-transition-speed: 3s;
|
||||
}
|
||||
--is_dark: calc(1 - var(--is_light));
|
||||
|
||||
.os-scrollbar-handle {
|
||||
background-color: spiceColor("text") !important;
|
||||
border-radius: calc(var(--scrollbar-vertical-size) / 2);
|
||||
}
|
||||
|
||||
.os-scrollbar-handle:hover {
|
||||
filter: brightness(80%);
|
||||
// Display warning for incorrectly installed js
|
||||
@keyframes noJsShow {
|
||||
to {
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
&:not([dribbblish-js-installed])::after {
|
||||
content: "dribbblish-dynamic.js not installed correctly";
|
||||
position: fixed;
|
||||
inset: 0px;
|
||||
color: red;
|
||||
background-color: black;
|
||||
text-align: center;
|
||||
line-height: 100vh;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
animation: noJsShow 0s ease-in 3s forwards;
|
||||
@include spiceFont("glue", 32px, "Bold");
|
||||
}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: var(--scrollbar-vertical-size);
|
||||
}
|
||||
|
||||
.os-scrollbar-handle,
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: spiceColor("text") !important;
|
||||
border-radius: calc(var(--scrollbar-vertical-size) / 2);
|
||||
background-color: spiceColor("sidebar") !important;
|
||||
border-radius: 50vw;
|
||||
}
|
||||
|
||||
.os-scrollbar-handle:hover,
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
filter: brightness(80%);
|
||||
background-color: spiceColor("sidebar", 0.8) !important;
|
||||
}
|
||||
|
||||
.main-type-mesto,
|
||||
|
|
@ -74,6 +91,24 @@
|
|||
color: spiceColor("text") !important;
|
||||
}
|
||||
|
||||
#dribbblish-search-box {
|
||||
display: none;
|
||||
order: 99;
|
||||
height: 28px;
|
||||
border-radius: var(--sidebar-icons-border-radius) !important;
|
||||
@include spiceFont("info", 14px, "Regular");
|
||||
}
|
||||
|
||||
#main[search-box] {
|
||||
#dribbblish-search-box {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.main-topBar-topbarContent form[role="search"] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.main-home-homeHeader,
|
||||
.x-entityHeader-overlay,
|
||||
.x-actionBarBackground-background,
|
||||
|
|
@ -88,15 +123,6 @@
|
|||
color: white;
|
||||
}
|
||||
|
||||
.connect-title,
|
||||
.connect-header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.connect-device-list {
|
||||
margin: 0px -5px;
|
||||
}
|
||||
|
||||
/* Remove Topbar background colour */
|
||||
.main-topBar-background {
|
||||
background-color: unset !important;
|
||||
|
|
@ -105,18 +131,31 @@
|
|||
background-color: spiceColor("main");
|
||||
}
|
||||
|
||||
.main-entityHeader-shadow,
|
||||
.connect-device-list-container {
|
||||
.main-entityHeader-shadow {
|
||||
box-shadow: 0 4px 20px #21212130;
|
||||
}
|
||||
|
||||
.main-trackList-rowMarker {
|
||||
color: spiceColor("text");
|
||||
}
|
||||
|
||||
.main-trackList-playingIcon {
|
||||
-webkit-mask-image: url(icon64("equaliser", '{"size": 14}'));
|
||||
background-color: currentColor;
|
||||
content-visibility: hidden;
|
||||
image-rendering: pixelated;
|
||||
filter: grayscale(1);
|
||||
}
|
||||
|
||||
.main-trackList-trackListRowGrid {
|
||||
background-color: var(--spice-main);
|
||||
}
|
||||
|
||||
.main-trackList-active {
|
||||
background-color: spiceColor("selected-row", 0.25) !important;
|
||||
}
|
||||
|
||||
.main-trackList-trackListRow:hover {
|
||||
background-color: spiceColor("selected-row", 0.2) !important;
|
||||
background-color: spiceColor("selected-row", 0.15) !important;
|
||||
}
|
||||
|
||||
.main-trackList-trackListRow:focus-within,
|
||||
|
|
@ -131,6 +170,11 @@ span.artist-artistVerifiedBadge-badge svg > path:last-of-type {
|
|||
fill: spiceColor("text");
|
||||
}
|
||||
|
||||
/* Playlist text color */
|
||||
.main-entityHeader-subtitle.main-entityHeader-gra {
|
||||
color: spiceColor("subtext");
|
||||
}
|
||||
|
||||
/* Full window artist background */
|
||||
.main-entityHeader-background.main-entityHeader-gradient {
|
||||
opacity: 0.3;
|
||||
|
|
@ -290,6 +334,7 @@ span.artist-artistVerifiedBadge-badge svg > path:last-of-type {
|
|||
}
|
||||
|
||||
.main-nowPlayingBar-container {
|
||||
position: relative;
|
||||
border-radius: 0 0 var(--main-corner-radius) var(--main-corner-radius);
|
||||
background-color: spiceColor("main");
|
||||
border-top: 0;
|
||||
|
|
@ -305,11 +350,27 @@ span.artist-artistVerifiedBadge-badge svg > path:last-of-type {
|
|||
}
|
||||
|
||||
.main-connectBar-connectBar {
|
||||
border-radius: 0 0 var(--main-corner-radius) var(--main-corner-radius);
|
||||
border: 2px solid spiceColor("main");
|
||||
border-top: 0;
|
||||
background-color: spiceColor("button") !important;
|
||||
color: spiceColor("text") !important;
|
||||
// These styles are useless, but maybe we'll change this back to the way it was before so I'll leave them
|
||||
display: none;
|
||||
|
||||
// position: absolute;
|
||||
// display: flex;
|
||||
// flex-direction: row-reverse;
|
||||
// gap: 8px;
|
||||
// height: fit-content;
|
||||
// right: 4px;
|
||||
// bottom: 4px;
|
||||
// padding: 2px 8px;
|
||||
// background-color: spiceColor("text", 0.2) !important;
|
||||
// border-radius: var(--main-corner-radius);
|
||||
|
||||
// &::after {
|
||||
// display: none;
|
||||
// }
|
||||
|
||||
// svg {
|
||||
// margin: 0px;
|
||||
// }
|
||||
}
|
||||
|
||||
.Root__nav-bar {
|
||||
|
|
@ -472,11 +533,17 @@ html.sidebar-hide-text .GlueDropTarget span {
|
|||
display: none;
|
||||
}
|
||||
|
||||
/** */
|
||||
.main-topBar-historyButtons .main-topBar-button {
|
||||
background-color: unset;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
.main-topBar-historyButtons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.main-topBar-button {
|
||||
margin: 0px;
|
||||
background-color: unset;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.main-topBar-historyButtons svg {
|
||||
|
|
@ -492,13 +559,9 @@ html.sidebar-hide-text .GlueDropTarget span {
|
|||
|
||||
.playback-bar {
|
||||
position: absolute;
|
||||
width: var(--main-view-width);
|
||||
left: var(--sidebar-width);
|
||||
bottom: calc(var(--main-gap) + var(--bar-height) - 12px / 2);
|
||||
}
|
||||
|
||||
.Root.is-connectBarVisible .playback-bar {
|
||||
bottom: calc(var(--main-gap) + var(--bar-height) + 24px - 12px / 2);
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
top: calc(0px - 12px / 2);
|
||||
}
|
||||
|
||||
.main-nowPlayingWidget-coverArt .cover-art {
|
||||
|
|
@ -513,6 +576,7 @@ html.sidebar-hide-text .GlueDropTarget span {
|
|||
|
||||
.progress-bar {
|
||||
--progress-bar-height: 2px;
|
||||
--is-active-fg-color: #{spiceColor("button-active")};
|
||||
--fg-color: #{spiceColor("button")};
|
||||
--bg-color: #{spiceColor("text", 0.2)};
|
||||
}
|
||||
|
|
@ -528,12 +592,18 @@ html.sidebar-hide-text .GlueDropTarget span {
|
|||
opacity: 1;
|
||||
}
|
||||
|
||||
.progress-bar:not(:active) .x-progressBar-progressBarBg > div:first-child > div {
|
||||
transition: transform var(--playbar-movement-anim-speed) ease;
|
||||
}
|
||||
#main[playbar-transition] {
|
||||
.progress-bar:not(:active) .x-progressBar-progressBarBg > div:first-child > div {
|
||||
transition: transform var(--playbar-movement-anim-speed) ease;
|
||||
}
|
||||
|
||||
.progress-bar:not(:active) .progress-bar__slider {
|
||||
transition-property: left, opacity;
|
||||
.progress-bar:not(:active) .progress-bar__slider {
|
||||
transition-property: left, opacity;
|
||||
}
|
||||
|
||||
.playback-bar:not(:active) .prog-tooltip {
|
||||
transition: transform var(--playbar-movement-anim-speed) ease;
|
||||
}
|
||||
}
|
||||
|
||||
.playback-bar .prog-tooltip {
|
||||
|
|
@ -543,15 +613,11 @@ html.sidebar-hide-text .GlueDropTarget span {
|
|||
left: 50%;
|
||||
transform: translateX(calc(-50%));
|
||||
padding: 0 5px;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
color: spiceColor("text");
|
||||
background-color: spiceColor("button");
|
||||
white-space: nowrap;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.playback-bar:not(:active) .prog-tooltip {
|
||||
transition: transform var(--playbar-movement-anim-speed) ease;
|
||||
color: spiceColor("subtext");
|
||||
@include spiceGlass();
|
||||
}
|
||||
|
||||
.minimal-player .player-controls__buttons {
|
||||
|
|
@ -606,13 +672,17 @@ html.sidebar-hide-text .GlueDropTarget span {
|
|||
}
|
||||
|
||||
#main-trackInfo-year {
|
||||
display: block;
|
||||
|
||||
#main[playbar-album-info="false"] & {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
#main-trackInfo-genre {
|
||||
#main[playbar-genre-info="false"] & {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.main-topBar-topbarContent .main-playButton-PlayButton {
|
||||
--size: 35px !important;
|
||||
}
|
||||
|
|
@ -643,7 +713,7 @@ html.sidebar-hide-text .GlueDropTarget span {
|
|||
padding: 0 12px;
|
||||
}
|
||||
|
||||
img.playlist-picture {
|
||||
.playlist-picture {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
flex: 0 0 32px;
|
||||
|
|
@ -651,6 +721,10 @@ img.playlist-picture {
|
|||
background-size: cover;
|
||||
background-position: center;
|
||||
border-radius: var(--sidebar-icons-border-radius);
|
||||
|
||||
&[type="icon"] {
|
||||
padding: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.main-rootlist-rootlistItem a span {
|
||||
|
|
@ -742,12 +816,15 @@ li.GlueDropTarget {
|
|||
padding-top: 32px;
|
||||
}
|
||||
|
||||
#main[top-bar="none-padding"] .spotify__container--is-desktop.spotify__os--is-windows[dir="ltr"] .Root__top-bar + .main-buddyFeed-buddyFeedRoot .main-topBar-container {
|
||||
padding-right: 167px;
|
||||
}
|
||||
|
||||
.main-topBar-container {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
max-width: unset;
|
||||
padding: 16px 32px !important;
|
||||
|
||||
& > * {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/** Custom elements */
|
||||
|
|
@ -974,7 +1051,8 @@ span.main-userWidget-displayName,
|
|||
|
||||
.main-rootlist-wrapper > div:nth-child(2) > li img,
|
||||
.main-navBar-navBarLink > svg,
|
||||
.main-navBar-navBarLink > .icon {
|
||||
.main-navBar-navBarLink > .icon,
|
||||
.main-rootlist-rootlistItemLink > svg {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
|
@ -994,7 +1072,7 @@ span.main-userWidget-displayName,
|
|||
left: -200%;
|
||||
opacity: 0.4;
|
||||
background-color: black;
|
||||
border-radius: var(--image-radius);
|
||||
border-radius: var(--sidebar-icons-border-radius);
|
||||
pointer-events: none;
|
||||
transition: all calc(var(--sidebar-icons-hover-animation) * 0.2s) ease;
|
||||
transition-property: left, opacity;
|
||||
|
|
@ -1051,12 +1129,8 @@ div.GlueDropTarget.personal-library > *.active {
|
|||
left: calc(var(--sidebar-width) + 10px);
|
||||
}
|
||||
|
||||
.Root.is-connectBarVisible .main-coverSlotExpanded-container {
|
||||
bottom: calc(var(--main-gap) + var(--bar-height) + 24px + 10px);
|
||||
}
|
||||
|
||||
html.right-expanded-cover .main-coverSlotExpanded-container {
|
||||
right: var(--main-gap);
|
||||
right: calc(var(--main-gap) * 2);
|
||||
left: unset;
|
||||
}
|
||||
|
||||
|
|
@ -1113,11 +1187,6 @@ html.right-expanded-cover.buddyfeed-visible .main-coverSlotExpanded-container {
|
|||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.connect-device-list-item:focus,
|
||||
.connect-device-list-item:hover {
|
||||
background-color: spiceColor("selected-row", 0.3);
|
||||
}
|
||||
|
||||
/* 1.1.56 */
|
||||
.main-navBar-navBar {
|
||||
width: var(--sidebar-width) !important;
|
||||
|
|
@ -1163,10 +1232,6 @@ html.right-expanded-cover.buddyfeed-visible .main-coverSlotExpanded-container {
|
|||
.main-tag-container {
|
||||
background-color: spiceColor("text");
|
||||
}
|
||||
/* progressbar tooltip text color */
|
||||
.playback-bar .prog-tooltip {
|
||||
color: spiceColor("sidebar-text") !important;
|
||||
}
|
||||
|
||||
/* edit button of CustomApps */
|
||||
.reddit-sort-container button.switch,
|
||||
|
|
@ -1203,6 +1268,10 @@ html.right-expanded-cover.buddyfeed-visible .main-coverSlotExpanded-container {
|
|||
color: spiceColor("sidebar-text") !important;
|
||||
}
|
||||
|
||||
.main-shelf-title {
|
||||
@include spiceFont("glue");
|
||||
}
|
||||
|
||||
/* translucent background cover */
|
||||
.Root__top-container::before {
|
||||
z-index: 3;
|
||||
|
|
@ -1216,24 +1285,37 @@ html.right-expanded-cover.buddyfeed-visible .main-coverSlotExpanded-container {
|
|||
background-position: center center;
|
||||
filter: blur(15px);
|
||||
border-radius: var(--main-corner-radius);
|
||||
opacity: calc(0.07 + 0.03 * var(--is_light, 0));
|
||||
opacity: lightOffset(0.07, 0.03);
|
||||
transition: background-image var(--song-transition-speed) linear;
|
||||
|
||||
html:not(.buddyfeed-visible) & {
|
||||
inset: var(--main-gap) 0px var(--main-gap) var(--sidebar-width);
|
||||
}
|
||||
}
|
||||
|
||||
// Fix lyrics-plus having a scrollbar when top-bar is `solid` or `transparent`
|
||||
.lyrics-lyricsContainer-LyricsContainer {
|
||||
height: 100% !important;
|
||||
}
|
||||
// Fix main-view-containers having a scrollbar when top-bar is `solid` or `transparent`
|
||||
.main-view-container__scroll-node-child {
|
||||
height: 100%;
|
||||
padding-bottom: 0px;
|
||||
|
||||
& > * {
|
||||
height: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Hide default Sporify "Offline" notice
|
||||
// Hide default Spotify "Offline" notice
|
||||
.main-noConnection {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Notifications
|
||||
.main-notificationBubbleContainer-NotificationBubbleContainer {
|
||||
.main-notificationBubble-NotificationBubble {
|
||||
color: spiceColor("subtext");
|
||||
@include spiceGlass();
|
||||
}
|
||||
}
|
||||
|
||||
// ! WORKAROUNDS / TEMP FIXES
|
||||
// Spotify UI breaks after advertisements #63
|
||||
canvas[width="250"][height="250"] {
|
||||
|
|
@ -1251,3 +1333,20 @@ canvas[width="250"][height="250"] {
|
|||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
// fix albumn name font size (#171)
|
||||
.main-trackInfo-release {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
// fix play buttons color going green (#174)
|
||||
.encore-bright-accent-set {
|
||||
--background-highlight: var(--spice-button) !important;
|
||||
--background-press: var(--spice-button-active) !important;
|
||||
color: var(--spice-sidebar-text);
|
||||
}
|
||||
|
||||
// hide the triangle under the connect button
|
||||
.control-button--active::after {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="16" height="16">
|
||||
<path fill="currentColor" d="M413.1 222.5l22.2 22.2c9.4 9.4 9.4 24.6 0 33.9L241 473c-9.4 9.4-24.6 9.4-33.9 0L12.7 278.6c-9.4-9.4-9.4-24.6 0-33.9l22.2-22.2c9.5-9.5 25-9.3 34.3.4L184 343.4V56c0-13.3 10.7-24 24-24h32c13.3 0 24 10.7 24 24v287.4l114.8-120.5c9.3-9.8 24.8-10 34.3-.4z"></path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 383 B |
|
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512" width="16" height="16">
|
||||
<path fill="currentColor" d="M278.9 511.5l-61-17.7c-6.4-1.8-10-8.5-8.2-14.9L346.2 8.7c1.8-6.4 8.5-10 14.9-8.2l61 17.7c6.4 1.8 10 8.5 8.2 14.9L293.8 503.3c-1.9 6.4-8.5 10.1-14.9 8.2zm-114-112.2l43.5-46.4c4.6-4.9 4.3-12.7-.8-17.2L117 256l90.6-79.7c5.1-4.5 5.5-12.3.8-17.2l-43.5-46.4c-4.5-4.8-12.1-5.1-17-.5L3.8 247.2c-5.1 4.7-5.1 12.8 0 17.5l144.1 135.1c4.9 4.6 12.5 4.4 17-.5zm327.2.6l144.1-135.1c5.1-4.7 5.1-12.8 0-17.5L492.1 112.1c-4.8-4.5-12.4-4.3-17 .5L431.6 159c-4.6 4.9-4.3 12.7.8 17.2L523 256l-90.6 79.7c-5.1 4.5-5.5 12.3-.8 17.2l43.5 46.4c4.5 4.9 12.1 5.1 17 .6z"></path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 675 B |
|
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="16" height="16">
|
||||
<path fill="currentColor" d="M487.4 315.7l-42.6-24.6c4.3-23.2 4.3-47 0-70.2l42.6-24.6c4.9-2.8 7.1-8.6 5.5-14-11.1-35.6-30-67.8-54.7-94.6-3.8-4.1-10-5.1-14.8-2.3L380.8 110c-17.9-15.4-38.5-27.3-60.8-35.1V25.8c0-5.6-3.9-10.5-9.4-11.7-36.7-8.2-74.3-7.8-109.2 0-5.5 1.2-9.4 6.1-9.4 11.7V75c-22.2 7.9-42.8 19.8-60.8 35.1L88.7 85.5c-4.9-2.8-11-1.9-14.8 2.3-24.7 26.7-43.6 58.9-54.7 94.6-1.7 5.4.6 11.2 5.5 14L67.3 221c-4.3 23.2-4.3 47 0 70.2l-42.6 24.6c-4.9 2.8-7.1 8.6-5.5 14 11.1 35.6 30 67.8 54.7 94.6 3.8 4.1 10 5.1 14.8 2.3l42.6-24.6c17.9 15.4 38.5 27.3 60.8 35.1v49.2c0 5.6 3.9 10.5 9.4 11.7 36.7 8.2 74.3 7.8 109.2 0 5.5-1.2 9.4-6.1 9.4-11.7v-49.2c22.2-7.9 42.8-19.8 60.8-35.1l42.6 24.6c4.9 2.8 11 1.9 14.8-2.3 24.7-26.7 43.6-58.9 54.7-94.6 1.5-5.5-.7-11.3-5.6-14.1zM256 336c-44.1 0-80-35.9-80-80s35.9-80 80-80 80 35.9 80 80-35.9 80-80 80z"></path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 945 B |
|
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="16" height="16">
|
||||
<path fill="currentColor" d="M255.545 8c-66.269.119-126.438 26.233-170.86 68.685L48.971 40.971C33.851 25.851 8 36.559 8 57.941V192c0 13.255 10.745 24 24 24h134.059c21.382 0 32.09-25.851 16.971-40.971l-41.75-41.75c30.864-28.899 70.801-44.907 113.23-45.273 92.398-.798 170.283 73.977 169.484 169.442C423.236 348.009 349.816 424 256 424c-41.127 0-79.997-14.678-110.63-41.556-4.743-4.161-11.906-3.908-16.368.553L89.34 422.659c-4.872 4.872-4.631 12.815.482 17.433C133.798 479.813 192.074 504 256 504c136.966 0 247.999-111.033 248-247.998C504.001 119.193 392.354 7.755 255.545 8z"></path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 679 B |
|
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512" width="16" height="16">
|
||||
<path fill="currentColor" d="M5.09 154.87c-6.66 6.16-6.79 16.59-.35 22.97l34.24 33.96c6.14 6.09 16.02 6.23 22.4.38 6.99-6.4 14.31-12.22 21.65-18.01l-64.96-50.21c-4.3 3.71-8.79 7.04-12.98 10.91zm471.75 181.9l45.42-45.21c6.52-6.46 6.29-17.06-.57-23.17-64.94-57.74-148.91-82.66-230.34-74.98l-83.16-64.27c125.94-38.36 267.75-11.01 370.43 83.05 6.38 5.85 16.26 5.71 22.4-.38l34.24-33.96c6.44-6.39 6.3-16.82-.35-22.97C496.46 26.82 298.08-.76 133.42 71.35L45.47 3.37C38.49-2.05 28.43-.8 23.01 6.18L3.37 31.45C-2.05 38.42-.8 48.47 6.18 53.9l588.36 454.73c6.98 5.43 17.03 4.17 22.46-2.81l19.64-25.27c5.42-6.97 4.17-17.03-2.81-22.45L476.84 336.77zm-358.53-68.38c-6.86 6.1-7.08 16.7-.57 23.17l34.28 34.01c5.97 5.93 15.59 6.32 21.94.8 13.35-11.6 28.01-20.66 43.15-28.55l-68.36-52.83c-10.48 7.15-20.74 14.78-30.44 23.4zM256 416c0 35.35 28.65 64 64 64 31.91 0 58.15-23.42 62.99-53.98l-88.7-68.56C271.77 367.37 256 389.82 256 416z"></path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1,021 B |
|
|
@ -1,4 +1,4 @@
|
|||
spicetify config current_theme " " extensions dribbblish-dynamic.js-
|
||||
spicetify config current_theme "SpicetifyDefault" extensions dribbblish-dynamic.js-
|
||||
|
||||
$spicePath = spicetify -c | Split-Path
|
||||
$configFile = Get-Content "$spicePath\config-xpui.ini"
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
set -e
|
||||
|
||||
echo "UN-INSTALLING"
|
||||
cd "$(dirname "$(spicetify -c)")/Themes/DribbblishDynamic"
|
||||
spicetify config current_theme " " extensions dribbblish-dynamic.js-
|
||||
cd "$(dirname "$(spicetify -c)")"
|
||||
spicetify config current_theme "SpicetifyDefault" extensions dribbblish-dynamic.js-
|
||||
|
||||
echo "UN-PATCHING"
|
||||
if cat config-xpui.ini | grep -o '\[Patch\]'; then
|
||||
|
|
|
|||
110
webpack.config.babel.js
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
import webpack from "webpack";
|
||||
import sass from "sass";
|
||||
import { CleanWebpackPlugin } from "clean-webpack-plugin";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
|
||||
import Icons from "./src/js/Icons";
|
||||
import { lightOffset, spiceColor } from "./src/js/SassUtil";
|
||||
|
||||
const _icons = {};
|
||||
function addIcon(name, style, path) {
|
||||
name = name.replace(/_/g, "-");
|
||||
if (!_icons.hasOwnProperty(name)) _icons[name] = {};
|
||||
_icons[name][style] = fs.readFileSync(path, { encoding: "utf8" });
|
||||
}
|
||||
// Add Material Icons
|
||||
let iconDir = path.resolve(__dirname, "node_modules/@material-icons/svg/svg");
|
||||
for (const dir of fs.readdirSync(iconDir)) {
|
||||
for (const file of fs.readdirSync(path.resolve(iconDir, dir))) {
|
||||
addIcon(dir, `material:${file.replace(/\..*?$/, "")}`, path.resolve(iconDir, dir, file));
|
||||
}
|
||||
}
|
||||
// Add Custom Icons
|
||||
iconDir = path.resolve(__dirname, "src/icons");
|
||||
for (const icon of fs.readdirSync(iconDir)) {
|
||||
addIcon(icon.replace(/\..*?$/, ""), "custom", path.resolve(iconDir, icon));
|
||||
}
|
||||
|
||||
const icons = new Icons(_icons);
|
||||
|
||||
/** @type {import('webpack').Configuration} */
|
||||
const config = {
|
||||
mode: "development",
|
||||
entry: [path.resolve(__dirname, "src/js/main.js"), path.resolve(__dirname, "src/styles/main.scss"), path.resolve(__dirname, "src/styles/Colors.scss")],
|
||||
output: {
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
filename: "dribbblish-dynamic.js"
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
include: path.resolve(__dirname, "src/js/main.js"),
|
||||
use: []
|
||||
},
|
||||
{
|
||||
include: path.resolve(__dirname, "src/styles/main.scss"),
|
||||
type: "asset/resource",
|
||||
generator: {
|
||||
filename: "user.css"
|
||||
},
|
||||
use: [
|
||||
{
|
||||
loader: "sass-loader",
|
||||
options: {
|
||||
implementation: sass,
|
||||
sourceMap: true,
|
||||
sassOptions: {
|
||||
functions: {
|
||||
"lightOffset($n, $offset)": (n, offset) => {
|
||||
return new sass.types.String(lightOffset(n.getValue(), offset.getValue()));
|
||||
},
|
||||
"spiceColor($key, $alpha: 1, $light-offset: 0)": (key, alpha, _lightOffset) => {
|
||||
return new sass.types.String(spiceColor(key.getValue(), alpha.getValue(), _lightOffset.getValue()));
|
||||
},
|
||||
"font64($font)": (font) => {
|
||||
const file = path.resolve(__dirname, "src/fonts", font.getValue());
|
||||
return new sass.types.String(`"data:font/truetype;charset=utf-8;base64,${fs.readFileSync(file, { encoding: "base64" })}"`);
|
||||
},
|
||||
'icon64($name, $options: "{}")': (name, options) => {
|
||||
name = name.getValue();
|
||||
options = JSON.parse(options.getValue());
|
||||
return new sass.types.String(icons.get(name, { ...options, base64: true }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
include: path.resolve(__dirname, "src/styles/Colors.scss"),
|
||||
type: "asset/resource",
|
||||
generator: {
|
||||
filename: "color.ini"
|
||||
},
|
||||
use: [path.resolve(__dirname, "src/loaders/color-loader.js")]
|
||||
}
|
||||
]
|
||||
},
|
||||
devtool: "inline-source-map",
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
"process.env.BUG_REPORT": JSON.stringify(
|
||||
fs
|
||||
.readFileSync(path.resolve(__dirname, ".github/ISSUE_TEMPLATE/bug_report.md"), { encoding: "utf8" })
|
||||
.replace(/^---(.*?\r?\n)+---(.*?\r?\n)*?(?=\S)/, "") // Replace GitHub header
|
||||
.replace(/\*\*Desktop Setup\*\*\r?\n(- .*?\r?\n)+\r?\n/, "") // Replace **Desktop Setup** block
|
||||
),
|
||||
"process.env.DRIBBBLISH_ICONS": JSON.stringify(_icons),
|
||||
"process.env.DRIBBBLISH_VERSION": JSON.stringify(process.env.DRIBBBLISH_VERSION ?? "Dev"),
|
||||
"process.env.COMMIT_HASH": JSON.stringify(process.env.COMMIT_HASH ?? "local")
|
||||
}),
|
||||
new CleanWebpackPlugin({
|
||||
protectWebpackAssets: false,
|
||||
cleanAfterEveryBuildPatterns: ["*.LICENSE.txt"]
|
||||
})
|
||||
]
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
const webpack = require("webpack");
|
||||
const sass = require("sass");
|
||||
const CopyPlugin = require("copy-webpack-plugin");
|
||||
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
|
||||
const path = require("path");
|
||||
|
||||
/** @type {import('webpack').Configuration} */
|
||||
module.exports = {
|
||||
entry: [path.resolve(__dirname, "./src/js/main.js"), path.resolve(__dirname, "./src/styles/main.scss"), path.resolve(__dirname, "./src/styles/Colors.scss")],
|
||||
output: {
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
filename: "dribbblish-dynamic.js"
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".js", ".svg"],
|
||||
alias: {
|
||||
svg: path.resolve(__dirname, "./src/svg")
|
||||
}
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
include: path.resolve(__dirname, "./src/js/main.js"),
|
||||
use: []
|
||||
},
|
||||
{
|
||||
include: path.resolve(__dirname, "./src/styles/main.scss"),
|
||||
type: "asset/resource",
|
||||
generator: {
|
||||
filename: "user.css"
|
||||
},
|
||||
use: [
|
||||
{
|
||||
loader: "sass-loader",
|
||||
options: {
|
||||
implementation: sass,
|
||||
sourceMap: true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
include: path.resolve(__dirname, "./src/styles/Colors.scss"),
|
||||
type: "asset/resource",
|
||||
generator: {
|
||||
filename: "color.ini"
|
||||
},
|
||||
use: [path.resolve(__dirname, "./src/loaders/color-loader.js")]
|
||||
},
|
||||
{
|
||||
test: /\.svg/,
|
||||
exclude: /node_modules/,
|
||||
type: "asset/source"
|
||||
}
|
||||
]
|
||||
},
|
||||
devtool: "inline-source-map",
|
||||
plugins: [
|
||||
new CleanWebpackPlugin({
|
||||
protectWebpackAssets: false,
|
||||
cleanAfterEveryBuildPatterns: ["*.LICENSE.txt"]
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
"process.env.DRIBBBLISH_VERSION": JSON.stringify(process.env.DRIBBBLISH_VERSION || "Dev"),
|
||||
"process.env.COMMIT_HASH": JSON.stringify(process.env.COMMIT_HASH || "local")
|
||||
}),
|
||||
new CopyPlugin({
|
||||
patterns: [{ from: "src/assets", to: "assets" }]
|
||||
})
|
||||
]
|
||||
};
|
||||