Merge remote-tracking branch 'origin/webpack'

This commit is contained in:
Send_Nukez 2021-10-28 17:46:13 +02:00 committed by GitHub Action
commit 6161f6808f
27 changed files with 2633 additions and 2498 deletions

34
.github/workflows/empty-changelog.yaml vendored Normal file
View file

@ -0,0 +1,34 @@
name: Empty CHANGELOG.md
on:
release:
types: [published]
jobs:
empty-changelog:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
ref: ${{ github.event.repository.default_branch }}
- name: Empty CHANGELOG.md
run: |
git config --global user.email "action@github.com"
git config --global user.name "github-actions"
if [ -s CHANGELOG.md ]; then
echo "Comitting emptied CHANGELOG.md"
else
echo "CHANGELOG.md is already empty. skipping comitting"
exit 0
fi
rm CHANGELOG.md
touch CHANGELOG.md
git add CHANGELOG.md
git commit -m "Empty CHANGELOG.md"
git push

73
.github/workflows/release.yaml vendored Normal file
View file

@ -0,0 +1,73 @@
name: Release
on:
workflow_dispatch:
inputs:
version:
description: 'Version of Release (format: X.X.X)'
required: true
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Verify Input
run: |
[[ "${{ github.event.inputs.version }}" =~ ^[0-9]\.[0-9]\.[0-9]$ ]] && echo "Matches" && exit 0 || echo "Use versions like '1.2.3'" && exit 1
- name: Checkout
uses: actions/checkout@v2
with:
ref: ${{ github.head_ref }}
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '14'
- name: Build Webpack
run: |
npm install
npm run build
echo "${{ github.event.inputs.version }}" > dist/VERSION
env:
DRIBBBLISH_VERSION: ${{ github.event.inputs.version }}
- name: Zip Release
working-directory: dist
run: |
sudo apt-get install zip
zip -r DribbblishDynamic_v${{ github.event.inputs.version }}.zip *
mv DribbblishDynamic_v${{ github.event.inputs.version }}.zip ..
- name: Read CHANGELOG.md
run: |
[ -s CHANGELOG.md ] && CHANGELOG=$(< CHANGELOG.md) || CHANGELOG="*Empty.*"
echo "CHANGELOG<<EOF" >> $GITHUB_ENV
echo "$CHANGELOG" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
- name: Upload Release
uses: softprops/action-gh-release@v1
with:
fail_on_unmatched_files : true
files: DribbblishDynamic_v${{ github.event.inputs.version }}.zip
tag_name: ${{ github.event.inputs.version }}
draft: true
name: v${{ github.event.inputs.version }}
body: |
## Changelog
${{ env.CHANGELOG }}
---
### Install / Update
#### Windows (PowerShell)
```powershell
Invoke-WebRequest -UseBasicParsing "https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/master/install.ps1" | Invoke-Expression
```
#### Linux/MacOS (Bash)
```bash
curl -fsSL https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/master/install.sh | sh
```

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
node_modules/
dist/

View file

@ -1 +1,2 @@
Vibrant.min.js Vibrant.min.js
dist/

0
CHANGELOG.md Normal file
View file

View file

