mirror of
https://github.com/danbulant/dribbblish-dynamic-theme
synced 2026-06-22 00:02:41 +00:00
Merge remote-tracking branch 'origin/webpack'
This commit is contained in:
commit
6161f6808f
27 changed files with 2633 additions and 2498 deletions
34
.github/workflows/empty-changelog.yaml
vendored
Normal file
34
.github/workflows/empty-changelog.yaml
vendored
Normal 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
73
.github/workflows/release.yaml
vendored
Normal 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
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
node_modules/
|
||||
dist/
|
||||
|
|
@ -1 +1,2 @@
|
|||
Vibrant.min.js
|
||||
Vibrant.min.js
|
||||
dist/
|
||||
0
CHANGELOG.md
Normal file
0
CHANGELOG.md
Normal file
62
README.md
62
README.md
|
|
@ -1,24 +1,25 @@
|
|||
# Dribbblish Dynamic
|
||||
|
||||
### Preview
|
||||

|
||||
|
||||
<img src="showcase-images/preview.gif" alt="img" width="500px">
|
||||
|
||||
## Features
|
||||
### 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
|
||||
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="https://i.imgur.com/86gqPe8.png" alt="img" width="500px">
|
||||
<img src="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="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
|
||||
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
|
||||
Make sure you are using spicetify >= v2.6.0 and Spotify >= v1.1.67.
|
||||
|
||||
Run these commands:
|
||||
|
||||
### Windows
|
||||
In **Powershell**:
|
||||
### Windows (PowerShell)
|
||||
```powershell
|
||||
Invoke-WebRequest -UseBasicParsing "https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/master/install.ps1" | Invoke-Expression
|
||||
```
|
||||
|
||||
### Linux and MacOS:
|
||||
In **Bash**:
|
||||
### Linux/MacOS (Bash)
|
||||
```bash
|
||||
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:
|
||||
```ini
|
||||
[Patch]
|
||||
|
|
@ -50,30 +61,23 @@ xpui.js_repl_8008 = ,${1}56,
|
|||
## 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.
|
||||

|
||||
|
||||
<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:
|
||||