@ -1,24 +1,25 @@
# Dribbblish Dynamic # Dribbblish Dynamic
### Preview ### Preview
![preview](showcase-images/preview.gif)
<img src="showcase-images/preview.gif" alt="img" width="500px">
## Features ## Features
### Resizable sidebar ### Resizable sidebar
<img src="https://i.imgur.com/1zomkmd.png" alt="img" width="500px"> <img src="showcase-images/resize-sidebar.png" alt="img" width="500px">
### Customizable sidebar ### Customizable sidebar
Rearrange icons positions, stick icons to header or hide unnecessary to save space. 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. 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. After you finish customizing, turn off Config mode in Profile menu to save.
<img src="https://i.imgur.com/86gqPe8.png" alt="img" width="500px"> <img src="showcase-images/customize-sidebar.png" alt="img" width="500px">
### Playlist Folder image ### 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. 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="https://i.imgur.com/WGQ7Bev.gif" alt="img" width="500px"> <img src="showcase-images/playlist-folders.gif" alt="img" width="500px">
### Left/Right expanded cover ### Left/Right expanded cover
In profile menu, toggle option "Right expanded cover" to change expaned current track cover image to left or right side, whereever you prefer. In profile menu, toggle option "Right expanded cover" to change expaned current track cover image to left or right side, whereever you prefer.
@ -26,20 +27,30 @@ In profile menu, toggle option "Right expanded cover" to change expaned current
## Install / Update ## Install / Update
Make sure you are using spicetify >= v2.6.0 and Spotify >= v1.1.67. Make sure you are using spicetify >= v2.6.0 and Spotify >= v1.1.67.
Run these commands: ### Windows (PowerShell)
### Windows
In **Powershell**:
```powershell ```powershell
Invoke-WebRequest -UseBasicParsing "https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/master/install.ps1" | Invoke-Expression Invoke-WebRequest -UseBasicParsing "https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/master/install.ps1" | Invoke-Expression
``` ```
### Linux and MacOS: ### Linux/MacOS (Bash)
In **Bash**:
```bash ```bash
curl -fsSL https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/master/install.sh | sh curl -fsSL https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/master/install.sh | sh
``` ```
### Manual Install
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. Run:
```
spicetify config extensions dribbblish-dynamic.js
spicetify config current_theme DribbblishDynamic
spicetify config color_scheme base
spicetify config inject_css 1 replace_colors 1 overwrite_assets 1
spicetify apply
```
## IMPORTANT!
From Spotify > v1.1.62, in sidebar, they use an adaptive render mechanic to actively show and hide items on scroll. It helps reducing number of items to render, hence there is significant performance boost if you have a large playlists collection. But the drawbacks is that item height is hard-coded, it messes up user interaction when we explicity change, in CSS, playlist item height bigger than original value. So you need to add these 2 lines in Patch section in config file: From Spotify > v1.1.62, in sidebar, they use an adaptive render mechanic to actively show and hide items on scroll. It helps reducing number of items to render, hence there is significant performance boost if you have a large playlists collection. But the drawbacks is that item height is hard-coded, it messes up user interaction when we explicity change, in CSS, playlist item height bigger than original value. So you need to add these 2 lines in Patch section in config file:
```ini ```ini
[Patch] [Patch]
@ -50,30 +61,23 @@ xpui.js_repl_8008 = ,${1}56,
## Hide Window Controls ## Hide Window Controls
Windows user, please edit your Spotify shortcut and add flag `--transparent-window-controls` after the Spotify.exe: 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 an taskbar shortcut, right click it, then right click Spotify in the list again.
![instruction1](showcase-images/windows-shortcut-instruction.png)
<img src="showcase-images/windows-shortcut-instruction.png" alt="img" width="500px">
In addition to `--transparent-window-controls` you can set `Windows Top Bars` to `Solid` or `Transparent` to look like this: In addition to `--transparent-window-controls` you can set `Windows Top Bars` to `Solid` or `Transparent` to look like this:
![top-bars](showcase-images/top-bars.png)
Alternatively, you can use [`SpotifyNoControl.exe`](https://github.com/JulienMaille/SpotifyNoControl/files/6793911/SpotifyNoControl.zip), included in this theme package, to completely remove all windows controls and title menu (three dots at top left corner). Title menu still can be accessed via the Alt key. Closing and minimizing can be done via the right click menu at top window region. <img src="showcase-images/top-bars.png" alt="img" width="500px">
`SpotifyNoControl.exe` could be used as Spotify launcher, it opens Spotify and hides controls right after. You can drag and drop it to your taskbar but make sure you unpin the original Spotify icon first. Alternatively you can make a shortcut for it and add to desktop or start menu.
![nocontrol](https://i.imgur.com/qdZyv1t.png) ## Uninstall
### Windows (PowerShell)
## Auto-uninstall
### Windows
```powershell ```powershell
Invoke-WebRequest -UseBasicParsing "https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/master/uninstall.ps1" | Invoke-Expression Invoke-WebRequest -UseBasicParsing "https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/master/uninstall.ps1" | Invoke-Expression
``` ```
## Manual uninstall ### Manual Uninstall
Remove the dribbblish script with the following commands 1. Remove Patch lines you added in config file earlier.
2. Run:
``` ```
spicetify config extensions dribbblish.js- spicetify config extensions dribbblish-dynamic.js-
spicetify config extensions dribbblish-dynamic.js- spicetify apply
``` ```
And remove Patch lines you added in config file earlier. Finally, run:
```
spicetify apply
```

23
Vibrant.min.js vendored
View file

@ -1,23 +0,0 @@
(function e$$0(x,z,l){function h(p,b){if(!z[p]){if(!x[p]){var a="function"==typeof require&&require;if(!b&&a)return a(p,!0);if(g)return g(p,!0);a=Error("Cannot find module '"+p+"'");throw a.code="MODULE_NOT_FOUND",a;}a=z[p]={exports:{}};x[p][0].call(a.exports,function(a){var b=x[p][1][a];return h(b?b:a)},a,a.exports,e$$0,x,z,l)}return z[p].exports}for(var g="function"==typeof require&&require,w=0;w<l.length;w++)h(l[w]);return h})({1:[function(A,x,z){if(!l)var l={map:function(h,g){var l={};return g?
h.map(function(h,b){l.index=b;return g.call(l,h)}):h.slice()},naturalOrder:function(h,g){return h<g?-1:h>g?1:0},sum:function(h,g){var l={};return h.reduce(g?function(h,b,a){l.index=a;return h+g.call(l,b)}:function(h,b){return h+b},0)},max:function(h,g){return Math.max.apply(null,g?l.map(h,g):h)}};A=function(){function h(f,c,a){return(f<<2*d)+(c<<d)+a}function g(f){function c(){a.sort(f);b=!0}var a=[],b=!1;return{push:function(c){a.push(c);b=!1},peek:function(f){b||c();void 0===f&&(f=a.length-1);return a[f]},
pop:function(){b||c();return a.pop()},size:function(){return a.length},map:function(c){return a.map(c)},debug:function(){b||c();return a}}}function w(f,c,a,b,m,e,q){this.r1=f;this.r2=c;this.g1=a;this.g2=b;this.b1=m;this.b2=e;this.histo=q}function p(){this.vboxes=new g(function(f,c){return l.naturalOrder(f.vbox.count()*f.vbox.volume(),c.vbox.count()*c.vbox.volume())})}function b(f){var c=Array(1<<3*d),a,b,m,r;f.forEach(function(f){b=f[0]>>e;m=f[1]>>e;r=f[2]>>e;a=h(b,m,r);c[a]=(c[a]||0)+1});return c}
function a(f,c){var a=1E6,b=0,m=1E6,d=0,q=1E6,n=0,h,k,l;f.forEach(function(c){h=c[0]>>e;k=c[1]>>e;l=c[2]>>e;h<a?a=h:h>b&&(b=h);k<m?m=k:k>d&&(d=k);l<q?q=l:l>n&&(n=l)});return new w(a,b,m,d,q,n,c)}function n(a,c){function b(a){var f=a+"1";a+="2";var v,d,m,e;d=0;for(k=c[f];k<=c[a];k++)if(y[k]>n/2){m=c.copy();e=c.copy();v=k-c[f];d=c[a]-k;for(v=v<=d?Math.min(c[a]-1,~~(k+d/2)):Math.max(c[f],~~(k-1-v/2));!y[v];)v++;for(d=s[v];!d&&y[v-1];)d=s[--v];m[a]=v;e[f]=m[a]+1;return[m,e]}}if(c.count()){var d=c.r2-
c.r1+1,m=c.g2-c.g1+1,e=l.max([d,m,c.b2-c.b1+1]);if(1==c.count())return[c.copy()];var n=0,y=[],s=[],k,g,t,u,p;if(e==d)for(k=c.r1;k<=c.r2;k++){u=0;for(g=c.g1;g<=c.g2;g++)for(t=c.b1;t<=c.b2;t++)p=h(k,g,t),u+=a[p]||0;n+=u;y[k]=n}else if(e==m)for(k=c.g1;k<=c.g2;k++){u=0;for(g=c.r1;g<=c.r2;g++)for(t=c.b1;t<=c.b2;t++)p=h(g,k,t),u+=a[p]||0;n+=u;y[k]=n}else for(k=c.b1;k<=c.b2;k++){u=0;for(g=c.r1;g<=c.r2;g++)for(t=c.g1;t<=c.g2;t++)p=h(g,t,k),u+=a[p]||0;n+=u;y[k]=n}y.forEach(function(a,c){s[c]=n-a});return e==
d?b("r"):e==m?b("g"):b("b")}}var d=5,e=8-d;w.prototype={volume:function(a){if(!this._volume||a)this._volume=(this.r2-this.r1+1)*(this.g2-this.g1+1)*(this.b2-this.b1+1);return this._volume},count:function(a){var c=this.histo;if(!this._count_set||a){a=0;var b,d,n;for(b=this.r1;b<=this.r2;b++)for(d=this.g1;d<=this.g2;d++)for(n=this.b1;n<=this.b2;n++)index=h(b,d,n),a+=c[index]||0;this._count=a;this._count_set=!0}return this._count},copy:function(){return new w(this.r1,this.r2,this.g1,this.g2,this.b1,
this.b2,this.histo)},avg:function(a){var c=this.histo;if(!this._avg||a){a=0;var b=1<<8-d,n=0,e=0,g=0,q,l,s,k;for(l=this.r1;l<=this.r2;l++)for(s=this.g1;s<=this.g2;s++)for(k=this.b1;k<=this.b2;k++)q=h(l,s,k),q=c[q]||0,a+=q,n+=q*(l+0.5)*b,e+=q*(s+0.5)*b,g+=q*(k+0.5)*b;this._avg=a?[~~(n/a),~~(e/a),~~(g/a)]:[~~(b*(this.r1+this.r2+1)/2),~~(b*(this.g1+this.g2+1)/2),~~(b*(this.b1+this.b2+1)/2)]}return this._avg},contains:function(a){var c=a[0]>>e;gval=a[1]>>e;bval=a[2]>>e;return c>=this.r1&&c<=this.r2&&
gval>=this.g1&&gval<=this.g2&&bval>=this.b1&&bval<=this.b2}};p.prototype={push:function(a){this.vboxes.push({vbox:a,color:a.avg()})},palette:function(){return this.vboxes.map(function(a){return a.color})},size:function(){return this.vboxes.size()},map:function(a){for(var c=this.vboxes,b=0;b<c.size();b++)if(c.peek(b).vbox.contains(a))return c.peek(b).color;return this.nearest(a)},nearest:function(a){for(var c=this.vboxes,b,n,d,e=0;e<c.size();e++)if(n=Math.sqrt(Math.pow(a[0]-c.peek(e).color[0],2)+Math.pow(a[1]-
c.peek(e).color[1],2)+Math.pow(a[2]-c.peek(e).color[2],2)),n<b||void 0===b)b=n,d=c.peek(e).color;return d},forcebw:function(){var a=this.vboxes;a.sort(function(a,b){return l.naturalOrder(l.sum(a.color),l.sum(b.color))});var b=a[0].color;5>b[0]&&5>b[1]&&5>b[2]&&(a[0].color=[0,0,0]);var b=a.length-1,n=a[b].color;251<n[0]&&251<n[1]&&251<n[2]&&(a[b].color=[255,255,255])}};return{quantize:function(d,c){function e(a,b){for(var c=1,d=0,f;1E3>d;)if(f=a.pop(),f.count()){var m=n(h,f);f=m[0];m=m[1];if(!f)break;
a.push(f);m&&(a.push(m),c++);if(c>=b)break;if(1E3<d++)break}else a.push(f),d++}if(!d.length||2>c||256<c)return!1;var h=b(d),m=0;h.forEach(function(){m++});var r=a(d,h),q=new g(function(a,b){return l.naturalOrder(a.count(),b.count())});q.push(r);e(q,0.75*c);for(r=new g(function(a,b){return l.naturalOrder(a.count()*a.volume(),b.count()*b.volume())});q.size();)r.push(q.pop());e(r,c-r.size());for(q=new p;r.size();)q.push(r.pop());return q}}}();x.exports=A.quantize},{}],2:[function(A,x,z){(function(){var l,
h,g,w=function(b,a){return function(){return b.apply(a,arguments)}},p=[].slice;window.Swatch=h=function(){function b(a,b){this.rgb=a;this.population=b}b.prototype.hsl=void 0;b.prototype.rgb=void 0;b.prototype.population=1;b.yiq=0;b.prototype.getHsl=function(){return this.hsl?this.hsl:this.hsl=g.rgbToHsl(this.rgb[0],this.rgb[1],this.rgb[2])};b.prototype.getPopulation=function(){return this.population};b.prototype.getRgb=function(){return this.rgb};b.prototype.getHex=function(){return"#"+(16777216+
(this.rgb[0]<<16)+(this.rgb[1]<<8)+this.rgb[2]).toString(16).slice(1,7)};b.prototype.getTitleTextColor=function(){this._ensureTextColors();return 200>this.yiq?"#fff":"#000"};b.prototype.getBodyTextColor=function(){this._ensureTextColors();return 150>this.yiq?"#fff":"#000"};b.prototype._ensureTextColors=function(){if(!this.yiq)return this.yiq=(299*this.rgb[0]+587*this.rgb[1]+114*this.rgb[2])/1E3};return b}();window.Vibrant=g=function(){function b(a,b,d){this.swatches=w(this.swatches,this);var e,f,
c,g,p,m,r,q;"undefined"===typeof b&&(b=64);"undefined"===typeof d&&(d=5);p=new l(a);r=p.getImageData().data;m=p.getPixelCount();a=[];for(g=0;g<m;)e=4*g,q=r[e+0],c=r[e+1],f=r[e+2],e=r[e+3],125<=e&&(250<q&&250<c&&250<f||a.push([q,c,f])),g+=d;this._swatches=this.quantize(a,b).vboxes.map(function(a){return function(a){return new h(a.color,a.vbox.count())}}(this));this.maxPopulation=this.findMaxPopulation;this.generateVarationColors();this.generateEmptySwatches();p.removeCanvas()}b.prototype.quantize=
A("quantize");b.prototype._swatches=[];b.prototype.TARGET_DARK_LUMA=0.26;b.prototype.MAX_DARK_LUMA=0.45;b.prototype.MIN_LIGHT_LUMA=0.55;b.prototype.TARGET_LIGHT_LUMA=0.74;b.prototype.MIN_NORMAL_LUMA=0.3;b.prototype.TARGET_NORMAL_LUMA=0.5;b.prototype.MAX_NORMAL_LUMA=0.7;b.prototype.TARGET_MUTED_SATURATION=0.3;b.prototype.MAX_MUTED_SATURATION=0.4;b.prototype.TARGET_VIBRANT_SATURATION=1;b.prototype.MIN_VIBRANT_SATURATION=0.35;b.prototype.WEIGHT_SATURATION=3;b.prototype.WEIGHT_LUMA=6;b.prototype.WEIGHT_POPULATION=
1;b.prototype.VibrantSwatch=void 0;b.prototype.MutedSwatch=void 0;b.prototype.DarkVibrantSwatch=void 0;b.prototype.DarkMutedSwatch=void 0;b.prototype.LightVibrantSwatch=void 0;b.prototype.LightMutedSwatch=void 0;b.prototype.HighestPopulation=0;b.prototype.generateVarationColors=function(){this.VibrantSwatch=this.findColorVariation(this.TARGET_NORMAL_LUMA,this.MIN_NORMAL_LUMA,this.MAX_NORMAL_LUMA,this.TARGET_VIBRANT_SATURATION,this.MIN_VIBRANT_SATURATION,1);this.LightVibrantSwatch=this.findColorVariation(this.TARGET_LIGHT_LUMA,
this.MIN_LIGHT_LUMA,1,this.TARGET_VIBRANT_SATURATION,this.MIN_VIBRANT_SATURATION,1);this.DarkVibrantSwatch=this.findColorVariation(this.TARGET_DARK_LUMA,0,this.MAX_DARK_LUMA,this.TARGET_VIBRANT_SATURATION,this.MIN_VIBRANT_SATURATION,1);this.MutedSwatch=this.findColorVariation(this.TARGET_NORMAL_LUMA,this.MIN_NORMAL_LUMA,this.MAX_NORMAL_LUMA,this.TARGET_MUTED_SATURATION,0,this.MAX_MUTED_SATURATION);this.LightMutedSwatch=this.findColorVariation(this.TARGET_LIGHT_LUMA,this.MIN_LIGHT_LUMA,1,this.TARGET_MUTED_SATURATION,
0,this.MAX_MUTED_SATURATION);return this.DarkMutedSwatch=this.findColorVariation(this.TARGET_DARK_LUMA,0,this.MAX_DARK_LUMA,this.TARGET_MUTED_SATURATION,0,this.MAX_MUTED_SATURATION)};b.prototype.generateEmptySwatches=function(){var a;void 0===this.VibrantSwatch&&void 0!==this.DarkVibrantSwatch&&(a=this.DarkVibrantSwatch.getHsl(),a[2]=this.TARGET_NORMAL_LUMA,this.VibrantSwatch=new h(b.hslToRgb(a[0],a[1],a[2]),0));if(void 0===this.DarkVibrantSwatch&&void 0!==this.VibrantSwatch)return a=this.VibrantSwatch.getHsl(),
a[2]=this.TARGET_DARK_LUMA,this.DarkVibrantSwatch=new h(b.hslToRgb(a[0],a[1],a[2]),0)};b.prototype.findMaxPopulation=function(){var a,b,d,e,f;d=0;e=this._swatches;a=0;for(b=e.length;a<b;a++)f=e[a],d=Math.max(d,f.getPopulation());return d};b.prototype.findColorVariation=function(a,b,d,e,f,c){var g,h,m,l,q,p,s,k;l=void 0;q=0;p=this._swatches;g=0;for(h=p.length;g<h;g++)if(k=p[g],s=k.getHsl()[1],m=k.getHsl()[2],s>=f&&s<=c&&m>=b&&m<=d&&!this.isAlreadySelected(k)&&(m=this.createComparisonValue(s,e,m,a,
k.getPopulation(),this.HighestPopulation),void 0===l||m>q))l=k,q=m;return l};b.prototype.createComparisonValue=function(a,b,d,e,f,c){return this.weightedMean(this.invertDiff(a,b),this.WEIGHT_SATURATION,this.invertDiff(d,e),this.WEIGHT_LUMA,f/c,this.WEIGHT_POPULATION)};b.prototype.invertDiff=function(a,b){return 1-Math.abs(a-b)};b.prototype.weightedMean=function(){var a,b,d,e,f,c;f=1<=arguments.length?p.call(arguments,0):[];for(a=d=b=0;a<f.length;)e=f[a],c=f[a+1],b+=e*c,d+=c,a+=2;return b/d};b.prototype.swatches=
function(){return{Vibrant:this.VibrantSwatch,Muted:this.MutedSwatch,DarkVibrant:this.DarkVibrantSwatch,DarkMuted:this.DarkMutedSwatch,LightVibrant:this.LightVibrantSwatch,LightMuted:this.LightMuted}};b.prototype.isAlreadySelected=function(a){return this.VibrantSwatch===a||this.DarkVibrantSwatch===a||this.LightVibrantSwatch===a||this.MutedSwatch===a||this.DarkMutedSwatch===a||this.LightMutedSwatch===a};b.rgbToHsl=function(a,b,d){var e,f,c,g,h;a/=255;b/=255;d/=255;g=Math.max(a,b,d);h=Math.min(a,b,d);
f=void 0;c=(g+h)/2;if(g===h)f=h=0;else{e=g-h;h=0.5<c?e/(2-g-h):e/(g+h);switch(g){case a:f=(b-d)/e+(b<d?6:0);break;case b:f=(d-a)/e+2;break;case d:f=(a-b)/e+4}f/=6}return[f,h,c]};b.hslToRgb=function(a,b,d){var e,f,c;e=f=c=void 0;e=function(a,b,c){0>c&&(c+=1);1<c&&(c-=1);return c<1/6?a+6*(b-a)*c:0.5>c?b:c<2/3?a+(b-a)*(2/3-c)*6:a};0===b?c=f=e=d:(b=0.5>d?d*(1+b):d+b-d*b,d=2*d-b,c=e(d,b,a+1/3),f=e(d,b,a),e=e(d,b,a-1/3));return[255*c,255*f,255*e]};return b}();window.CanvasImage=l=function(){function b(a){this.canvas=
document.createElement("canvas");this.context=this.canvas.getContext("2d");document.body.appendChild(this.canvas);this.width=this.canvas.width=a.width;this.height=this.canvas.height=a.height;this.context.drawImage(a,0,0,this.width,this.height)}b.prototype.clear=function(){return this.context.clearRect(0,0,this.width,this.height)};b.prototype.update=function(a){return this.context.putImageData(a,0,0)};b.prototype.getPixelCount=function(){return this.width*this.height};b.prototype.getImageData=function(){return this.context.getImageData(0,
0,this.width,this.height)};b.prototype.removeCanvas=function(){return this.canvas.parentNode.removeChild(this.canvas)};return b}()}).call(this)},{quantize:1}]},{},[2]);

View file

@ -1,453 +0,0 @@
let current = "2.6.0";
/* Config settings */
DribbblishShared.config.register({
area: "Animations & Transitions",
type: "slider",
key: "fadeDuration",
name: "Color Fade Duration",
description: "Select the duration of the color fading transition",
defaultValue: 0.5,
data: {
min: 0,
max: 10,
step: 0.1,
suffix: "s"
},
onChange: (val) => document.documentElement.style.setProperty("--song-transition-speed", val + "s")
});
// waitForElement because Spicetify is not initialized at startup
waitForElement(["#main"], () => {
DribbblishShared.config.register({
area: { name: "About", order: 999 },
type: "button",
key: "aboutDribbblish",
name: "Info",
description: `
OS: ${capitalizeFirstLetter(Spicetify.Platform.PlatformData.os_name)} v${Spicetify.Platform.PlatformData.os_version}
Spotify: v${Spicetify.Platform.PlatformData.event_sender_context_information?.client_version_string ?? Spicetify.Platform.PlatformData.client_version_triple}
Dribbblish: v${current}
`,
data: "Copy",
onChange: (val) => {
copyToClipboard(DribbblishShared.config.getOptions("aboutDribbblish").description);
Spicetify.showNotification("Copied Versions");
}
});
});
function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
function copyToClipboard(text) {
var input = document.createElement("textarea");
input.style.display = "fixed";
input.innerHTML = text;
document.body.appendChild(input);
input.select();
var result = document.execCommand("copy");
document.body.removeChild(input);
return result;
}
/* js */
function getAlbumInfo(uri) {
return Spicetify.CosmosAsync.get(`hm://album/v1/album-app/album/${uri}/desktop`);
}
function isLight(hex) {
var [r, g, b] = hexToRgb(hex).map(Number);
const brightness = (r * 299 + g * 587 + b * 114) / 1000;
return brightness > 128;
}
function hexToRgb(hex) {
var bigint = parseInt(hex.replace("#", ""), 16);
var r = (bigint >> 16) & 255;
var g = (bigint >> 8) & 255;
var b = bigint & 255;
return [r, g, b];
}
function rgbToHex([r, g, b]) {
const rgb = (r << 16) | (g << 8) | (b << 0);
return "#" + (0x1000000 + rgb).toString(16).slice(1);
}
const LightenDarkenColor = (h, p) =>
"#" +
[1, 3, 5]
.map((s) => parseInt(h.substr(s, 2), 16))
.map((c) => parseInt((c * (100 + p)) / 100))
.map((c) => (c < 255 ? c : 255))
.map((c) => c.toString(16).padStart(2, "0"))
.join("");
function rgbToHsl([r, g, b]) {
(r /= 255), (g /= 255), (b /= 255);
var max = Math.max(r, g, b),
min = Math.min(r, g, b);
var h,
s,
l = (max + min) / 2;
if (max == min) {
h = s = 0; // achromatic
} else {
var d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0);
break;
case g:
h = (b - r) / d + 2;
break;
case b:
h = (r - g) / d + 4;
break;
}
h /= 6;
}
return [h, s, l];
}
function hslToRgb([h, s, l]) {
var r, g, b;
if (s == 0) {
r = g = b = l; // achromatic
} else {
function hue2rgb(p, q, t) {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
}
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return [r * 255, g * 255, b * 255];
}
function setLightness(hex, lightness) {
hsl = rgbToHsl(hexToRgb(hex));
hsl[2] = lightness;
return rgbToHex(hslToRgb(hsl));
}
function parseComputedStyleColor(col) {
if (col.startsWith("#")) return col;
if (col.startsWith("rgb("))
return rgbToHex(
col
.replace(/rgb|(|)/g, "")
.split(",")
.map((part) => Number(part.trim()))
);
}
// `parseComputedStyleColor()` beacuse "--spice-sidebar" is `rgb()`
let textColor = parseComputedStyleColor(getComputedStyle(document.documentElement).getPropertyValue("--spice-text"));
let textColorBg = parseComputedStyleColor(getComputedStyle(document.documentElement).getPropertyValue("--spice-main"));
let sidebarColor = parseComputedStyleColor(getComputedStyle(document.documentElement).getPropertyValue("--spice-sidebar"));
function setRootColor(name, colHex) {
let root = document.documentElement;
if (root === null) return;
root.style.setProperty("--spice-" + name, colHex);
root.style.setProperty("--spice-rgb-" + name, hexToRgb(colHex).join(","));
}
function toggleDark(setDark) {
if (setDark === undefined) setDark = isLight(textColorBg);
document.documentElement.style.setProperty("--is_light", setDark ? 0 : 1);
textColorBg = setDark ? "#0A0A0A" : "#FAFAFA";
setRootColor("main", textColorBg);
setRootColor("player", textColorBg);
setRootColor("card", setDark ? "#040404" : "#ECECEC");
setRootColor("subtext", setDark ? "#EAEAEA" : "#3D3D3D");
setRootColor("notification", setDark ? "#303030" : "#DDDDDD");
updateColors(textColor, sidebarColor, false);
}
function checkDarkLightMode(colors) {
const theme = DribbblishShared.config.get("theme");
if (theme == 2) {
// Based on Time
const start = 60 * parseInt(DribbblishShared.config.get("darkModeOnTime").split(":")[0]) + parseInt(DribbblishShared.config.get("darkModeOnTime").split(":")[1]);
const end = 60 * parseInt(DribbblishShared.config.get("darkModeOffTime").split(":")[0]) + parseInt(DribbblishShared.config.get("darkModeOffTime").split(":")[1]);
const now = new Date();
const time = 60 * now.getHours() + now.getMinutes();
if (end < start) dark = start <= time || time < end;
else dark = start <= time && time < end;
toggleDark(dark);
} else if (theme == 3) {
// Based on Color
if (colors && colors.length > 0) toggleDark(isLight(colors[0]));
}
}
// Run every Minute to check time and set dark / light mode
setInterval(checkDarkLightMode, 60000);
DribbblishShared.config.register({
area: "Theme",
type: "checkbox",
key: "dynamicColors",
name: "Dynamic",
description: "If the Theme's Color should be extracted from Albumart",
defaultValue: true,
onChange: (val) => updateColors(),
showChildren: (val) => !val,
children: [
{
type: "color",
key: "colorOverride",
name: "Color",
description: "The Color of the Theme",
defaultValue: "#1ed760",
fireInitialChange: false,
onChange: (val) => updateColors()
}
]
});
DribbblishShared.config.register({
area: "Theme",
type: "select",
data: ["Dark", "Light", "Based on Time", "Based on Color"],
key: "theme",
name: "Theme",
description: "Select Dark / Bright mode",
defaultValue: 0,
showChildren: (val) => {
if (val == 2) return ["darkModeOnTime", "darkModeOffTime"];
//if (val == 3) return [""];
return false;
},
onChange: (val) => {
switch (val) {
case 0:
toggleDark(true);
break;
case 1:
toggleDark(false);
break;
case 2:
checkDarkLightMode();
break;
case 3:
checkDarkLightMode();
break;
}
},
children: [
{
type: "time",
key: "darkModeOnTime",
name: "Dark Mode On Time",
description: "Beginning of Dark mode time",
defaultValue: "20:00",
fireInitialChange: false,
onChange: checkDarkLightMode
},
{
type: "time",
key: "darkModeOffTime",
name: "Dark Mode Off Time",
description: "End of Dark mode time",
defaultValue: "06:00",
fireInitialChange: false,
onChange: checkDarkLightMode
}
]
});
var currentColor;
var currentSideColor;
function updateColors(textColHex, sideColHex, checkDarkMode = true) {
if (textColHex && sideColHex) {
currentColor = textColHex;
currentSideColor = sideColHex;
} else {
if (!(currentColor && currentSideColor)) return; // If `updateColors()` is called early these vars are undefined and would break
textColHex = currentColor;
sideColHex = currentSideColor;
}
if (!DribbblishShared.config.get("dynamicColors")) {
const col = DribbblishShared.config.get("colorOverride");
textColHex = col;
sideColHex = col;
}
let isLightBg = isLight(textColorBg);
if (isLightBg) textColHex = LightenDarkenColor(textColHex, -15); // vibrant color is always too bright for white bg mode
let darkColHex = LightenDarkenColor(textColHex, isLightBg ? 12 : -20);
let darkerColHex = LightenDarkenColor(textColHex, isLightBg ? 30 : -40);
let buttonBgColHex = setLightness(textColHex, isLightBg ? 0.9 : 0.14);
setRootColor("text", textColHex);
setRootColor("button", darkerColHex);
setRootColor("button-active", darkColHex);
setRootColor("selected-row", darkerColHex);
setRootColor("tab-active", buttonBgColHex);
setRootColor("button-disabled", buttonBgColHex);
setRootColor("sidebar", sideColHex);
if (checkDarkMode) checkDarkLightMode([textColHex, sideColHex]);
}
let nearArtistSpanText = "";
let coverListenerInstalled = false;
async function songchange() {
try {
// warning popup
if (Spicetify.Platform.PlatformData.client_version_triple < "1.1.68") Spicetify.showNotification(`Your version of Spotify ${Spicetify.Platform.PlatformData.client_version_triple}) is un-supported`);
} catch (err) {
console.error(err);
}
let album_uri = Spicetify.Player.data.track.metadata.album_uri;
let bgImage = Spicetify.Player.data.track.metadata.image_url;
if (bgImage === undefined) {
bgImage = "/images/tracklist-row-song-fallback.svg";
textColor = "#509bf5";
updateColors(textColor, textColor);
coverListenerInstalled = false;
}
if (!coverListenerInstalled) hookCoverChange(true);
if (album_uri !== undefined && !album_uri.includes("spotify:show")) {
const albumInfo = await getAlbumInfo(album_uri.replace("spotify:album:", ""));
let album_date = new Date(albumInfo.year, (albumInfo.month || 1) - 1, albumInfo.day || 0);
let recent_date = new Date();
recent_date.setMonth(recent_date.getMonth() - 6);
album_date = album_date.toLocaleString("default", album_date > recent_date ? { year: "numeric", month: "short" } : { year: "numeric" });
album_link = '<a title="' + Spicetify.Player.data.track.metadata.album_title + '" href="' + album_uri + '" data-uri="' + album_uri + '" data-interaction-target="album-name" class="tl-cell__content">' + Spicetify.Player.data.track.metadata.album_title + "</a>";
nearArtistSpanText = album_link + " • " + album_date;
} else if (Spicetify.Player.data.track.uri.includes("spotify:episode")) {
// podcast
bgImage = bgImage.replace("spotify:image:", "https://i.scdn.co/image/");
nearArtistSpanText = Spicetify.Player.data.track.metadata.album_title;
} else if (Spicetify.Player.data.track.metadata.is_local == "true") {
// local file
nearArtistSpanText = Spicetify.Player.data.track.metadata.album_title;
} else if (Spicetify.Player.data.track.provider == "ad") {
// ad
nearArtistSpanText = "advertisement";
coverListenerInstalled = false;
return;
} else {
// When clicking a song from the homepage, songChange is fired with half empty metadata
// todo: retry only once?
setTimeout(songchange, 200);
}
if (document.querySelector("#main-trackInfo-year") === null) {
waitForElement([".main-trackInfo-container"], (queries) => {
nearArtistSpan = document.createElement("div");
nearArtistSpan.id = "main-trackInfo-year";
nearArtistSpan.classList.add("main-trackInfo-artists", "ellipsis-one-line", "main-type-finale");
nearArtistSpan.innerHTML = nearArtistSpanText;
queries[0].append(nearArtistSpan);
});
} else {
nearArtistSpan.innerHTML = nearArtistSpanText;
}
document.documentElement.style.setProperty("--image_url", 'url("' + bgImage + '")');
}
Spicetify.Player.addEventListener("songchange", songchange);
function pickCoverColor(img) {
if (!img.currentSrc.startsWith("spotify:")) return;
var swatches = new Vibrant(img, 5).swatches();
lightCols = ["Vibrant", "DarkVibrant", "Muted", "LightVibrant"];
darkCols = ["Vibrant", "LightVibrant", "Muted", "DarkVibrant"];
mainCols = isLight(textColorBg) ? lightCols : darkCols;
textColor = "#509bf5";
for (var col in mainCols)
if (swatches[mainCols[col]]) {
textColor = swatches[mainCols[col]].getHex();
break;
}
sidebarColor = "#509bf5";
for (var col in lightCols)
if (swatches[lightCols[col]]) {
sidebarColor = swatches[lightCols[col]].getHex();
break;
}
updateColors(textColor, sidebarColor);
}
waitForElement([".main-nowPlayingBar-left"], (queries) => {
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (mutation.removedNodes.length > 0) coverListenerInstalled = false;
});
});
observer.observe(queries[0], { childList: true });
});
function hookCoverChange(pick) {
waitForElement([".cover-art-image"], (queries) => {
coverListenerInstalled = true;
var elem = queries.slice(-1)[0];
if (pick && elem.complete && elem.naturalHeight !== 0) pickCoverColor(elem);
elem.addEventListener("load", function () {
try {
pickCoverColor(elem);
} catch (error) {
console.error(error);
setTimeout(pickCoverColor, 300, elem);
}
});
});
}
(function Startup() {
if (!Spicetify.showNotification) {
setTimeout(Startup, 300);
return;
}
// Check latest release
fetch("https://api.github.com/repos/JulienMaille/dribbblish-dynamic-theme/releases/latest")
.then((response) => {
return response.json();
})
.then((data) => {
if (data.tag_name > current) {
upd = document.createElement("div");
upd.innerText = `Theme UPD v${data.tag_name} avail.`;
upd.classList.add("ellipsis-one-line", "main-type-finale");
upd.setAttribute("title", `Changes: ${data.name}`);
upd.style.setProperty("color", "var(--spice-button-active)");
document.querySelector(".main-userWidget-box").append(upd);
document.querySelector(".main-userWidget-box").classList.add("update-avail");
new Spicetify.Menu.Item("Update Dribbblish", false, () => window.open("https://github.com/JulienMaille/dribbblish-dynamic-theme/blob/main/README.md#install--update", "_blank")).register();
}
})
.catch((err) => {
// Do something for an error here
console.error(err);
});
})();
document.documentElement.style.setProperty("--warning_message", " ");

View file

@ -45,6 +45,7 @@ if ($PSVersionTable.PSVersion.Major -gt $PSMinVersion) {
Write-Done Write-Done
$version = ($latest_release_json | ConvertFrom-Json).tag_name -replace "v", "" $version = ($latest_release_json | ConvertFrom-Json).tag_name -replace "v", ""
$download_uri = ($latest_release_json | ConvertFrom-Json).assets[0].browser_download_url
} }
# Check ~\spicetify-cli\Themes directory already exists # Check ~\spicetify-cli\Themes directory already exists
@ -57,7 +58,6 @@ if ($PSVersionTable.PSVersion.Major -gt $PSMinVersion) {
# Download release. # Download release.
$zip_file = "${sp_dir}\${version}.zip" $zip_file = "${sp_dir}\${version}.zip"
$download_uri = "https://github.com/JulienMaille/dribbblish-dynamic-theme/archive/refs/tags/${version}.zip"
Write-Part "DOWNLOADING "; Write-Emphasized $download_uri Write-Part "DOWNLOADING "; Write-Emphasized $download_uri
Invoke-WebRequest -Uri $download_uri -UseBasicParsing -OutFile $zip_file Invoke-WebRequest -Uri $download_uri -UseBasicParsing -OutFile $zip_file
Write-Done Write-Done
@ -65,7 +65,7 @@ if ($PSVersionTable.PSVersion.Major -gt $PSMinVersion) {
# Extract theme from .zip file. # Extract theme from .zip file.
Write-Part "EXTRACTING "; Write-Emphasized $zip_file Write-Part "EXTRACTING "; Write-Emphasized $zip_file
Write-Part " into "; Write-Emphasized ${sp_dir}; Write-Part " into "; Write-Emphasized ${sp_dir};
Expand-Archive -Path $zip_file -DestinationPath $sp_dir -Force Expand-Archive -Path $zip_file -DestinationPath "${sp_dir}\dribbblish-dynamic-theme-${version}" -Force
Write-Done Write-Done
# Remove .zip file. # Remove .zip file.
@ -90,11 +90,9 @@ if ($PSVersionTable.PSVersion.Major -gt $PSMinVersion) {
# Installing. # Installing.
Write-Part "INSTALLING"; Write-Part "INSTALLING";
cd $sp_dot_dir cd $sp_dot_dir
Copy-Item dribbblish.js ..\..\Extensions
Copy-Item dribbblish-dynamic.js ..\..\Extensions Copy-Item dribbblish-dynamic.js ..\..\Extensions
Copy-Item Vibrant.min.js ..\..\Extensions spicetify config extensions default-dynamic.js- extensions dribbblish-dynamic.js- extensions dribbblish.js- extensions Vibrant.min.js-
spicetify config extensions default-dynamic.js- spicetify config extensions dribbblish-dynamic.js
spicetify config extensions dribbblish.js extensions dribbblish-dynamic.js extensions Vibrant.min.js
spicetify config current_theme DribbblishDynamic spicetify config current_theme DribbblishDynamic
spicetify config color_scheme base spicetify config color_scheme base
spicetify config inject_css 1 replace_colors 1 overwrite_assets 1 spicetify config inject_css 1 replace_colors 1 overwrite_assets 1

View file

@ -10,13 +10,14 @@ if [ $# -eq 0 ]; then
version=$( command curl -sSf "$latest_release_uri" | version=$( command curl -sSf "$latest_release_uri" |
command grep -Eo "tag_name\": .*" | command grep -Eo "tag_name\": .*" |
command grep -Eo "[0-9.]+" ) command grep -Eo "[0-9.]+" )
download_uri=$( command curl -sSf "$latest_release_uri" |
command grep -Eo "browser_download_url\": .*" |
command grep -Eo "http.*?\.zip" )
if [ ! "$version" ]; then exit 1; fi if [ ! "$version" ]; then exit 1; fi
else else
version="${1}" version="${1}"
fi fi
download_uri="https://github.com/JulienMaille/dribbblish-dynamic-theme/archive/refs/tags/${version}.zip"
spicetify_install="${SPICETIFY_INSTALL:-$HOME/spicetify-cli/Themes}" spicetify_install="${SPICETIFY_INSTALL:-$HOME/spicetify-cli/Themes}"
if [ ! -d "$spicetify_install" ]; then if [ ! -d "$spicetify_install" ]; then
@ -31,7 +32,7 @@ curl --fail --location --progress-bar --output "$tar_file" "$download_uri"
cd "$spicetify_install" cd "$spicetify_install"
echo "EXTRACTING $tar_file" echo "EXTRACTING $tar_file"
unzip -o "$tar_file" unzip -d "$spicetify_install/dribbblish-dynamic-theme-${version}" -o "$tar_file"
echo "REMOVING $tar_file" echo "REMOVING $tar_file"
rm "$tar_file" rm "$tar_file"
@ -49,10 +50,9 @@ cp -rf "$spicetify_install/dribbblish-dynamic-theme-${version}/." "$sp_dot_dir"
echo "INSTALLING" echo "INSTALLING"
cd "$(dirname "$(spicetify -c)")/Themes/DribbblishDynamic" cd "$(dirname "$(spicetify -c)")/Themes/DribbblishDynamic"
mkdir -p ../../Extensions mkdir -p ../../Extensions
cp dribbblish.js ../../Extensions/.
cp dribbblish-dynamic.js ../../Extensions/. cp dribbblish-dynamic.js ../../Extensions/.
cp Vibrant.min.js ../../Extensions/. spicetify config extensions default-dynamic.js- extensions dribbblish-dynamic.js- extensions dribbblish.js- extensions Vibrant.min.js-
spicetify config extensions dribbblish.js extensions dribbblish-dynamic.js extensions Vibrant.min.js spicetify config extensions dribbblish-dynamic.js
spicetify config current_theme DribbblishDynamic spicetify config current_theme DribbblishDynamic
spicetify config color_scheme base spicetify config color_scheme base
spicetify config inject_css 1 replace_colors 1 overwrite_assets 1 spicetify config inject_css 1 replace_colors 1 overwrite_assets 1

19
package.json Normal file
View file

@ -0,0 +1,19 @@
{
"devDependencies": {
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^9.0.1",
"css-minimizer-webpack-plugin": "^3.1.1",
"node-sass": "^6.0.1",
"sass-loader": "^12.2.0",
"webpack": "^5.58.2",
"webpack-cli": "^4.9.0"
},
"scripts": {
"build": "webpack --mode=development"
},
"dependencies": {
"chroma-js": "^2.1.2",
"jquery": "^3.6.0",
"node-vibrant": "3.1.4"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 783 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

View file

@ -1,162 +1,162 @@
[base] [base]
text = FFFFFF text = FFFFFF
subtext = F0F0F0 subtext = F0F0F0
sidebar-text = FFFFFF sidebar-text = FFFFFF
main = 000000 main = 000000
sidebar = 1ed760 sidebar = 1ed760
player = 000000 player = 000000
card = 000000 card = 000000
shadow = 202020 shadow = 202020
selected-row = 797979 selected-row = 797979
button = 1ed760 button = 1ed760
button-active = 1ed760 button-active = 1ed760
button-disabled = 535353 button-disabled = 535353
tab-active = 166632 tab-active = 166632
notification = 1db954 notification = 1db954
notification-error = e22134 notification-error = e22134
misc = BFBFBF misc = BFBFBF
[white] [white]
text = 363636 text = 363636
subtext = 3D3D3D subtext = 3D3D3D
sidebar-text = FFF9F4 sidebar-text = FFF9F4
main = FFF9F4 main = FFF9F4
sidebar = FFA789 sidebar = FFA789
player = FFF9F4 player = FFF9F4
card = FFF9F4 card = FFF9F4
shadow = d3d3d3 shadow = d3d3d3
selected-row = 6D6D6D selected-row = 6D6D6D
button = ff8367 button = ff8367
button-active = ff8367 button-active = ff8367
button-disabled = 535353 button-disabled = 535353
tab-active = ffdace tab-active = ffdace
notification = FFA789 notification = FFA789
notification-error = e22134 notification-error = e22134
misc = BFBFBF misc = BFBFBF
[dark] [dark]
text = F0F0F0 text = F0F0F0
subtext = F0F0F0 subtext = F0F0F0
sidebar-text = 0a0e14 sidebar-text = 0a0e14
main = 0a0e14 main = 0a0e14
sidebar = C2D935 sidebar = C2D935
player = 0a0e14 player = 0a0e14
card = 0a0e14 card = 0a0e14
shadow = 202020 shadow = 202020
selected-row = DEDEDE selected-row = DEDEDE
button = C2D935 button = C2D935
button-active = C2D935 button-active = C2D935
button-disabled = 535353 button-disabled = 535353
tab-active = 727d2b tab-active = 727d2b
notification = C2D935 notification = C2D935
notification-error = e22134 notification-error = e22134
misc = BFBFBF misc = BFBFBF
[dracula] [dracula]
text = f8f8f2 text = f8f8f2
subtext = f8f8f2 subtext = f8f8f2
sidebar-text = F0F0F0 sidebar-text = F0F0F0
main = 44475a main = 44475a
sidebar = 6272a4 sidebar = 6272a4
player = 44475a player = 44475a
card = 6272a4 card = 6272a4
shadow = 000000 shadow = 000000
selected-row = bd93f9 selected-row = bd93f9
button = ffb86c button = ffb86c
button-active = 8be9fd button-active = 8be9fd
button-disabled = 535353 button-disabled = 535353
tab-active = 6272a4 tab-active = 6272a4
notification = bd93f9 notification = bd93f9
notification-error = e22134 notification-error = e22134
misc = BFBFBF misc = BFBFBF
[nord-light] [nord-light]
text = 2e3440 text = 2e3440
subtext = 3b4252 subtext = 3b4252
sidebar-text = ECEFF4 sidebar-text = ECEFF4
main = ECEFF4 main = ECEFF4
sidebar = 5E81AC sidebar = 5E81AC
player = ECEFF4 player = ECEFF4
card = ebcb8b card = ebcb8b
shadow = eceff4 shadow = eceff4
selected-row = 4c566a selected-row = 4c566a
button = 81a1c1 button = 81a1c1
button-active = 81a1c1 button-active = 81a1c1
button-disabled = c0c0c0 button-disabled = c0c0c0
tab-active = ebcb8b tab-active = ebcb8b
notification = a3be8c notification = a3be8c
notification-error = bf616a notification-error = bf616a
misc = BFBFBF misc = BFBFBF
[nord-dark] [nord-dark]
text = ECEFF4 text = ECEFF4
subtext = E5E9F0 subtext = E5E9F0
sidebar-text = 434c5e sidebar-text = 434c5e
main = 2e3440 main = 2e3440
sidebar = 88C0D0 sidebar = 88C0D0
player = 2e3440 player = 2e3440
card = 2e3440 card = 2e3440
shadow = 2E3440 shadow = 2E3440
selected-row = D8DEE9 selected-row = D8DEE9
button = 81A1C1 button = 81A1C1
button-active = 81A1C1 button-active = 81A1C1
button-disabled = 434C5E button-disabled = 434C5E
tab-active = 434C5E tab-active = 434C5E
notification = A3BE8C notification = A3BE8C
notification-error = BF616A notification-error = BF616A
misc = BFBFBF misc = BFBFBF
[purple] [purple]
text = f1eaff text = f1eaff
subtext = f1eaff subtext = f1eaff
sidebar-text = e0d0ff sidebar-text = e0d0ff
main = 0A0E14 main = 0A0E14
sidebar = 6F3C89 sidebar = 6F3C89
player = 0A0E14 player = 0A0E14
card = 0A0E14 card = 0A0E14
shadow = 3a2645 shadow = 3a2645
selected-row = EBDFFF selected-row = EBDFFF
button = c76af6 button = c76af6
button-active = 6F3C89 button-active = 6F3C89
button-disabled = 535353 button-disabled = 535353
tab-active = 58306D tab-active = 58306D
notification = ff9e00 notification = ff9e00
notification-error = f61379 notification-error = f61379
misc = DEDEDE misc = DEDEDE
[samourai] [samourai]
text = ebdbb2 text = ebdbb2
subtext = ebdbb2 subtext = ebdbb2
sidebar-text = 461217 sidebar-text = 461217
main = 461217 main = 461217
sidebar = ebdbb2 sidebar = ebdbb2
player = 461217 player = 461217
card = 461217 card = 461217
shadow = 3a2645 shadow = 3a2645
selected-row = 909090 selected-row = 909090
button = e7a52d button = e7a52d
button-active = e7a52d button-active = e7a52d
button-disabled = 535353 button-disabled = 535353
tab-active = e7a52d tab-active = e7a52d
notification = e7a52d notification = e7a52d
notification-error = e22134 notification-error = e22134
misc = BFBFBF misc = BFBFBF
[beach-sunset] [beach-sunset]
text = FFFFFF text = FFFFFF
subtext = F0F0F0 subtext = F0F0F0
sidebar-text = F0F0F0 sidebar-text = F0F0F0
main = 262626 main = 262626
sidebar = bd3e3e sidebar = bd3e3e
player = 262626 player = 262626
card = 262626 card = 262626
shadow = 000000 shadow = 000000
selected-row = d1d6e2 selected-row = d1d6e2
button = f1a84f button = f1a84f
button-active = c98430 button-active = c98430
button-disabled = 535353 button-disabled = 535353
tab-active = f1a84f tab-active = f1a84f
notification = c98430 notification = c98430
notification-error = e22134 notification-error = e22134
misc = BFBFBF misc = BFBFBF

View file

@ -1,6 +1,4 @@
// Hide popover message export default class ConfigMenu {
// document.getElementById("popover-container").style.height = 0;
class ConfigMenu {
/** /**
* @typedef {Object} DribbblishConfigItem * @typedef {Object} DribbblishConfigItem
* @property {"checkbox" | "select" | "button" | "slider" | "number" | "text" | "time" | "color"} type * @property {"checkbox" | "select" | "button" | "slider" | "number" | "text" | "time" | "color"} type
@ -29,17 +27,20 @@ class ConfigMenu {
/** /**
* @callback showChildren * @callback showChildren
* @this {DribbblishConfigItem}
* @param {any} value * @param {any} value
* @returns {Boolean | String[]} * @returns {Boolean | String[]}
*/ */
/** /**
* @callback onAppended * @callback onAppended
* @this {DribbblishConfigItem}
* @returns {void} * @returns {void}
*/ */
/** /**
* @callback onChange * @callback onChange
* @this {DribbblishConfigItem}
* @param {any} value * @param {any} value
* @returns {void} * @returns {void}
*/ */
@ -49,7 +50,7 @@ class ConfigMenu {
constructor() { constructor() {
this.#config = {}; this.#config = {};
this.configButton = new Spicetify.Menu.Item("Dribbblish Settings", false, () => DribbblishShared.config.open()); this.configButton = new Spicetify.Menu.Item("Dribbblish Settings", false, () => this.open());
this.configButton.register(); this.configButton.register();
const container = document.createElement("div"); const container = document.createElement("div");
@ -66,8 +67,8 @@ class ConfigMenu {
`; `;
document.body.appendChild(container); document.body.appendChild(container);
document.querySelector(".dribbblish-config-close").addEventListener("click", () => DribbblishShared.config.close()); document.querySelector(".dribbblish-config-close").addEventListener("click", () => this.close());
document.querySelector(".dribbblish-config-backdrop").addEventListener("click", () => DribbblishShared.config.close()); document.querySelector(".dribbblish-config-backdrop").addEventListener("click", () => this.close());
} }
open() { open() {
@ -138,8 +139,8 @@ class ConfigMenu {
.join("\n"); .join("\n");
options._onChange = options.onChange; options._onChange = options.onChange;
options.onChange = (val) => { options.onChange = (val) => {
options._onChange(val); options._onChange.call(options, val);
const show = options.showChildren(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) => { options.children = options.children.map((child) => {
@ -147,7 +148,6 @@ class ConfigMenu {
}); });
this.#config[options.key] = options; this.#config[options.key] = options;
this.#config[options.key].value = localStorage.getItem(`dribbblish:config:${options.key}`) ?? JSON.stringify(options.defaultValue);
if (options.type == "checkbox") { if (options.type == "checkbox") {
const input = /* html */ ` const input = /* html */ `
@ -165,17 +165,19 @@ class ConfigMenu {
} else if (options.type == "select") { } else if (options.type == "select") {
// Validate // Validate
const val = this.get(options.key); const val = this.get(options.key);
if (val < 0 || val > options.data.length - 1) this.set(options.key); if (!Object.keys(options.data).includes(val)) this.reset(options.key);
const input = /* html */ ` const input = /* html */ `
<select class="main-dropDown-dropDown" id="dribbblish-config-input-${options.key}"> <select class="main-dropDown-dropDown" id="dribbblish-config-input-${options.key}">
${options.data.map((option, i) => `<option value="${i}"${this.get(options.key) == i ? " selected" : ""}>${option}</option>`).join("")} ${Object.entries(options.data)
.map(([key, name]) => `<option value="${key}"${this.get(options.key) == key ? " selected" : ""}>${name}</option>`)
.join("")}
</select> </select>
`; `;
this.addInputHTML({ ...options, input }); this.addInputHTML({ ...options, input });
document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener("change", (e) => { document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener("change", (e) => {
this.set(options.key, Number(e.target.value)); this.set(options.key, e.target.value);
options.onChange(this.get(options.key)); options.onChange(this.get(options.key));
}); });
} else if (options.type == "button") { } else if (options.type == "button") {
@ -289,7 +291,7 @@ class ConfigMenu {
options.children.forEach((child) => this.register(child)); options.children.forEach((child) => this.register(child));
options.onAppended(); options.onAppended.call(options);
if (options.fireInitialChange) options.onChange(this.get(options.key)); if (options.fireInitialChange) options.onChange(this.get(options.key));
} }
@ -332,9 +334,12 @@ class ConfigMenu {
* @returns {any} * @returns {any}
*/ */
get(key, defaultValueOverride) { get(key, defaultValueOverride) {
const val = JSON.parse(this.#config[key].value ?? 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()` dosen't like undefined
if (val == null) return defaultValueOverride ?? this.#config[key].defaultValue; if (val == null || val?.type != this.#config[key]?.type) {
return val; localStorage.removeItem(`dribbblish:config:${key}`);
return defaultValueOverride ?? this.#config[key].defaultValue;
}
return val.value;
} }
/** /**
@ -343,10 +348,20 @@ class ConfigMenu {
* @param {any} val * @param {any} val
*/ */
set(key, val) { set(key, val) {
this.#config[key].value = JSON.stringify(val); val = { type: this.#config[key].type, value: val ?? this.#config[key].defaultValue };
this.#config[key].storageCache = JSON.stringify(val);
localStorage.setItem(`dribbblish:config:${key}`, JSON.stringify(val)); localStorage.setItem(`dribbblish:config:${key}`, JSON.stringify(val));
} }
/**
*
* @param {String} key
*/
reset(key) {
delete this.#config[key].storageCache;
localStorage.removeItem(`dribbblish:config:${key}`);
}
/** /**
* *
* @param {String} key * @param {String} key
@ -361,334 +376,3 @@ class ConfigMenu {
return this.#config[key]; return this.#config[key];
} }
} }
class _DribbblishShared {
constructor() {
this.config = new ConfigMenu();
}
}
const DribbblishShared = new _DribbblishShared();
DribbblishShared.config.register({
type: "checkbox",
key: "rightBigCover",
name: "Right expanded cover",
description: "Have the expanded cover Image on the right instead of on the left",
defaultValue: true,
onChange: (val) => {
if (val) {
document.documentElement.classList.add("right-expanded-cover");
} else {
document.documentElement.classList.remove("right-expanded-cover");
}
}
});
DribbblishShared.config.register({
type: "checkbox",
key: "roundSidebarIcons",
name: "Round Sidebar Icons",
description: "If the Sidebar Icons should be round instead of square",
defaultValue: false,
onChange: (val) => document.documentElement.style.setProperty("--sidebar-icons-border-radius", val ? "50%" : "var(--image-radius)")
});
DribbblishShared.config.register({
area: "Animations & Transitions",
type: "checkbox",
key: "sidebarHoverAnimation",
name: "Sidebar Hover Animation",
description: "If the Sidebar Icons should have an animated background on hover",
defaultValue: true,
onChange: (val) => document.documentElement.style.setProperty("--sidebar-icons-hover-animation", val ? "1" : "0")
});
waitForElement(["#main"], () => {
DribbblishShared.config.register({
type: "select",
data: ["None", "None (With Top Padding)", "Solid", "Transparent"],
key: "winTopBar",
name: "Windows Top Bar",
description: "Have different top Bars (or none at all)",
defaultValue: 0,
onChange: (val) => {
switch (val) {
case 0:
document.getElementById("main").setAttribute("top-bar", "none");
break;
case 1:
document.getElementById("main").setAttribute("top-bar", "none-padding");
break;
case 2:
document.getElementById("main").setAttribute("top-bar", "solid");
break;
case 3:
document.getElementById("main").setAttribute("top-bar", "transparent");
break;
}
}
});
DribbblishShared.config.register({
type: "select",
data: ["Dribbblish", "Spotify"],
key: "playerControlsStyle",
name: "Player Controls Style",
description: "Style of the Player Controls. Selecting Spotify basically changes Play / Pause back to the center",
defaultValue: 0,
onChange: (val) => {
switch (val) {
case 0:
document.getElementById("main").setAttribute("player-controls", "dribbblish");
break;
case 1:
document.getElementById("main").setAttribute("player-controls", "spotify");
break;
}
}
});
DribbblishShared.config.register({
area: "Ads",
type: "checkbox",
key: "hideAds",
name: "Hide Ads",
description: `Hide ads / premium features (see: <a href="https://github.com/Daksh777/SpotifyNoPremium">SpotifyNoPremium</a>)`,
defaultValue: false,
onAppended: () => {
document.styleSheets[0].insertRule(/* css */ `
/* Remove upgrade button*/
#main[hide-ads] .main-topBar-UpgradeButton {
display: none
}
`);
document.styleSheets[0].insertRule(/* css */ `
/* Remove upgrade to premium button in user menu */
#main[hide-ads] .main-contextMenu-menuItemButton[href="https://www.spotify.com/premium/"] {
display: none
}
`);
document.styleSheets[0].insertRule(/* css */ `
/* Remove ad placeholder in main screen */
#main[hide-ads] .main-leaderboardComponent-container {
display: none
}
`);
},
onChange: (val) => document.getElementById("main").toggleAttribute("hide-ads", val)
});
});
function waitForElement(els, func, timeout = 100) {
const queries = els.map((el) => document.querySelector(el));
if (queries.every((a) => a)) {
func(queries);
} else if (timeout > 0) {
setTimeout(waitForElement, 300, els, func, --timeout);
}
}
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 */
function loadPlaylistImage() {
for (const item of listElem.children) {
let link = item.querySelector("a");
if (!link) continue;
let [_, app, uid] = link.pathname.split("/");
let uri;
if (app === "playlist") {
uri = Spicetify.URI.playlistV2URI(uid);
} else if (app === "folder") {
const base64 = localStorage.getItem("dribbblish:folder-image:" + uid);
let img = link.querySelector("img");
if (!img) {
img = document.createElement("img");
img.classList.add("playlist-picture");
link.prepend(img);
}
img.src = base64 || "/images/tracklist-row-song-fallback.svg";
continue;
}
Spicetify.CosmosAsync.get(`sp://core-playlist/v1/playlist/${uri.toURI()}/metadata`, { policy: { picture: true } }).then((res) => {
const meta = res.metadata;
let img = link.querySelector("img");
if (!img) {
img = document.createElement("img");
img.classList.add("playlist-picture");
link.prepend(img);
}
img.src = meta.picture || "/images/tracklist-row-song-fallback.svg";
});
}
}
DribbblishShared.loadPlaylistImage = loadPlaylistImage;
loadPlaylistImage();
new MutationObserver(loadPlaylistImage).observe(listElem, { childList: true });
});
waitForElement([".main-rootlist-rootlist", ".main-rootlist-wrapper > :nth-child(2) > :first-child", "#spicetify-show-list"], ([rootlist]) => {
function checkSidebarPlaylistScroll() {
const topDist = rootlist.getBoundingClientRect().top - document.querySelector("#spicetify-show-list:not(:empty), .main-rootlist-wrapper > :nth-child(2) > :first-child").getBoundingClientRect().top;
const bottomDist = document.querySelector(".main-rootlist-wrapper > :nth-child(2) > :last-child").getBoundingClientRect().bottom - rootlist.getBoundingClientRect().bottom;
rootlist.classList.remove("no-top-shadow", "no-bottom-shadow");
if (topDist < 10) rootlist.classList.add("no-top-shadow");
if (bottomDist < 10) rootlist.classList.add("no-bottom-shadow");
}
checkSidebarPlaylistScroll();
// Use Interval because scrolling takes a while and getBoundingClientRect() gets position at the moment of calling, so the interval keeps calling for 1s
let c = 0;
let interval;
rootlist.addEventListener("wheel", () => {
checkSidebarPlaylistScroll();
c = 0;
if (interval == null)
interval = setInterval(() => {
if (c > 20) {
clearInterval(interval);
interval = null;
return;
}
checkSidebarPlaylistScroll();
c++;
}, 50);
});
});
waitForElement([".Root__main-view"], ([mainView]) => {
const shadow = document.createElement("div");
shadow.id = "dribbblish-back-shadow";
mainView.prepend(shadow);
});
waitForElement([".Root__nav-bar .LayoutResizer__input, .Root__nav-bar .LayoutResizer__resize-bar input"], ([resizer]) => {
const observer = new MutationObserver(updateVariable);
observer.observe(resizer, { attributes: true, attributeFilter: ["value"] });
function updateVariable() {
let value = resizer.value;
if (value < 121) {
value = 72;
document.documentElement.classList.add("sidebar-hide-text");
} else {
document.documentElement.classList.remove("sidebar-hide-text");
}
document.documentElement.style.setProperty("--sidebar-width", value + "px");
}
updateVariable();
});
waitForElement([".Root__main-view .os-resize-observer-host"], ([resizeHost]) => {
const observer = new ResizeObserver(updateVariable);
observer.observe(resizeHost);
function updateVariable([event]) {
document.documentElement.style.setProperty("--main-view-width", event.contentRect.width + "px");
document.documentElement.style.setProperty("--main-view-height", event.contentRect.height + "px");
if (event.contentRect.width < 700) {
document.documentElement.classList.add("minimal-player");
} else {
document.documentElement.classList.remove("minimal-player");
}
if (event.contentRect.width < 550) {
document.documentElement.classList.add("extra-minimal-player");
} else {
document.documentElement.classList.remove("extra-minimal-player");
}
}
});
(function Dribbblish() {
const progBar = document.querySelector(".playback-bar");
const root = document.querySelector(".Root");
if (!Spicetify.Player.origin || !progBar || !root) {
setTimeout(Dribbblish, 300);
return;
}
const progKnob = progBar.querySelector(".progress-bar__slider");
const tooltip = document.createElement("div");
tooltip.className = "prog-tooltip";
progKnob.append(tooltip);
function updateProgTime(timeOverride) {
const newText = Spicetify.Player.formatTime(timeOverride || Spicetify.Player.getProgress()) + " / " + Spicetify.Player.formatTime(Spicetify.Player.getDuration());
// To reduce DOM Updates when the Song is Paused
if (tooltip.innerText != newText) tooltip.innerText = newText;
}
const knobPosObserver = new MutationObserver((muts) => {
const progressPercentage = Number(getComputedStyle(document.querySelector(".progress-bar")).getPropertyValue("--progress-bar-transform").replace("%", "")) / 100;
updateProgTime(Spicetify.Player.getDuration() * progressPercentage);
});
knobPosObserver.observe(document.querySelector(".progress-bar"), {
attributes: true,
attributeFilter: ["style"]
});
Spicetify.Player.addEventListener("songchange", () => updateProgTime());
updateProgTime();
Spicetify.CosmosAsync.sub("sp://connect/v1", (state) => {
const isExternal = state.devices.some((a) => a.is_active);
if (isExternal) {
root.classList.add("is-connectBarVisible");
} else {
root.classList.remove("is-connectBarVisible");
}
});
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");
}
DribbblishShared.loadPlaylistImage?.call();
};
reader.readAsDataURL(file);
};
new Spicetify.ContextMenu.Item(
"Remove folder image",
([uri]) => {
const id = Spicetify.URI.from(uri).id;
localStorage.removeItem("dribbblish:folder-image:" + id);
DribbblishShared.loadPlaylistImage?.call();
},
([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();
})();

672
src/js/main.js Normal file
View file

@ -0,0 +1,672 @@
import * as Vibrant from "node-vibrant";
import chroma from "chroma-js";
import $ from "jquery";
import ConfigMenu from "./ConfigMenu";
class _DribbblishShared {
constructor() {
this.config = new ConfigMenu();
}
}
const DribbblishShared = new _DribbblishShared();
DribbblishShared.config.register({
type: "checkbox",
key: "rightBigCover",
name: "Right expanded cover",
description: "Have the expanded cover Image on the right instead of on the left",
defaultValue: true,
onChange: (val) => $("html").toggleClass("right-expanded-cover", val)
});
DribbblishShared.config.register({
type: "checkbox",
key: "roundSidebarIcons",
name: "Round Sidebar Icons",
description: "If the Sidebar Icons should be round instead of square",
defaultValue: false,
onChange: (val) => $("html").css("--sidebar-icons-border-radius", val ? "50%" : "var(--image-radius)")
});
DribbblishShared.config.register({
area: "Animations & Transitions",
type: "checkbox",
key: "sidebarHoverAnimation",
name: "Sidebar Hover Animation",
description: "If the Sidebar Icons should have an animated background on hover",
defaultValue: true,
onChange: (val) => $("html").css("--sidebar-icons-hover-animation", val ? "1" : "0")
});
waitForElement(["#main"], () => {
DribbblishShared.config.register({
type: "select",
data: { none: "None", "none-padding": "None (With Top Padding)", solid: "Solid", transparent: "Transparent" },
key: "winTopBar",
name: "Windows Top Bar",
description: "Have different top Bars (or none at all)",
defaultValue: "none",
onChange: (val) => $("#main").attr("top-bar", val)
});
DribbblishShared.config.register({
type: "select",
data: { dribbblish: "Dribbblish", spotify: "Spotify" },
key: "playerControlsStyle",
name: "Player Controls Style",
description: "Style of the Player Controls. Selecting Spotify basically changes Play / Pause back to the center",
defaultValue: "dribbblish",
onChange: (val) => $("#main").attr("player-controls", val)
});
DribbblishShared.config.register({
area: "Ads",
type: "checkbox",
key: "hideAds",
name: "Hide Ads",
description: `Hide ads / premium features (see: <a href="https://github.com/Daksh777/SpotifyNoPremium">SpotifyNoPremium</a>)`,
defaultValue: false,
onChange: (val) => $("#main").attr("hide-ads", val)
});
});
function waitForElement(els, func, timeout = 100) {
const queries = els.map((el) => document.querySelector(el));
if (queries.every((a) => a)) {
func(queries);
} else if (timeout > 0) {
setTimeout(waitForElement, 300, els, func, --timeout);
}
}
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 */
function loadPlaylistImage() {
for (const item of listElem.children) {
let link = item.querySelector("a");
if (!link) continue;
let [_, app, uid] = link.pathname.split("/");
let uri;
if (app === "playlist") {
uri = Spicetify.URI.playlistV2URI(uid);
} else if (app === "folder") {
const base64 = localStorage.getItem("dribbblish:folder-image:" + uid);
let img = link.querySelector("img");
if (!img) {
img = document.createElement("img");
img.classList.add("playlist-picture");
link.prepend(img);
}
img.src = base64 || "/images/tracklist-row-song-fallback.svg";
continue;
}
Spicetify.CosmosAsync.get(`sp://core-playlist/v1/playlist/${uri.toURI()}/metadata`, { policy: { picture: true } }).then((res) => {
const meta = res.metadata;
let img = link.querySelector("img");
if (!img) {
img = document.createElement("img");
img.classList.add("playlist-picture");
link.prepend(img);
}
img.src = meta.picture || "/images/tracklist-row-song-fallback.svg";
});
}
}
DribbblishShared.loadPlaylistImage = loadPlaylistImage;
loadPlaylistImage();
new MutationObserver(loadPlaylistImage).observe(listElem, { childList: true });
});
waitForElement([".main-rootlist-rootlist", ".main-rootlist-wrapper > :nth-child(2) > :first-child", "#spicetify-show-list"], ([rootlist]) => {
function checkSidebarPlaylistScroll() {
const topDist = rootlist.getBoundingClientRect().top - document.querySelector("#spicetify-show-list:not(:empty), .main-rootlist-wrapper > :nth-child(2) > :first-child").getBoundingClientRect().top;
const bottomDist = document.querySelector(".main-rootlist-wrapper > :nth-child(2) > :last-child").getBoundingClientRect().bottom - rootlist.getBoundingClientRect().bottom;
rootlist.classList.remove("no-top-shadow", "no-bottom-shadow");
if (topDist < 10) rootlist.classList.add("no-top-shadow");
if (bottomDist < 10) rootlist.classList.add("no-bottom-shadow");
}
checkSidebarPlaylistScroll();
// Use Interval because scrolling takes a while and getBoundingClientRect() gets position at the moment of calling, so the interval keeps calling for 1s
let c = 0;
let interval;
rootlist.addEventListener("wheel", () => {
checkSidebarPlaylistScroll();
c = 0;
if (interval == null)
interval = setInterval(() => {
if (c > 20) {
clearInterval(interval);
interval = null;
return;
}
checkSidebarPlaylistScroll();
c++;
}, 50);
});
});
waitForElement([".Root__main-view"], ([mainView]) => {
const shadow = document.createElement("div");
shadow.id = "dribbblish-back-shadow";
mainView.prepend(shadow);
});
waitForElement([".Root__nav-bar .LayoutResizer__input, .Root__nav-bar .LayoutResizer__resize-bar input"], ([resizer]) => {
const observer = new MutationObserver(updateVariable);
observer.observe(resizer, { attributes: true, attributeFilter: ["value"] });
function updateVariable() {
let value = resizer.value;
if (value < 121) value = 72;
$("html").toggleClass("sidebar-hide-text", value < 121);
$("html").css("--sidebar-width", `${value}px`);
}
updateVariable();
});
waitForElement([".Root__main-view .os-resize-observer-host"], ([resizeHost]) => {
const observer = new ResizeObserver(updateVariable);
observer.observe(resizeHost);
function updateVariable([event]) {
$("html").css("--main-view-width", event.contentRect.width + "px");
$("html").css("--main-view-height", event.contentRect.height + "px");
$("html").toggleClass("minimal-player", event.contentRect.width < 700);
$("html").toggleClass("extra-minimal-player", event.contentRect.width < 550);
}
});
(function Dribbblish() {
const progBar = document.querySelector(".playback-bar");
const root = document.querySelector(".Root");
if (!Spicetify.Player.origin || !progBar || !root) {
setTimeout(Dribbblish, 300);
return;
}
const progKnob = progBar.querySelector(".progress-bar__slider");
const tooltip = document.createElement("div");
tooltip.className = "prog-tooltip";
progKnob.append(tooltip);
function updateProgTime(timeOverride) {
const newText = Spicetify.Player.formatTime(timeOverride || Spicetify.Player.getProgress()) + " / " + Spicetify.Player.formatTime(Spicetify.Player.getDuration());
// To reduce DOM Updates when the Song is Paused
if (tooltip.innerText != newText) tooltip.innerText = newText;
}
const knobPosObserver = new MutationObserver((muts) => {
const progressPercentage = Number($(".progress-bar").css("--progress-bar-transform").replace("%", "")) / 100;
updateProgTime(Spicetify.Player.getDuration() * progressPercentage);
});
knobPosObserver.observe(document.querySelector(".progress-bar"), {
attributes: true,
attributeFilter: ["style"]
});
Spicetify.Player.addEventListener("songchange", () => updateProgTime());
updateProgTime();
Spicetify.CosmosAsync.sub("sp://connect/v1", (state) => {
const isExternal = state.devices.some((a) => a.is_active);
if (isExternal) {
root.classList.add("is-connectBarVisible");
} else {
root.classList.remove("is-connectBarVisible");
}
});
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");
}
DribbblishShared.loadPlaylistImage?.call();
};
reader.readAsDataURL(file);
};
new Spicetify.ContextMenu.Item(
"Remove folder image",
([uri]) => {
const id = Spicetify.URI.from(uri).id;
localStorage.removeItem("dribbblish:folder-image:" + id);
DribbblishShared.loadPlaylistImage?.call();
},
([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();
})();
/* Config settings */
DribbblishShared.config.register({
area: "Animations & Transitions",
type: "slider",
key: "fadeDuration",
name: "Color Fade Duration",
description: "Select the duration of the color fading transition",
defaultValue: 0.5,
data: {
min: 0,
max: 10,
step: 0.1,
suffix: "s"
},
onChange: (val) => $("html").css("--song-transition-speed", `${val}s`)
});
// waitForElement because Spicetify is not initialized at startup
waitForElement(["#main"], () => {
DribbblishShared.config.register({
area: { name: "About", order: 999 },
type: "button",
key: "aboutDribbblish",
name: "Info",
description: `
OS: ${capitalizeFirstLetter(Spicetify.Platform.PlatformData.os_name)} v${Spicetify.Platform.PlatformData.os_version}
Spotify: v${Spicetify.Platform.PlatformData.event_sender_context_information?.client_version_string ?? Spicetify.Platform.PlatformData.client_version_triple}
Dribbblish: v${process.env.DRIBBBLISH_VERSION}
`,
data: "Copy",
onChange: (val) => {
copyToClipboard(DribbblishShared.config.getOptions("aboutDribbblish").description);
Spicetify.showNotification("Copied Versions");
}
});
});
function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
function copyToClipboard(text) {
var input = document.createElement("textarea");
input.style.display = "fixed";
input.innerHTML = text;
document.body.appendChild(input);
input.select();
var result = document.execCommand("copy");
document.body.removeChild(input);
return result;
}
/* js */
function getAlbumInfo(uri) {
return Spicetify.CosmosAsync.get(`hm://album/v1/album-app/album/${uri}/desktop`);
}
function isLight(hex) {
var [r, g, b] = chroma(hex).rgb().map(Number);
const brightness = (r * 299 + g * 587 + b * 114) / 1000;
return brightness > 128;
}
// From: https://stackoverflow.com/a/13763063/12126879
function getImageLightness(img) {
var colorSum = 0;
var canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
var data = imageData.data;
var r, g, b, avg;
for (var x = 0, len = data.length; x < len; x += 4) {
r = data[x];
g = data[x + 1];
b = data[x + 2];
avg = Math.floor((r + g + b) / 3);
colorSum += avg;
}
var brightness = Math.floor(colorSum / (img.width * img.height));
return brightness;
}
// parse to hex beacuse "--spice-sidebar" is `rgb()`
let textColor = chroma($("html").css("--spice-text")).hex();
let textColorBg = chroma($("html").css("--spice-main")).hex();
let sidebarColor = chroma($("html").css("--spice-sidebar")).hex();
function setRootColor(name, colHex) {
$("html").css(`--spice-${name}`, colHex);
$("html").css(`--spice-rgb-${name}`, chroma(colHex).rgb().join(","));
}
function toggleDark(setDark) {
if (setDark === undefined) setDark = isLight(textColorBg);
$("html").css("--is_light", setDark ? 0 : 1);
textColorBg = setDark ? "#0A0A0A" : "#FAFAFA";
setRootColor("main", textColorBg);
setRootColor("player", textColorBg);
setRootColor("card", setDark ? "#040404" : "#ECECEC");
setRootColor("subtext", setDark ? "#EAEAEA" : "#3D3D3D");
setRootColor("notification", setDark ? "#303030" : "#DDDDDD");
updateColors(textColor, sidebarColor, false);
}
function checkDarkLightMode(colors) {
const theme = DribbblishShared.config.get("theme");
if (theme == "time") {
const start = 60 * parseInt(DribbblishShared.config.get("darkModeOnTime").split(":")[0]) + parseInt(DribbblishShared.config.get("darkModeOnTime").split(":")[1]);
const end = 60 * parseInt(DribbblishShared.config.get("darkModeOffTime").split(":")[0]) + parseInt(DribbblishShared.config.get("darkModeOffTime").split(":")[1]);
const now = new Date();
const time = 60 * now.getHours() + now.getMinutes();
let dark;
if (end < start) dark = start <= time || time < end;
else dark = start <= time && time < end;
toggleDark(dark);
} else if (theme == "color") {
if (colors && colors.length > 0) toggleDark(isLight(colors[0]));
}
}
// Run every Minute to check time and set dark / light mode
setInterval(checkDarkLightMode, 60000);
DribbblishShared.config.register({
area: "Theme",
type: "checkbox",
key: "dynamicColors",
name: "Dynamic",
description: "If the Theme's Color should be extracted from Albumart",
defaultValue: true,
onChange: (val) => updateColors(),
showChildren: (val) => !val,
children: [
{
type: "color",
key: "colorOverride",
name: "Color",
description: "The Color of the Theme",
defaultValue: "#1ed760",
fireInitialChange: false,
onChange: (val) => updateColors()
}
]
});
DribbblishShared.config.register({
area: "Theme",
type: "select",
data: { dark: "Dark", light: "Light", time: "Based on Time", color: "Based on Color" },
key: "theme",
name: "Theme",
description: "Select Dark / Bright mode",
defaultValue: "dark",
showChildren: (val) => {
if (val == 2) return ["darkModeOnTime", "darkModeOffTime"];
//if (val == 3) return [""];
return false;
},
onChange: (val) => {
switch (val) {
case "dark":
toggleDark(true);
break;
case "light":
toggleDark(false);
break;
case "time":
checkDarkLightMode();
break;
case "color":
checkDarkLightMode();
break;
}
},
children: [
{
type: "time",
key: "darkModeOnTime",
name: "Dark Mode On Time",
description: "Beginning of Dark mode time",
defaultValue: "20:00",
fireInitialChange: false,
onChange: checkDarkLightMode
},
{
type: "time",
key: "darkModeOffTime",
name: "Dark Mode Off Time",
description: "End of Dark mode time",
defaultValue: "06:00",
fireInitialChange: false,
onChange: checkDarkLightMode
}
]
});
var currentColor;
var currentSideColor;
function updateColors(textColHex, sideColHex, checkDarkMode = true) {
if (textColHex && sideColHex) {
currentColor = textColHex;
currentSideColor = sideColHex;
} else {
if (!(currentColor && currentSideColor)) return; // If `updateColors()` is called early these vars are undefined and would break
textColHex = currentColor;
sideColHex = currentSideColor;
}
if (!DribbblishShared.config.get("dynamicColors")) {
const col = DribbblishShared.config.get("colorOverride");
textColHex = col;
sideColHex = col;
}
let isLightBg = isLight(textColorBg);
if (isLightBg) textColHex = chroma(textColHex).darken(0.15).hex(); // vibrant color is always too bright for white bg mode
let darkColHex = chroma(textColHex)
.brighten(isLightBg ? 0.12 : -0.2)
.hex();
let darkerColHex = chroma(textColHex)
.brighten(isLightBg ? 0.3 : -0.4)
.hex();
let buttonBgColHex = chroma(textColHex)
.set("hsl.l", isLightBg ? 0.9 : 0.14)
.hex();
setRootColor("text", textColHex);
setRootColor("button", darkerColHex);
setRootColor("button-active", darkColHex);
setRootColor("selected-row", darkerColHex);
setRootColor("tab-active", buttonBgColHex);
setRootColor("button-disabled", buttonBgColHex);
setRootColor("sidebar", sideColHex);
if (checkDarkMode) checkDarkLightMode([textColHex, sideColHex]);
}
let nearArtistSpan;
let nearArtistSpanText = "";
let coverListenerInstalled = false;
async function songchange() {
try {
// warning popup
if (Spicetify.Platform.PlatformData.client_version_triple < "1.1.68") Spicetify.showNotification(`Your version of Spotify ${Spicetify.Platform.PlatformData.client_version_triple}) is un-supported`);
} catch (err) {
console.error(err);
}
let album_uri = Spicetify.Player.data.track.metadata.album_uri;
let bgImage = Spicetify.Player.data.track.metadata.image_url;
if (bgImage === undefined) {
bgImage = "/images/tracklist-row-song-fallback.svg";
textColor = "#509bf5";
updateColors(textColor, textColor);
coverListenerInstalled = false;
}
if (!coverListenerInstalled) hookCoverChange(true);
if (album_uri !== undefined && !album_uri.includes("spotify:show")) {
const albumInfo = await getAlbumInfo(album_uri.replace("spotify:album:", ""));
let album_date = new Date(albumInfo.year, (albumInfo.month || 1) - 1, albumInfo.day || 0);
let recent_date = new Date();
recent_date.setMonth(recent_date.getMonth() - 6);
album_date = album_date.toLocaleString("default", album_date > recent_date ? { year: "numeric", month: "short" } : { year: "numeric" });
let album_link = '<a title="' + Spicetify.Player.data.track.metadata.album_title + '" href="' + album_uri + '" data-uri="' + album_uri + '" data-interaction-target="album-name" class="tl-cell__content">' + Spicetify.Player.data.track.metadata.album_title + "</a>";
nearArtistSpanText = album_link + " • " + album_date;
} else if (Spicetify.Player.data.track.uri.includes("spotify:episode")) {
// podcast
bgImage = bgImage.replace("spotify:image:", "https://i.scdn.co/image/");
nearArtistSpanText = Spicetify.Player.data.track.metadata.album_title;
} else if (Spicetify.Player.data.track.metadata.is_local == "true") {
// local file
nearArtistSpanText = Spicetify.Player.data.track.metadata.album_title;
} else if (Spicetify.Player.data.track.provider == "ad") {
// ad
nearArtistSpanText = "advertisement";
coverListenerInstalled = false;
return;
} else {
// When clicking a song from the homepage, songChange is fired with half empty metadata
// todo: retry only once?
setTimeout(songchange, 200);
}
if (document.querySelector("#main-trackInfo-year") === null) {
waitForElement([".main-trackInfo-container"], (queries) => {
nearArtistSpan = document.createElement("div");
nearArtistSpan.id = "main-trackInfo-year";
nearArtistSpan.classList.add("main-trackInfo-artists", "ellipsis-one-line", "main-type-finale");
nearArtistSpan.innerHTML = nearArtistSpanText;
queries[0].append(nearArtistSpan);
});
} else {
nearArtistSpan.innerHTML = nearArtistSpanText;
}
$("html").css("--image-url", `url("${bgImage}")`);
}
Spicetify.Player.addEventListener("songchange", songchange);
async function pickCoverColor(img) {
if (!img.currentSrc.startsWith("spotify:")) return;
$("html").css("--image-brightness", getImageLightness(img) / 255);
var swatches = await new Promise((resolve, reject) => new Vibrant(img, 5).getPalette().then(resolve).catch(reject));
var lightCols = ["Vibrant", "DarkVibrant", "Muted", "LightVibrant"];
var darkCols = ["Vibrant", "LightVibrant", "Muted", "DarkVibrant"];
var mainCols = isLight(textColorBg) ? lightCols : darkCols;
textColor = "#509bf5";
for (var col in mainCols)
if (swatches[mainCols[col]]) {
textColor = swatches[mainCols[col]].getHex();
break;
}
sidebarColor = "#509bf5";
for (var col in lightCols)
if (swatches[lightCols[col]]) {
sidebarColor = swatches[lightCols[col]].getHex();
break;
}
updateColors(textColor, sidebarColor);
}
waitForElement([".main-nowPlayingBar-left"], (queries) => {
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (mutation.removedNodes.length > 0) coverListenerInstalled = false;
});
});
observer.observe(queries[0], { childList: true });
});
function hookCoverChange(pick) {
waitForElement([".cover-art-image"], (queries) => {
coverListenerInstalled = true;
var elem = queries.slice(-1)[0];
if (pick && elem.complete && elem.naturalHeight !== 0) pickCoverColor(elem);
elem.addEventListener("load", function () {
try {
pickCoverColor(elem);
} catch (error) {
console.error(error);
setTimeout(pickCoverColor, 300, elem);
}
});
});
}
hookCoverChange(false);
// Check latest release
waitForElement([".main-userWidget-box"], ([userWidget]) => {
fetch("https://api.github.com/repos/JulienMaille/dribbblish-dynamic-theme/releases/latest")
.then((response) => {
return response.json();
})
.then((data) => {
const upd = document.createElement("div");
upd.classList.add("ellipsis-one-line", "main-type-finale");
upd.setAttribute("title", `Changes: ${data.name}`);
upd.style.setProperty("color", "var(--spice-button-active)");
if (process.env.DRIBBBLISH_VERSION == "Dev") {
upd.innerText = "Dev version!";
} else if (data.tag_name > process.env.DRIBBBLISH_VERSION) {
upd.innerText = `Theme UPD v${data.tag_name} avail.`;
new Spicetify.Menu.Item("Update Dribbblish", false, () => window.open("https://github.com/JulienMaille/dribbblish-dynamic-theme/releases/latest", "_blank")).register();
}
userWidget.append(upd);
userWidget.classList.add("update-avail");
})
.catch((err) => {
// Do something for an error here
console.error(err);
});
});
$("html").css("--warning_message", " ");

138
src/styles/ConfigMenu.scss Normal file
View file

@ -0,0 +1,138 @@
#dribbblish-config {
display: none;
z-index: 99999;
position: absolute;
inset: 0px;
align-items: center;
justify-content: center;
color: var(--spice-text);
&[active] {
display: flex;
}
.dribbblish-config-container {
z-index: 1;
position: relative;
width: clamp(500px, 50%, 650px);
background-color: rgba(var(--spice-rgb-main), 0.9);
backdrop-filter: blur(3px);
padding: 20px 15px;
border-radius: var(--main-corner-radius);
box-shadow: 0 0 10px 3px #0000003b;
display: flex;
gap: 5px;
flex-direction: column;
align-items: center;
justify-content: center;
.dribbblish-config-close {
position: absolute;
top: 15px;
right: 15px;
}
.dribbblish-config-areas {
display: flex;
width: 100%;
flex-direction: column;
gap: 16px;
max-height: 60vh;
overflow-y: auto;
padding: 0px 50px;
.dribbblish-config-area {
display: flex;
flex-direction: column;
gap: 16px;
&[collapsed] {
overflow: hidden;
min-height: 38px; //for some reason height alone isn't enough
height: 38px;
> h2 svg {
transform: rotate(270deg);
}
}
&:empty {
display: none;
}
.dribbblish-config-area-header {
position: relative;
text-align: center;
height: 38px;
svg {
position: absolute;
margin-left: 10px;
bottom: -2px;
color: var(--spice-text);
padding: 0px;
height: 100%;
stroke-width: 2px;
transform: rotate(90deg);
}
}
.dribbblish-config-area-items {
display: flex;
flex-direction: column;
gap: 16px;
.dribbblish-config-item {
position: relative;
width: 100%;
height: min-content;
display: grid;
grid-template-columns: 1fr auto;
grid-template-rows: auto auto;
gap: 5px 10px;
grid-template-areas: "header input" "description input";
&[parent] {
padding-left: 16px;
}
&[hidden="true"] {
display: none;
}
> {
.x-settings-title {
grid-area: header;
margin: 0px;
height: min-content;
position: relative;
bottom: 0px;
&.no-desc {
bottom: -10px;
}
}
.main-type-mesto {
grid-area: description;
height: min-content;
color: var(--spice-subtext);
}
.x-settings-secondColumn {
grid-area: input;
}
}
}
}
}
}
}
.dribbblish-config-backdrop {
position: absolute;
content: "";
inset: 0px;
backdrop-filter: blur(3px) brightness(60%);
}
}

114
src/styles/Inputs.scss Normal file
View file

@ -0,0 +1,114 @@
button.main-button-primary {
background-color: rgba(var(--spice-rgb-selected-row), 0.4) !important;
&:hover,
&:active {
background-color: rgba(var(--spice-rgb-selected-row), 0.6) !important;
}
span {
color: var(--spice-subtext) !important;
}
}
input,
textarea {
background-color: rgba(var(--spice-rgb-selected-row), 0.4) !important;
border-radius: 4px !important;
padding: 6px 10px 6px 48px;
color: var(--spice-text) !important;
&:hover,
&:active {
background-color: rgba(var(--spice-rgb-selected-row), 0.6) !important;
}
&[type="range"] {
-webkit-appearance: none;
background: transparent;
padding: 0px;
&::-webkit-slider-thumb {
-webkit-appearance: none;
width: 16px;
height: 16px;
margin-top: -4px;
border-radius: 50%;
background-color: var(--spice-text);
&:hover,
&:active {
filter: brightness(80%);
}
}
&::after {
z-index: 9999;
content: attr(tooltip);
position: absolute;
min-width: 50px;
top: -10px;
left: 50%;
transform: translateX(calc(-50%));
padding: 0 5px;
border-radius: 4px;
text-align: center;
color: var(--spice-sidebar-text);
background-color: var(--spice-button);
transition: opacity 0.25s ease;
opacity: 0;
}
&:hover::after,
&:active::after {
opacity: 1;
}
&:focus {
outline: none;
}
&::-webkit-slider-runnable-track {
width: 100%;
height: 8px;
background-color: rgba(var(--spice-rgb-text), 0.2);
border-radius: 50vw;
}
}
&[type="number"],
&[type="text"],
&[type="time"] {
height: 32px;
border: none;
border-radius: 4px !important;
padding: 0px 10px;
color: var(--spice-subtext) !important;
}
&[type="time"] {
&::-webkit-calendar-picker-indicator {
filter: invert(calc(1 - var(--is_light)));
}
}
&[type="color"] {
position: relative;
padding: 0px;
border: none;
&::before {
z-index: -1;
content: "";
position: absolute;
inset: -5px;
border-radius: 4px;
background-color: rgba(var(--spice-rgb-selected-row), 0.4);
}
&:hover::before,
&:active::before {
background-color: rgba(var(--spice-rgb-selected-row), 0.6);
}
}
}

17
src/styles/NoAds.scss Normal file
View file

@ -0,0 +1,17 @@
#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 */
.main-leaderboardComponent-container {
display: none;
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
spicetify config current_theme " " extensions dribbblish.js- extensions dribbblish-dynamic.js- extensions Vibrant.min.js- spicetify config current_theme " " extensions dribbblish-dynamic.js-
$spicePath = spicetify -c | Split-Path $spicePath = spicetify -c | Split-Path
$configFile = Get-Content "$spicePath\config-xpui.ini" $configFile = Get-Content "$spicePath\config-xpui.ini"

56
webpack.config.js Normal file
View file

@ -0,0 +1,56 @@
const webpack = require("webpack");
const CopyPlugin = require("copy-webpack-plugin");
const CssMinimizerPlugin = require("css-minimizer-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")],
output: {
path: path.resolve(__dirname, "dist"),
filename: "dribbblish-dynamic.js"
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: []
},
{
test: /\.scss$/,
exclude: /node_modules/,
type: "asset/resource",
generator: {
filename: "user.css"
},
use: [
{
loader: "sass-loader",
options: {
sourceMap: true
}
}
]
}
]
},
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")
}),
new CopyPlugin({
patterns: [{ from: "src/assets", to: "assets" }, { from: "src/color.ini" }]
})
],
optimization: {
minimize: true,
minimizer: [`...`, new CssMinimizerPlugin()]
}
};