|
||||
|
||||
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.
|
||||
`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.
|
||||
<img src="showcase-images/top-bars.png" alt="img" width="500px">
|
||||
|
||||

|
||||
|
||||
## Auto-uninstall
|
||||
### Windows
|
||||
## Uninstall
|
||||
### Windows (PowerShell)
|
||||
```powershell
|
||||
Invoke-WebRequest -UseBasicParsing "https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/master/uninstall.ps1" | Invoke-Expression
|
||||
```
|
||||
|
||||
## Manual uninstall
|
||||
Remove the dribbblish script with the following commands
|
||||
|
||||
```
|
||||
spicetify config extensions dribbblish.js-
|
||||
spicetify config extensions dribbblish-dynamic.js-
|
||||
```
|
||||
And remove Patch lines you added in config file earlier. Finally, run:
|
||||
```
|
||||
spicetify apply
|
||||
```
|
||||
### Manual Uninstall
|
||||
1. Remove Patch lines you added in config file earlier.
|
||||
2. Run:
|
||||
```
|
||||
spicetify config extensions dribbblish-dynamic.js-
|
||||
spicetify apply
|
||||
```
|
||||
23
Vibrant.min.js
vendored
23
Vibrant.min.js
vendored
|
|
@ -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]);
|
||||
|
|
@ -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", " ");
|
||||
10
install.ps1
10
install.ps1
|
|
@ -45,6 +45,7 @@ if ($PSVersionTable.PSVersion.Major -gt $PSMinVersion) {
|
|||
Write-Done
|
||||
|
||||
$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
|
||||
|
|
@ -57,7 +58,6 @@ if ($PSVersionTable.PSVersion.Major -gt $PSMinVersion) {
|
|||
|
||||
# Download release.
|
||||
$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
|
||||
Invoke-WebRequest -Uri $download_uri -UseBasicParsing -OutFile $zip_file
|
||||
Write-Done
|
||||
|
|
@ -65,7 +65,7 @@ if ($PSVersionTable.PSVersion.Major -gt $PSMinVersion) {
|
|||
# Extract theme from .zip file.
|
||||
Write-Part "EXTRACTING "; Write-Emphasized $zip_file
|
||||
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
|
||||
|
||||
# Remove .zip file.
|
||||
|
|
@ -90,11 +90,9 @@ if ($PSVersionTable.PSVersion.Major -gt $PSMinVersion) {
|
|||
# Installing.
|
||||
Write-Part "INSTALLING";
|
||||
cd $sp_dot_dir
|
||||
Copy-Item dribbblish.js ..\..\Extensions
|
||||
Copy-Item dribbblish-dynamic.js ..\..\Extensions
|
||||
Copy-Item Vibrant.min.js ..\..\Extensions
|
||||
spicetify config extensions default-dynamic.js-
|
||||
spicetify config extensions dribbblish.js extensions dribbblish-dynamic.js extensions Vibrant.min.js
|
||||
spicetify config extensions default-dynamic.js- extensions dribbblish-dynamic.js- extensions dribbblish.js- extensions Vibrant.min.js-
|
||||
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
|
||||
|
|
|
|||
12
install.sh
12
install.sh
|
|
@ -10,13 +10,14 @@ if [ $# -eq 0 ]; then
|
|||
version=$( command curl -sSf "$latest_release_uri" |
|
||||
command grep -Eo "tag_name\": .*" |
|
||||
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
|
||||
else
|
||||
version="${1}"
|
||||
fi
|
||||
|
||||
download_uri="https://github.com/JulienMaille/dribbblish-dynamic-theme/archive/refs/tags/${version}.zip"
|
||||
|
||||
spicetify_install="${SPICETIFY_INSTALL:-$HOME/spicetify-cli/Themes}"
|
||||
|
||||
if [ ! -d "$spicetify_install" ]; then
|
||||
|
|
@ -31,7 +32,7 @@ curl --fail --location --progress-bar --output "$tar_file" "$download_uri"
|
|||
cd "$spicetify_install"
|
||||
|
||||
echo "EXTRACTING $tar_file"
|
||||
unzip -o "$tar_file"
|
||||
unzip -d "$spicetify_install/dribbblish-dynamic-theme-${version}" -o "$tar_file"
|
||||
|
||||
echo "REMOVING $tar_file"
|
||||
rm "$tar_file"
|
||||
|
|
@ -49,10 +50,9 @@ cp -rf "$spicetify_install/dribbblish-dynamic-theme-${version}/." "$sp_dot_dir"
|
|||
echo "INSTALLING"
|
||||
cd "$(dirname "$(spicetify -c)")/Themes/DribbblishDynamic"
|
||||
mkdir -p ../../Extensions
|
||||
cp dribbblish.js ../../Extensions/.
|
||||
cp dribbblish-dynamic.js ../../Extensions/.
|
||||
cp Vibrant.min.js ../../Extensions/.
|
||||
spicetify config extensions dribbblish.js extensions dribbblish-dynamic.js extensions Vibrant.min.js
|
||||
spicetify config extensions default-dynamic.js- extensions dribbblish-dynamic.js- extensions dribbblish.js- extensions Vibrant.min.js-
|
||||
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
|
||||
|
|
|
|||
19
package.json
Normal file
19
package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
BIN
showcase-images/customize-sidebar.png
Normal file
BIN
showcase-images/customize-sidebar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 248 KiB |
BIN
showcase-images/playlist-folders.gif
Normal file
BIN
showcase-images/playlist-folders.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 783 KiB |
BIN
showcase-images/resize-sidebar.png
Normal file
BIN
showcase-images/resize-sidebar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 270 KiB |
|
|
@ -1,162 +1,162 @@
|
|||
[base]
|
||||
text = FFFFFF
|
||||
subtext = F0F0F0
|
||||
sidebar-text = FFFFFF
|
||||
main = 000000
|
||||
sidebar = 1ed760
|
||||
player = 000000
|
||||
card = 000000
|
||||
shadow = 202020
|
||||
selected-row = 797979
|
||||
button = 1ed760
|
||||
button-active = 1ed760
|
||||
button-disabled = 535353
|
||||
tab-active = 166632
|
||||
notification = 1db954
|
||||
notification-error = e22134
|
||||
misc = BFBFBF
|
||||
|
||||
|
||||
[white]
|
||||
text = 363636
|
||||
subtext = 3D3D3D
|
||||
sidebar-text = FFF9F4
|
||||
main = FFF9F4
|
||||
sidebar = FFA789
|
||||
player = FFF9F4
|
||||
card = FFF9F4
|
||||
shadow = d3d3d3
|
||||
selected-row = 6D6D6D
|
||||
button = ff8367
|
||||
button-active = ff8367
|
||||
button-disabled = 535353
|
||||
tab-active = ffdace
|
||||
notification = FFA789
|
||||
notification-error = e22134
|
||||
misc = BFBFBF
|
||||
|
||||
[dark]
|
||||
text = F0F0F0
|
||||
subtext = F0F0F0
|
||||
sidebar-text = 0a0e14
|
||||
main = 0a0e14
|
||||
sidebar = C2D935
|
||||
player = 0a0e14
|
||||
card = 0a0e14
|
||||
shadow = 202020
|
||||
selected-row = DEDEDE
|
||||
button = C2D935
|
||||
button-active = C2D935
|
||||
button-disabled = 535353
|
||||
tab-active = 727d2b
|
||||
notification = C2D935
|
||||
notification-error = e22134
|
||||
misc = BFBFBF
|
||||
|
||||
[dracula]
|
||||
text = f8f8f2
|
||||
subtext = f8f8f2
|
||||
sidebar-text = F0F0F0
|
||||
main = 44475a
|
||||
sidebar = 6272a4
|
||||
player = 44475a
|
||||
card = 6272a4
|
||||
shadow = 000000
|
||||
selected-row = bd93f9
|
||||
button = ffb86c
|
||||
button-active = 8be9fd
|
||||
button-disabled = 535353
|
||||
tab-active = 6272a4
|
||||
notification = bd93f9
|
||||
notification-error = e22134
|
||||
misc = BFBFBF
|
||||
|
||||
[nord-light]
|
||||
text = 2e3440
|
||||
subtext = 3b4252
|
||||
sidebar-text = ECEFF4
|
||||
main = ECEFF4
|
||||
sidebar = 5E81AC
|
||||
player = ECEFF4
|
||||
card = ebcb8b
|
||||
shadow = eceff4
|
||||
selected-row = 4c566a
|
||||
button = 81a1c1
|
||||
button-active = 81a1c1
|
||||
button-disabled = c0c0c0
|
||||
tab-active = ebcb8b
|
||||
notification = a3be8c
|
||||
notification-error = bf616a
|
||||
misc = BFBFBF
|
||||
|
||||
[nord-dark]
|
||||
text = ECEFF4
|
||||
subtext = E5E9F0
|
||||
sidebar-text = 434c5e
|
||||
main = 2e3440
|
||||
sidebar = 88C0D0
|
||||
player = 2e3440
|
||||
card = 2e3440
|
||||
shadow = 2E3440
|
||||
selected-row = D8DEE9
|
||||
button = 81A1C1
|
||||
button-active = 81A1C1
|
||||
button-disabled = 434C5E
|
||||
tab-active = 434C5E
|
||||
notification = A3BE8C
|
||||
notification-error = BF616A
|
||||
misc = BFBFBF
|
||||
|
||||
[purple]
|
||||
text = f1eaff
|
||||
subtext = f1eaff
|
||||
sidebar-text = e0d0ff
|
||||
main = 0A0E14
|
||||
sidebar = 6F3C89
|
||||
player = 0A0E14
|
||||
card = 0A0E14
|
||||
shadow = 3a2645
|
||||
selected-row = EBDFFF
|
||||
button = c76af6
|
||||
button-active = 6F3C89
|
||||
button-disabled = 535353
|
||||
tab-active = 58306D
|
||||
notification = ff9e00
|
||||
notification-error = f61379
|
||||
misc = DEDEDE
|
||||
|
||||
[samourai]
|
||||
text = ebdbb2
|
||||
subtext = ebdbb2
|
||||
sidebar-text = 461217
|
||||
main = 461217
|
||||
sidebar = ebdbb2
|
||||
player = 461217
|
||||
card = 461217
|
||||
shadow = 3a2645
|
||||
selected-row = 909090
|
||||
button = e7a52d
|
||||
button-active = e7a52d
|
||||
button-disabled = 535353
|
||||
tab-active = e7a52d
|
||||
notification = e7a52d
|
||||
notification-error = e22134
|
||||
misc = BFBFBF
|
||||
|
||||
[beach-sunset]
|
||||
text = FFFFFF
|
||||
subtext = F0F0F0
|
||||
sidebar-text = F0F0F0
|
||||
main = 262626
|
||||
sidebar = bd3e3e
|
||||
player = 262626
|
||||
card = 262626
|
||||
shadow = 000000
|
||||
selected-row = d1d6e2
|
||||
button = f1a84f
|
||||
button-active = c98430
|
||||
button-disabled = 535353
|
||||
tab-active = f1a84f
|
||||
notification = c98430
|
||||
notification-error = e22134
|
||||
[base]
|
||||
text = FFFFFF
|
||||
subtext = F0F0F0
|
||||
sidebar-text = FFFFFF
|
||||
main = 000000
|
||||
sidebar = 1ed760
|
||||
player = 000000
|
||||
card = 000000
|
||||
shadow = 202020
|
||||
selected-row = 797979
|
||||
button = 1ed760
|
||||
button-active = 1ed760
|
||||
button-disabled = 535353
|
||||
tab-active = 166632
|
||||
notification = 1db954
|
||||
notification-error = e22134
|
||||
misc = BFBFBF
|
||||
|
||||
|
||||
[white]
|
||||
text = 363636
|
||||
subtext = 3D3D3D
|
||||
sidebar-text = FFF9F4
|
||||
main = FFF9F4
|
||||
sidebar = FFA789
|
||||
player = FFF9F4
|
||||
card = FFF9F4
|
||||
shadow = d3d3d3
|
||||
selected-row = 6D6D6D
|
||||
button = ff8367
|
||||
button-active = ff8367
|
||||
button-disabled = 535353
|
||||
tab-active = ffdace
|
||||
notification = FFA789
|
||||
notification-error = e22134
|
||||
misc = BFBFBF
|
||||
|
||||
[dark]
|
||||
text = F0F0F0
|
||||
subtext = F0F0F0
|
||||
sidebar-text = 0a0e14
|
||||
main = 0a0e14
|
||||
sidebar = C2D935
|
||||
player = 0a0e14
|
||||
card = 0a0e14
|
||||
shadow = 202020
|
||||
selected-row = DEDEDE
|
||||
button = C2D935
|
||||
button-active = C2D935
|
||||
button-disabled = 535353
|
||||
tab-active = 727d2b
|
||||
notification = C2D935
|
||||
notification-error = e22134
|
||||
misc = BFBFBF
|
||||
|
||||
[dracula]
|
||||
text = f8f8f2
|
||||
subtext = f8f8f2
|
||||
sidebar-text = F0F0F0
|
||||
main = 44475a
|
||||
sidebar = 6272a4
|
||||
player = 44475a
|
||||
card = 6272a4
|
||||
shadow = 000000
|
||||
selected-row = bd93f9
|
||||
button = ffb86c
|
||||
button-active = 8be9fd
|
||||
button-disabled = 535353
|
||||
tab-active = 6272a4
|
||||
notification = bd93f9
|
||||
notification-error = e22134
|
||||
misc = BFBFBF
|
||||
|
||||
[nord-light]
|
||||
text = 2e3440
|
||||
subtext = 3b4252
|
||||
sidebar-text = ECEFF4
|
||||
main = ECEFF4
|
||||
sidebar = 5E81AC
|
||||
player = ECEFF4
|
||||
card = ebcb8b
|
||||
shadow = eceff4
|
||||
selected-row = 4c566a
|
||||
button = 81a1c1
|
||||
button-active = 81a1c1
|
||||
button-disabled = c0c0c0
|
||||
tab-active = ebcb8b
|
||||
notification = a3be8c
|
||||
notification-error = bf616a
|
||||
misc = BFBFBF
|
||||
|
||||
[nord-dark]
|
||||
text = ECEFF4
|
||||
subtext = E5E9F0
|
||||
sidebar-text = 434c5e
|
||||
main = 2e3440
|
||||
sidebar = 88C0D0
|
||||
player = 2e3440
|
||||
card = 2e3440
|
||||
shadow = 2E3440
|
||||
selected-row = D8DEE9
|
||||
button = 81A1C1
|
||||
button-active = 81A1C1
|
||||
button-disabled = 434C5E
|
||||
tab-active = 434C5E
|
||||
notification = A3BE8C
|
||||
notification-error = BF616A
|
||||
misc = BFBFBF
|
||||
|
||||
[purple]
|
||||
text = f1eaff
|
||||
subtext = f1eaff
|
||||
sidebar-text = e0d0ff
|
||||
main = 0A0E14
|
||||
sidebar = 6F3C89
|
||||
player = 0A0E14
|
||||
card = 0A0E14
|
||||
shadow = 3a2645
|
||||
selected-row = EBDFFF
|
||||
button = c76af6
|
||||
button-active = 6F3C89
|
||||
button-disabled = 535353
|
||||
tab-active = 58306D
|
||||
notification = ff9e00
|
||||
notification-error = f61379
|
||||
misc = DEDEDE
|
||||
|
||||
[samourai]
|
||||
text = ebdbb2
|
||||
subtext = ebdbb2
|
||||
sidebar-text = 461217
|
||||
main = 461217
|
||||
sidebar = ebdbb2
|
||||
player = 461217
|
||||
card = 461217
|
||||
shadow = 3a2645
|
||||
selected-row = 909090
|
||||
button = e7a52d
|
||||
button-active = e7a52d
|
||||
button-disabled = 535353
|
||||
tab-active = e7a52d
|
||||
notification = e7a52d
|
||||
notification-error = e22134
|
||||
misc = BFBFBF
|
||||
|
||||
[beach-sunset]
|
||||
text = FFFFFF
|
||||
subtext = F0F0F0
|
||||
sidebar-text = F0F0F0
|
||||
main = 262626
|
||||
sidebar = bd3e3e
|
||||
player = 262626
|
||||
card = 262626
|
||||
shadow = 000000
|
||||
selected-row = d1d6e2
|
||||
button = f1a84f
|
||||
button-active = c98430
|
||||
button-disabled = 535353
|
||||
tab-active = f1a84f
|
||||
notification = c98430
|
||||
notification-error = e22134
|
||||
misc = BFBFBF
|
||||
|
|
@ -1,6 +1,4 @@
|
|||
// Hide popover message
|
||||
// document.getElementById("popover-container").style.height = 0;
|
||||
class ConfigMenu {
|
||||
export default class ConfigMenu {
|
||||
/**
|
||||
* @typedef {Object} DribbblishConfigItem
|
||||
* @property {"checkbox" | "select" | "button" | "slider" | "number" | "text" | "time" | "color"} type
|
||||
|
|
@ -29,17 +27,20 @@ class ConfigMenu {
|
|||
|
||||
/**
|
||||
* @callback showChildren
|
||||
* @this {DribbblishConfigItem}
|
||||
* @param {any} value
|
||||
* @returns {Boolean | String[]}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @callback onAppended
|
||||
* @this {DribbblishConfigItem}
|
||||
* @returns {void}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @callback onChange
|
||||
* @this {DribbblishConfigItem}
|
||||
* @param {any} value
|
||||
* @returns {void}
|
||||
*/
|
||||
|
|
@ -49,7 +50,7 @@ class ConfigMenu {
|
|||
|
||||
constructor() {
|
||||
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();
|
||||
|
||||
const container = document.createElement("div");
|
||||
|
|
@ -66,8 +67,8 @@ class ConfigMenu {
|
|||
`;
|
||||
|
||||
document.body.appendChild(container);
|
||||
document.querySelector(".dribbblish-config-close").addEventListener("click", () => DribbblishShared.config.close());
|
||||
document.querySelector(".dribbblish-config-backdrop").addEventListener("click", () => DribbblishShared.config.close());
|
||||
document.querySelector(".dribbblish-config-close").addEventListener("click", () => this.close());
|
||||
document.querySelector(".dribbblish-config-backdrop").addEventListener("click", () => this.close());
|
||||
}
|
||||
|
||||
open() {
|
||||
|
|
@ -138,8 +139,8 @@ class ConfigMenu {
|
|||
.join("\n");
|
||||
options._onChange = options.onChange;
|
||||
options.onChange = (val) => {
|
||||
options._onChange(val);
|
||||
const show = options.showChildren(val);
|
||||
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 = options.children.map((child) => {
|
||||
|
|
@ -147,7 +148,6 @@ class ConfigMenu {
|
|||
});
|
||||
|
||||
this.#config[options.key] = options;
|
||||
this.#config[options.key].value = localStorage.getItem(`dribbblish:config:${options.key}`) ?? JSON.stringify(options.defaultValue);
|
||||
|
||||
if (options.type == "checkbox") {
|
||||
const input = /* html */ `
|
||||
|
|
@ -165,17 +165,19 @@ class ConfigMenu {
|
|||
} else if (options.type == "select") {
|
||||
// Validate
|
||||
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 */ `
|
||||
<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>
|
||||
`;
|
||||
this.addInputHTML({ ...options, input });
|
||||
|
||||
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));
|
||||
});
|
||||
} else if (options.type == "button") {
|
||||
|
|
@ -289,7 +291,7 @@ class ConfigMenu {
|
|||
|
||||
options.children.forEach((child) => this.register(child));
|
||||
|
||||
options.onAppended();
|
||||
options.onAppended.call(options);
|
||||
if (options.fireInitialChange) options.onChange(this.get(options.key));
|
||||
}
|
||||
|
||||
|
|
@ -332,9 +334,12 @@ class ConfigMenu {
|
|||
* @returns {any}
|
||||
*/
|
||||
get(key, defaultValueOverride) {
|
||||
const val = JSON.parse(this.#config[key].value ?? null); // Turn undefined into null because `JSON.parse()` dosen't like undefined
|
||||
if (val == null) return defaultValueOverride ?? this.#config[key].defaultValue;
|
||||
return val;
|
||||
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 || val?.type != this.#config[key]?.type) {
|
||||
localStorage.removeItem(`dribbblish:config:${key}`);
|
||||
return defaultValueOverride ?? this.#config[key].defaultValue;
|
||||
}
|
||||
return val.value;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -343,10 +348,20 @@ class ConfigMenu {
|
|||
* @param {any} 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));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {String} key
|
||||
*/
|
||||
reset(key) {
|
||||
delete this.#config[key].storageCache;
|
||||
localStorage.removeItem(`dribbblish:config:${key}`);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {String} key
|
||||
|
|
@ -361,334 +376,3 @@ class ConfigMenu {
|
|||
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
672
src/js/main.js
Normal 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
138
src/styles/ConfigMenu.scss
Normal 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
114
src/styles/Inputs.scss
Normal 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
17
src/styles/NoAds.scss
Normal 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
|
|
@ -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
|
||||
$configFile = Get-Content "$spicePath\config-xpui.ini"
|
||||
|
|
|
|||
56
webpack.config.js
Normal file
56
webpack.config.js
Normal 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()]
|
||||
}
|
||||
};
|
||||
Loading…
Reference in a new issue