Compare commits

...

63 commits
4.1.1 ... main

Author SHA1 Message Date
Julien
77182042dd
Merge pull request #209 from JulienMaille/dependabot/npm_and_yarn/markdown-it-12.3.2
Bump markdown-it from 12.2.0 to 12.3.2
2022-04-16 13:17:20 +02:00
dependabot[bot]
7492c86124
Bump markdown-it from 12.2.0 to 12.3.2
Bumps [markdown-it](https://github.com/markdown-it/markdown-it) from 12.2.0 to 12.3.2.
- [Release notes](https://github.com/markdown-it/markdown-it/releases)
- [Changelog](https://github.com/markdown-it/markdown-it/blob/master/CHANGELOG.md)
- [Commits](https://github.com/markdown-it/markdown-it/compare/12.2.0...12.3.2)

---
updated-dependencies:
- dependency-name: markdown-it
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-09 17:06:59 +00:00
Julien
55693078f6
Merge pull request #214 from JulienMaille/dependabot/npm_and_yarn/moment-2.29.2
Bump moment from 2.29.1 to 2.29.2
2022-04-09 19:06:17 +02:00
dependabot[bot]
2c8dea7f5d
Bump moment from 2.29.1 to 2.29.2
Bumps [moment](https://github.com/moment/moment) from 2.29.1 to 2.29.2.
- [Release notes](https://github.com/moment/moment/releases)
- [Changelog](https://github.com/moment/moment/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/moment/moment/compare/2.29.1...2.29.2)

---
updated-dependencies:
- dependency-name: moment
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-09 13:32:51 +00:00
Julien Maille
6d23932d0b FIX #190 Album covers not being rounded edge in latest played 2022-04-04 23:55:59 +02:00
Julien Maille
d7e355bf46 FIX: wrong font size for genre element 2022-04-04 23:55:59 +02:00
Julien
c9ec3971ad
Merge pull request #207 from JulienMaille/dependabot/npm_and_yarn/minimist-1.2.6
Bump minimist from 1.2.5 to 1.2.6
2022-04-04 23:53:07 +02:00
dependabot[bot]
bb7017960b
Bump minimist from 1.2.5 to 1.2.6
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-31 23:02:12 +00:00
Julien Maille
1064087b48 Fix color of playlist descriptions FIX #198 2022-03-27 15:32:12 +02:00
Julien Maille
3edfc3fa6b Hightlighting current playing song FIX #189 2022-03-27 15:28:43 +02:00
Julien Maille
239033ad5e Try to fix genre display when playing a podcast #200 2022-03-26 14:48:12 +01:00
Julien Maille
145cfe9b53 Replace deprecated hermes protocol FIX #199 2022-03-26 14:19:13 +01:00
Julien
2e55b23b44
Create stale.yml 2022-03-15 10:18:55 +01:00
Julien Maille
8c5a45c64a temporary remove marketplace integration 2022-03-13 11:12:53 +01:00
Julien Maille
8d9cdf3df5 FIX progress-bar hover color 2022-03-04 08:24:33 +01:00
Julien Maille
9f5a988eca typo 2022-02-28 20:52:36 +01:00
Julien Maille
167e253a86 ok now button colors should look fine 2022-02-27 22:28:13 +01:00
Erik
ba5846bc41
Merge pull request #180 from JulienMaille/fix-blue
fix color shifting to blue for a second (#179)
2022-02-27 21:27:27 +01:00
Julien Maille
d687a13cb7 revert commit, button color are broken without this 2022-02-27 21:22:48 +01:00
Send_Nukez
33134709ed fix color shifting to blue for a second (#179) 2022-02-27 21:13:30 +01:00
Julien Maille
13e1f64054 IMP: installation script 2022-02-27 20:30:29 +01:00
Julien Maille
827c9f3ba0 proper fix for #174 #169 and recent css changes 2022-02-27 20:10:00 +01:00
Julien
f13a3c1ff9
Merge pull request #178 from f1shy-dev/main
Minor fixes
2022-02-27 20:01:29 +01:00
f1shy-dev
14da00bf74 remove the control button triangle 2022-02-27 18:36:21 +00:00
f1shy-dev
23fe7deb15 add a comment to show the issue ID 2022-02-27 18:02:34 +00:00
f1shy-dev
a146134b1d fixes 2022-02-27 17:59:05 +00:00
Send_Nukez
4c134a94a0 update changelog 2022-02-03 19:56:44 +01:00
Erik
14902a060f Merge pull request #165 from Shinyhero36/playback-genre
Add option to display the genre of the current playback
2022-02-03 16:40:22 +00:00
Gérald LEBAN
c45c5faad0 Change description and set default to false 2022-02-03 17:01:52 +01:00
Send_Nukez
162616c2cb hopefully fix actions 2022-02-03 16:18:05 +01:00
Send_Nukez
301a972a53 add @babel/core as devDependency. resolves #164 2022-02-03 15:52:25 +01:00
Gérald LEBAN
290df59e02 Add option to display the genre of the current playback 2022-02-03 13:10:56 +01:00
Send_Nukez
3e2cde1e53 Merge branch 'main' of https://github.com/JulienMaille/dribbblish-dynamic-theme 2022-01-23 13:59:58 +01:00
Julien Maille
eb9a7b1939 FIX: clean uninstall of the theme 2022-01-23 11:12:04 +01:00
Send_Nukez
7d3341b7a7 Merge branch 'main' of https://github.com/JulienMaille/dribbblish-dynamic-theme 2022-01-23 11:07:24 +01:00
Send_Nukez
d78ae80ef1 make sass functions available in js & color playlist visualizer 2022-01-23 10:03:59 +00:00
Send_Nukez
316e7581cf make sass functions available in js & color playlist visualizer 2022-01-23 11:03:40 +01:00
Send_Nukez
36fe1f5a2f fix #147 2022-01-23 10:44:04 +01:00
Julien Maille
273a5b3e7e FIX: Wrong volume slider color on hover #149 2022-01-22 11:52:46 +01:00
Julien Maille
f9c30d230a FIX: automatic path detection when patchin spotify.exe #155 2022-01-16 16:42:25 +01:00
Julien
bc200bfef6
FIX #150 transparent title bar 2021-12-31 23:38:49 +01:00
Send_Nukez
13186767e8 update loading.svg 2021-12-24 21:54:24 +01:00
Send_Nukez
d69c825555 update actions to use node 15 2021-12-24 21:51:24 +01:00
Send_Nukez
56b109cc01 refactor stuff and change notification styles 2021-12-24 20:41:17 +00:00
Send_Nukez
decfe17aa2 add package-lock.json 2021-12-23 23:32:14 +01:00
Send_Nukez
b55a82bf99 fix prettier action 2021-12-23 23:31:07 +01:00
Send_Nukez
c052260a8b make automatic bug reports use <details> 2021-12-20 23:04:46 +01:00
Send_Nukez
ee98ffd134 use loadsh.defaultsDeep to deep merge options 2021-12-19 23:18:06 +01:00
Send_Nukez
305d70fb6d fix user image context menu 2021-12-19 22:15:31 +01:00
Send_Nukez
e50b8b4bb3 remove "spotify" as color selection algorithm 2021-12-18 23:23:34 +01:00
Send_Nukez
ca0d24fd11 refactor loader & icons a bit 2021-12-18 23:20:42 +01:00
Send_Nukez
fa29a12d00 throw an error if the ready event is not fired after 50 tries 2021-12-18 22:59:12 +01:00
Send_Nukez
0ecd8d6166 change custom app tab styles 2021-12-18 20:26:04 +01:00
Send_Nukez
c5fec9cbd1 fix custom search input not being focussed after clicking 2021-12-18 17:03:24 +01:00
Send_Nukez
11eae40857 added ability to hide premium features in addition to ads 2021-12-16 12:19:27 +01:00
Send_Nukez
604ddfbf97 add embedWidgetGenerator modals to custom modal styles 2021-12-15 10:03:17 +01:00
Send_Nukez
1b45d68568 Merge branch 'main' of https://github.com/JulienMaille/dribbblish-dynamic-theme 2021-12-13 21:56:43 +01:00
Julien Maille
2bfa6e55db IMP: patch-dark-mode.ps1 2021-12-13 16:57:12 +01:00
Julien
48c475ee9a
FIX #142
thank you @alixti
2021-12-13 16:43:50 +01:00
Send_Nukez
8517327ee6 Merge branch 'main' of https://github.com/JulienMaille/dribbblish-dynamic-theme 2021-12-10 20:45:17 +01:00
Send_Nukez
e2e6214159 add stable release to marketplace 2021-12-10 19:44:21 +00:00
Send_Nukez
7ac95c23a9 add stable release to marketplace 2021-12-10 20:43:59 +01:00
github-actions
048d007430 Release v4.1.1 2021-12-10 19:40:43 +00:00
40 changed files with 9852 additions and 356 deletions

3
.babelrc Normal file
View file

@ -0,0 +1,3 @@
{
"presets": ["@babel/env"]
}

View file

@ -12,6 +12,7 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
repository: ${{ github.event.repository.full_name }}
ref: ${{ github.event.repository.default_branch }} ref: ${{ github.event.repository.default_branch }}
fetch-depth: 0 fetch-depth: 0

View file

@ -14,11 +14,12 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
repository: ${{ github.event.repository.full_name }}
ref: ${{ github.head_ref }} ref: ${{ github.head_ref }}
fetch-depth: 0 fetch-depth: 0
- name: Prettify code - name: Prettify code
uses: creyD/prettier_action@v4.0 uses: creyD/prettier_action@v4.1.1
with: with:
prettier_options: --write **/*.{js,css} prettier_options: --write **/*.{js,css}
same_commit: True same_commit: True

View file

@ -28,7 +28,7 @@ jobs:
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v2 uses: actions/setup-node@v2
with: with:
node-version: "14" node-version: "15"
- name: Build Webpack - name: Build Webpack
run: | run: |

21
.github/workflows/stale.yml vendored Normal file
View file

@ -0,0 +1,21 @@
name: 'Close stale issues and PRs'
on:
schedule:
- cron: '30 1 * * *'
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v4
with:
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days.'
stale-pr-message: 'This PR is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 15 days.'
close-issue-message: 'This issue was closed because it has been stalled for 7 days with no activity.'
close-pr-message: 'This PR was closed because it has been stalled for 15 days with no activity.'
days-before-issue-stale: 60
days-before-pr-stale: 60
days-before-issue-close: 7
days-before-pr-close: 15
operations-per-run: 100
ascending: true

View file

@ -10,6 +10,7 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
repository: ${{ github.event.repository.full_name }}
ref: ${{ github.head_ref }} ref: ${{ github.head_ref }}
- name: Get commit SHA - name: Get commit SHA
@ -18,7 +19,7 @@ jobs:
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v2 uses: actions/setup-node@v2
with: with:
node-version: "14" node-version: "15"
- name: Test-Build Webpack - name: Test-Build Webpack
run: | run: |

View file

@ -1,5 +1,13 @@
Added:
- Ability to hide premium features in addition to ads
- Ability to show current artist's genres
Fixed: Fixed:
- Weird pill-shaped background on Playlist pages title (#137) - Custom search input not being focussed after clicking
- When changing songs, the color shifts to blue for a second (#179)
Improved: Improved:
- Changed default `sidebar` color to be darker, so you won't get flashed green on startup - Add `embedWidgetGenerator` modals to custom modal styles. (Things like the [spicetify-marketplace](https://github.com/CharlieS1103/spicetify-marketplace) options)
- Changed custom app tab styles to fit in with the theme
- Changed notification styles to fit in with other elements of the theme
- The visualizer icon when playing a song from a playlist is now colored

View file

@ -117,12 +117,12 @@ xpui.js_repl_8008=,`${1}58,
Write-Part "APPLYING"; Write-Part "APPLYING";
$backupVer = $configFile -match "^version" $backupVer = $configFile -match "^version"
$version = ConvertFrom-StringData $backupVer[0] if ($backupVer.Length -gt 0) {
if ($version.version.Length -gt 0) {
spicetify apply spicetify apply
} else { } else {
spicetify backup apply spicetify backup apply
} }
Write-Done
} }
else { else {
Write-Part "`nYour Powershell version is less than "; Write-Emphasized "$PSMinVersion"; Write-Part "`nYour Powershell version is less than "; Write-Emphasized "$PSMinVersion";

View file

@ -1,10 +0,0 @@
{
"name": "Dribbblish Dynamic (Beta)",
"description": "A mod of Dribbblish theme for Spicetify with support for light/dark modes and album art based colors. (Beta Release)",
"branch": "release-beta",
"preview": "https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/main/showcase-images/preview.gif",
"readme": "https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/main/README.md",
"usercss": "user.css",
"schemes": "color.ini",
"include": ["https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/release-beta/dribbblish-dynamic.js"]
}

22
manifest.json.rem Normal file
View file

@ -0,0 +1,22 @@
[
{
"name": "Dribbblish Dynamic",
"description": "A mod of Dribbblish theme for Spicetify with support for light/dark modes and album art based colors. (Beta Release)",
"branch": "release-stable",
"preview": "https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/main/showcase-images/preview.gif",
"readme": "https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/main/README.md",
"usercss": "user.css",
"schemes": "color.ini",
"include": ["https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/release-stable/dribbblish-dynamic.js"]
},
{
"name": "Dribbblish Dynamic (Beta)",
"description": "A mod of Dribbblish theme for Spicetify with support for light/dark modes and album art based colors. (Beta Release)",
"branch": "release-beta",
"preview": "https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/main/showcase-images/preview.gif",
"readme": "https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/main/README.md",
"usercss": "user.css",
"schemes": "color.ini",
"include": ["https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/release-beta/dribbblish-dynamic.js"]
}
]

9109
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,11 +1,14 @@
{ {
"name": "dribbblish-dynamic-theme", "name": "dribbblish-dynamic-theme",
"version": "4.1.0", "version": "4.1.1",
"homepage": "https://github.com/JulienMaille/dribbblish-dynamic-theme", "homepage": "https://github.com/JulienMaille/dribbblish-dynamic-theme",
"bugs": { "bugs": {
"url": "https://github.com/JulienMaille/dribbblish-dynamic-theme/issues" "url": "https://github.com/JulienMaille/dribbblish-dynamic-theme/issues"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.17.0",
"@babel/preset-env": "^7.16.5",
"@babel/register": "^7.16.5",
"@material-icons/svg": "^1.0.22", "@material-icons/svg": "^1.0.22",
"clean-webpack-plugin": "^4.0.0", "clean-webpack-plugin": "^4.0.0",
"sass": "^1.43.5", "sass": "^1.43.5",
@ -14,15 +17,17 @@
"webpack-cli": "^4.9.0" "webpack-cli": "^4.9.0"
}, },
"scripts": { "scripts": {
"build": "webpack --mode=development" "build": "webpack"
}, },
"dependencies": { "dependencies": {
"chroma-js": "^2.1.2", "chroma-js": "^2.1.2",
"colorthief": "^2.3.2", "colorthief": "^2.3.2",
"jquery": "^3.6.0", "jquery": "^3.6.0",
"markdown-it": "^12.2.0", "lodash.debounce": "^4.0.8",
"lodash.defaultsdeep": "^4.6.1",
"markdown-it": "^12.3.2",
"markdown-it-attrs": "^4.1.0", "markdown-it-attrs": "^4.1.0",
"moment": "^2.29.1", "moment": "^2.29.2",
"node-vibrant": "^3.1.6", "node-vibrant": "^3.1.6",
"svgson": "^5.2.1" "svgson": "^5.2.1"
} }

View file

@ -1,10 +1,20 @@
$sp = "$env:APPDATA\Spotify\Spotify.exe" Get-Process -Name Spotify -ErrorAction SilentlyContinue | Stop-Process -Force
Get-Process -Name SpotifyWebHelper -ErrorAction SilentlyContinue | Stop-Process -Force
$sp = spicetify config spotify_path
$sp += "\Spotify.exe"
Copy-Item $sp ($sp + ".backup") Copy-Item $sp ($sp + ".backup")
$bytes = [System.IO.File]::ReadAllBytes($sp); $bytes = [System.IO.File]::ReadAllBytes($sp);
$toRemove = [System.Text.Encoding]::UTF8.GetBytes("force-dark-mode"); $toRemove = [System.Text.Encoding]::UTF8.GetBytes("force-dark-mode");
$sw = [System.Diagnostics.Stopwatch]::StartNew()
for ($i = 0; $i -lt $bytes.Length; $i++) { for ($i = 0; $i -lt $bytes.Length; $i++) {
if ($sw.Elapsed.TotalMilliseconds -ge 1000) {
Write-Progress -Activity "Enabling dark mode in Spotify.exe" -status "Patching binary file $i" -percentComplete ($i / $bytes.Length*100);
$sw.Reset();
$sw.Start();
}
$found = $true $found = $true
for ($j = 0; $j -lt $toRemove.Length; $j++) { for ($j = 0; $j -lt $toRemove.Length; $j++) {
if ($bytes[$i + $j] -ne $toRemove[$j]) { if ($bytes[$i + $j] -ne $toRemove[$j]) {
@ -22,3 +32,8 @@ for ($i = 0; $i -lt $bytes.Length; $i++) {
} }
[System.IO.File]::WriteAllBytes($sp, $bytes); [System.IO.File]::WriteAllBytes($sp, $bytes);
if ( $found ) {
Write-Host "The patch is complete." -ForegroundColor "Green"
} else {
Write-Host "Failed to patch the file." -ForegroundColor "Red"
}

131
src/icons/equaliser.svg Normal file
View file

@ -0,0 +1,131 @@
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22 24'>
<defs>
<style>
@keyframes play {
0% {
transform: scaleY(1);
}
3.3% {
transform: scaleY(0.9583);
}
6.6% {
transform: scaleY(0.9166);
}
9.9% {
transform: scaleY(0.8333);
}
13.3% {
transform: scaleY(0.7083);
}
16.6% {
transform: scaleY(0.5416);
}
19.9% {
transform: scaleY(0.4166);
}
23.3% {
transform: scaleY(0.25);
}
26.6% {
transform: scaleY(0.1666);
}
29.9% {
transform: scaleY(0.125);
}
33.3% {
transform: scaleY(0.125);
}
36.6% {
transform: scaleY(0.1666);
}
39.9% {
transform: scaleY(0.1666);
}
43.3% {
transform: scaleY(0.2083);
}
46.6% {
transform: scaleY(0.2916);
}
49.9% {
transform: scaleY(0.375);
}
53.3% {
transform: scaleY(0.5);
}
56.6% {
transform: scaleY(0.5833);
}
59.9% {
transform: scaleY(0.625);
}
63.3% {
transform: scaleY(0.6666);
}
66.6% {
transform: scaleY(0.6666);
}
69.9% {
transform: scaleY(0.6666);
}
73.3% {
transform: scaleY(0.6666);
}
76.6% {
transform: scaleY(0.7083);
}
79.9% {
transform: scaleY(0.75);
}
83.3% {
transform: scaleY(0.8333);
}
86.6% {
transform: scaleY(0.875);
}
89.9% {
transform: scaleY(0.9166);
}
93.3% {
transform: scaleY(0.9583);
}
96.6% {
transform: scaleY(1);
}
}
svg[icon-type="dribbblish"][icon-name="equaliser"] > rect[i="0"] {
transform-origin: bottom;
animation: play 0.9s -0.51s infinite;
}
svg[icon-type="dribbblish"][icon-name="equaliser"] > rect[i="1"] {
transform-origin: bottom;
animation: play 0.9s infinite;
}
svg[icon-type="dribbblish"][icon-name="equaliser"] > rect[i="2"] {
transform-origin: bottom;
animation: play 0.9s -0.15s infinite;
}
svg[icon-type="dribbblish"][icon-name="equaliser"] > rect[i="3"] {
transform-origin: bottom;
animation: play 0.9s -0.75s infinite;
}
</style>
</defs>
<rect fill="currentColor" i="0" class='cls-1' width='4' height='24'/>
<rect fill="currentColor" i="1" class='cls-1' x='6' width='4' height='24'/>
<rect fill="currentColor" i="2" class='cls-1' x='12' width='4' height='24'/>
<rect fill="currentColor" i="3" class='cls-1' x='18' width='4' height='24'/>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

42
src/icons/loading.svg Normal file
View file

@ -0,0 +1,42 @@
<svg viewBox="0 0 66 66" xmlns="http://www.w3.org/2000/svg">
<defs>
<style>
/* From https://codepen.io/mrrocks/pen/EiplA */
@keyframes loader-rotator {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(270deg);
}
}
@keyframes loader-dash {
0% {
stroke-dashoffset: 187;
}
50% {
stroke-dashoffset: 46.75;
transform: rotate(135deg);
}
100% {
stroke-dashoffset: 187;
transform: rotate(450deg);
}
}
svg[icon-type="dribbblish"][icon-name="loading"] {
animation: loader-rotator 1.4s linear infinite;
}
svg[icon-type="dribbblish"][icon-name="loading"] circle {
stroke-dasharray: 187;
stroke-dashoffset: 0;
transform-origin: center;
animation: loader-dash 1.4s ease-in-out infinite;
}
</style>
</defs>
<circle fill="none" stroke="currentColor" stroke-width="6" stroke-linecap="round" cx="33" cy="33" r="30"></circle>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -1,6 +1,6 @@
import $ from "jquery"; import $ from "jquery";
import { renderMD } from "./Util"; import { renderMD, defaults } from "./Util";
import { icons } from "./Icons"; import { icons } from "./Icons";
export default class ConfigMenu { export default class ConfigMenu {
@ -125,7 +125,7 @@ export default class ConfigMenu {
parent: null parent: null
}; };
// Set Defaults // Set Defaults
options = { ...defaultOptions, ...options }; options = defaults(options, defaultOptions);
if (typeof options.area == "string") options.area = { name: options.area, order: 0 }; if (typeof options.area == "string") options.area = { name: options.area, order: 0 };
options.description = options.description options.description = options.description
.split("\n") .split("\n")

View file

@ -1,7 +1,8 @@
import ConfigMenu from "./ConfigMenu"; import ConfigMenu from "./ConfigMenu";
import Info from "./Info"; import Info from "./Info";
import Loader from "./Loader"; import Loader from "./Loader";
import { icons } from "./Icons"; import Overlay from "./Overlay";
import Icon, { icons } from "./Icons";
export default class Dribbblish { export default class Dribbblish {
/** /**
@ -23,7 +24,10 @@ export default class Dribbblish {
/** @type {Loader} */ /** @type {Loader} */
loader; loader;
/** @type {Icons} */ /** @type {Overlay} */
overlay;
/** @type {Icon} */
icons; icons;
/** @type {Object.<string, listener[]>} */ /** @type {Object.<string, listener[]>} */
@ -36,9 +40,12 @@ export default class Dribbblish {
this.config = new ConfigMenu(); this.config = new ConfigMenu();
this.info = new Info(); this.info = new Info();
this.loader = new Loader(); this.loader = new Loader();
this.overlay = new Overlay();
this.icons = icons; this.icons = icons;
let tries = 0;
const interval = setInterval(() => { const interval = setInterval(() => {
if (++tries > 50) throw new Error("ready timeout");
if (document.querySelector("#main") == null || Spicetify?.showNotification == undefined || !this.info.isReady()) return; if (document.querySelector("#main") == null || Spicetify?.showNotification == undefined || !this.info.isReady()) return;
this.#ready = true; this.#ready = true;
this.emit("ready"); this.emit("ready");

View file

@ -1,3 +1,4 @@
import { defaults } from "./Util";
import { parseSync as parseSVG, stringify as stringifySVG } from "svgson"; import { parseSync as parseSVG, stringify as stringifySVG } from "svgson";
export default class Icons { export default class Icons {
@ -6,6 +7,7 @@ export default class Icons {
/** /**
* @typedef {Object} IconOptions * @typedef {Object} IconOptions
* @property {IconStyle} [style="round"] * @property {IconStyle} [style="round"]
* @property {String} [className=""]
* @property {Number} [size=16] * @property {Number} [size=16]
* @property {Number} [scale=1] * @property {Number} [scale=1]
* @property {String} [fill="currentColor"] * @property {String} [fill="currentColor"]
@ -15,8 +17,8 @@ export default class Icons {
/** @type {Object.<String, Object.<IconStyle, String>>} */ /** @type {Object.<String, Object.<IconStyle, String>>} */
#icons; #icons;
constructor() { constructor(icons) {
this.#icons = process.env.DRIBBBLISH_ICONS; this.#icons = icons ?? process.env.DRIBBBLISH_ICONS;
} }
/** /**
@ -66,32 +68,42 @@ export default class Icons {
/** @type {IconOptions} */ /** @type {IconOptions} */
const defaultOptions = { const defaultOptions = {
style: this.#getDefaultStyle(name), style: this.#getDefaultStyle(name),
className: "",
size: 16, size: 16,
scale: 1, scale: 1,
fill: "currentColor", fill: "currentColor",
base64: false base64: false
}; };
options = { ...defaultOptions, ...options }; options = defaults(options, defaultOptions);
const svg = parseSVG(this.getRawSVG(name, options.style)); const svg = parseSVG(this.getRawSVG(name, options.style));
// Add general / required attributes
svg.attributes["icon-type"] = "dribbblish"; svg.attributes["icon-type"] = "dribbblish";
svg.attributes["icon-name"] = name;
svg.attributes["icon-style"] = options.style; svg.attributes["icon-style"] = options.style;
svg.attributes.fill = options.fill; svg.attributes.fill = options.fill;
svg.attributes.width = options.size; svg.attributes.width = options.size;
svg.attributes.height = options.size; svg.attributes.height = options.size;
// Create CSSStyleDeclaration by creating an element since there is no constructor for it // Add className
const styles = document.createElement("a").style; if (options.className != "") svg.attributes.class = options.className;
// Add Styles
const styles = {};
if (options.scale != 1) { if (options.scale != 1) {
styles.transform = `scale(${options.scale})`; styles["transform"] = `scale(${options.scale})`;
styles.transformOrigin = "center"; styles["transform-origin"] = "center";
} }
const styleStr = Object.entries(styles)
.map(([prop, val]) => `${prop}: ${val};`)
.join(" ");
svg.children = svg.children.map((child) => { svg.children = svg.children.map((child) => {
if (styles.cssText != "") child.attributes.style = styles.cssText; if (styleStr != "") child.attributes.style = styleStr;
return child; return child;
}); });
// Add title
if (options.title != null) { if (options.title != null) {
svg.children.push({ svg.children.push({
name: "title", name: "title",

View file

@ -1,28 +1,18 @@
import Overlay from "./Overlay";
export default class Loader { export default class Loader {
/** @type {HTMLDivElement} */ /** @type {Overlay} */
#container; #overlay;
constructor() { constructor() {
this.#container = document.createElement("div"); this.#overlay = new Overlay();
this.#container.id = "dribbblish-loader";
this.#container.innerHTML = /* html */ `
<div>
<svg width="65px" height="65px" viewBox="0 0 66 66" xmlns="http://www.w3.org/2000/svg">
<circle fill="none" stroke-width="6" stroke-linecap="round" cx="33" cy="33" r="30"></circle>
</svg>
<span>Loading...</span>
</div>
`;
document.body.appendChild(this.#container);
} }
show(text) { show(text) {
this.#container.querySelector("span").innerText = text ?? ""; this.#overlay.show({ icon: "loading", text });
this.#container.setAttribute("active", "");
} }
hide() { hide() {
this.#container.removeAttribute("active"); this.#overlay.hide();
} }
} }

68
src/js/Overlay.js Normal file
View file

@ -0,0 +1,68 @@
import { defaults } from "./Util";
import { icons } from "./Icons";
export default class Overlay {
/**
* @typedef {Object} DribbblishOverlayOptions
* @property {String} icon
* @property {String} text
* @property {DribbblishOverlayColor} color
*/
/**
* @typedef {Object} DribbblishOverlayColor
* @property {String} [icon]
* @property {String} [text]
*/
/** @type {HTMLDivElement} */
#container;
/** @type {HTMLElement} */
#iconElem;
/** @type {HTMLSpanElement} */
#textElem;
constructor() {
this.#container = document.createElement("div");
this.#container.id = "dribbblish-overlay";
this.#container.innerHTML = /* html */ `
<div>
<i></i>
<span>Loading...</span>
</div>
`;
this.#iconElem = this.#container.querySelector("i");
this.#textElem = this.#container.querySelector("span");
document.body.appendChild(this.#container);
}
/**
*
* @param {DribbblishOverlayOptions} options
*/
show(options) {
const defaultOptions = {
icon: "",
text: "",
color: {
icon: "var(--spice-text)",
text: "var(--spice-subtext)"
}
};
options = defaults(options, defaultOptions);
if (options.icon != "" && !options.icon.startsWith("<svg")) options.icon = icons.get(options.icon, { size: 64 });
this.#iconElem.innerHTML = options.icon;
this.#iconElem.style.setProperty("--color", options.color.icon);
this.#textElem.innerHTML = options.text;
this.#textElem.style.setProperty("--color", options.color.text);
this.#container.setAttribute("active", "");
}
hide() {
this.#container.removeAttribute("active");
}
}

13
src/js/SassUtil.js Normal file
View file

@ -0,0 +1,13 @@
export function lightOffset(n, offset) {
return `calc(${n} + ${offset} * var(--is_light))`;
}
export function spiceColor(key, alpha = 1, _lightOffset = 0) {
if (alpha == 1) {
return `var(--spice-${key})`;
} else if (_lightOffset == 0) {
return `rgba(var(--spice-rgb-${key}), ${alpha})`;
} else {
return `rgba(var(--spice-rgb-${key}), ${lightOffset(alpha, _lightOffset)})`;
}
}

View file

@ -1,5 +1,7 @@
import MarkdownIt from "markdown-it"; import MarkdownIt from "markdown-it";
import MarkdownItAttrs from "markdown-it-attrs"; import MarkdownItAttrs from "markdown-it-attrs";
import defaultsDeep from "lodash.defaultsdeep";
import { default as _debounce } from "lodash.debounce";
/** /**
* @callback waitForElCb * @callback waitForElCb
@ -69,22 +71,25 @@ export function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min; return Math.floor(Math.random() * (max - min + 1)) + min;
} }
/**
* @template T
* @param {T[]} arr
* @returns {T}
*/
export function randomFromArray(arr) { export function randomFromArray(arr) {
return arr[Math.floor(Math.random() * arr.length)]; return arr[Math.floor(Math.random() * arr.length)];
} }
export function debounce(func, wait, immediate) { /** @type {_debounce} */
var timeout; export function debounce(fn, wait, opts) {
return function () { return _debounce(fn, wait, opts);
var context = this, }
args = arguments;
var later = function () { /**
timeout = null; * @param {Object} options
if (!immediate) func.apply(context, args); * @param {...Object} defaults
}; * @returns {Object}
var callNow = immediate && !timeout; */
clearTimeout(timeout); export function defaults(options, ...defaults) {
timeout = setTimeout(later, wait); return defaultsDeep(options, ...defaults);
if (callNow) func.apply(context, args);
};
} }

View file

@ -12,6 +12,7 @@ $("html").attr("dribbblish-js-installed", "");
import { waitForElement, copyToClipboard, capitalizeFirstLetter, getClosestToNum, randomFromArray, debounce } from "./Util"; import { waitForElement, copyToClipboard, capitalizeFirstLetter, getClosestToNum, randomFromArray, debounce } from "./Util";
import { default as _Dribbblish } from "./Dribbblish"; import { default as _Dribbblish } from "./Dribbblish";
import "./Folders"; import "./Folders";
import { spiceColor } from "./SassUtil";
// To expose to external scripts // To expose to external scripts
const Dribbblish = new _Dribbblish(); const Dribbblish = new _Dribbblish();
@ -47,8 +48,8 @@ Dribbblish.on("ready", () => {
? { ? {
icon: "settings", icon: "settings",
color: { color: {
fg: "var(--spice-subtext)", fg: spiceColor("subtext"),
bg: "rgba(var(--spice-rgb-subtext), calc(0.1 + var(--is_light) * 0.05))" bg: spiceColor("subtext", 0.1, 0.05)
}, },
order: 999, order: 999,
tooltip: "Open Dribbblish Settings", tooltip: "Open Dribbblish Settings",
@ -76,7 +77,7 @@ Dribbblish.on("ready", () => {
input.setAttribute("spellcheck", "false"); input.setAttribute("spellcheck", "false");
input.addEventListener("click", (e) => { input.addEventListener("click", (e) => {
if (!Spicetify.Platform.History.location.pathname.startsWith("/search")) Spicetify.Platform.History.push(`/search/${input.value}`); if (!Spicetify.Platform.History.location.pathname.startsWith("/search")) Spicetify.Platform.History.push(`/search/${input.value}`);
waitForElement([`[data-testid="search-input"]`], ([defaultSearch]) => { waitForElement([`.main-topBar-topbarContent form[role="search"]`], ([defaultSearch]) => {
input.focus(); input.focus();
}); });
}); });
@ -216,13 +217,28 @@ Dribbblish.on("ready", () => {
}); });
Dribbblish.config.register({ Dribbblish.config.register({
order: 999, area: "Playbar",
type: "checkbox", type: "checkbox",
key: "hideAds", key: "showGenreInfoInPlaybar",
name: "Hide Ads", name: "Show Genre Info in Playbar",
description: `Hide ads / premium features (see: [SpotifyNoPremium](https://github.com/Daksh777/SpotifyNoPremium))`, description: "Show artist's genres in the Playbar",
defaultValue: false, defaultValue: false,
onChange: (val) => $("#main").attr("hide-ads", val) onChange: (val) => $("#main").attr("playbar-genre-info", val)
});
Dribbblish.config.register({
order: 999,
type: "select",
data: { none: "Hide Nothing", ads: "Hide Ads", premium: "Hide Premium Features", both: "Hide Both" },
key: "hideAdsOrPremium",
name: "Hide Ads / Premium Features",
description: `Hide ads / premium features like Fullscreen and Download Buttons (see: [SpotifyNoPremium](https://github.com/Daksh777/SpotifyNoPremium))`,
defaultValue: "none",
onChange: (val) => {
if (val == "none") return $("#main").attr("hide-ads", null);
if (val == "both") return $("#main").attr("hide-ads", "ads premium");
$("#main").attr("hide-ads", val);
}
}); });
waitForElement([".main-rootlist-rootlist", ".main-rootlist-wrapper > :nth-child(2) > :first-child", "#spicetify-show-list"], ([rootlist]) => { waitForElement([".main-rootlist-rootlist", ".main-rootlist-wrapper > :nth-child(2) > :first-child", "#spicetify-show-list"], ([rootlist]) => {
@ -389,25 +405,24 @@ Dribbblish.on("ready", () => {
<!-- Leave the lines below as they are --> <!-- Leave the lines below as they are -->
--- ---
### Info for Contributors: <details>
<summary>Info for Contributors</summary>
**Versions** **Versions**
${Dribbblish.config.getOptions("aboutDribbblishInfo").description} ${Dribbblish.config.getOptions("aboutDribbblishInfo").description.split("\n").join("\n ")}
**Extensions** **Extensions**
${$(`script[src^="extensions/"]`) ${$(`script[src^="extensions/"]`)
.toArray() .toArray()
.map((e) => `- ${e.src.split("/").slice(-1)[0]}`) .map((e) => `- ${e.src.split("/").slice(-1)[0]}`)
.join("\n")} .join("\n ")}
**Settings** **Settings**
\`\`\`json \`\`\`json
${JSON.stringify(Dribbblish.config.export(), null, 4)} ${JSON.stringify(Dribbblish.config.export(), null, 4).split("\n").join("\n ")}
\`\`\` \`\`\`
` </details>
.split("\n") `.replace(/^ {12}/gm, "");
.map((line) => line.replace(/^ {16}/, ""))
.join("\n");
const reportURL = new URL("https://github.com/JulienMaille/dribbblish-dynamic-theme/issues/new"); const reportURL = new URL("https://github.com/JulienMaille/dribbblish-dynamic-theme/issues/new");
reportURL.searchParams.set("labels", "bug"); reportURL.searchParams.set("labels", "bug");
@ -445,8 +460,13 @@ Dribbblish.on("ready", () => {
/* js */ /* js */
async function getAlbumRelease(uri) { async function getAlbumRelease(uri) {
const info = await Spicetify.CosmosAsync.get(`hm://album/v1/album-app/album/${uri}/desktop`); const info = await Spicetify.CosmosAsync.get(`https://api.spotify.com/v1/albums/${uri}`);
return { year: info.year, month: (info.month ?? 1) - 1, day: info.day ?? 1 }; return { year: info.release_date };
}
async function getGenres(uri) {
const res = await Spicetify.CosmosAsync.get(`https://api.spotify.com/v1/artists/${uri}`);
return res.genres.slice(0, 3);
} }
function isLight(hex) { function isLight(hex) {
@ -545,11 +565,10 @@ Dribbblish.on("ready", () => {
Algorithm of selecting colors from the albumart Algorithm of selecting colors from the albumart
- **Colorthief [(see)](https://lokeshdhakar.com/projects/color-thief/):** Gets more fitting colors - **Colorthief [(see)](https://lokeshdhakar.com/projects/color-thief/):** Gets more fitting colors
- **Vibrant [(see)](https://jariz.github.io/vibrant.js/):** Gets more vibrant colors *(was the default up to v3.1.1)* - **Vibrant [(see)](https://jariz.github.io/vibrant.js/):** Gets more vibrant colors *(was the default up to v3.1.1)*
- **Spotify:** Basically Vibrant but internal and without support of local files
- **Static:** Select a static color to be used - **Static:** Select a static color to be used
{.muted} {.muted}
`, `,
data: { spotify: "Spotify", colorthief: "Colorthief", vibrant: "Vibrant", static: "Static" }, data: { colorthief: "Colorthief", vibrant: "Vibrant", static: "Static" },
defaultValue: "colorthief", defaultValue: "colorthief",
onChange: () => updateColors(), onChange: () => updateColors(),
showChildren: (val) => { showChildren: (val) => {
@ -733,8 +752,19 @@ Dribbblish.on("ready", () => {
} }
const albumInfoSpan = document.getElementById("main-trackInfo-year"); const albumInfoSpan = document.getElementById("main-trackInfo-year");
let album_uri = Spicetify.Player.data.track.metadata.album_uri; if (!document.getElementById("main-trackInfo-genre")) {
let bgImage = Spicetify.Player.data.track.metadata.image_url; const el = document.createElement("div");
el.classList.add("main-trackInfo-release", "standalone-ellipsis-one-line", "main-type-finale");
el.setAttribute("as", "div");
el.id = "main-trackInfo-genre";
document.querySelector(".main-trackInfo-container").append(el);
}
const genreInfoSpan = document.getElementById("main-trackInfo-genre");
let track = Spicetify.Player.data.track;
let album_uri = track.metadata.album_uri;
let artist_uri = track.metadata.artist_uri;
let bgImage = track.metadata.image_url;
if (bgImage === undefined) { if (bgImage === undefined) {
bgImage = "/images/tracklist-row-song-fallback.svg"; bgImage = "/images/tracklist-row-song-fallback.svg";
} }
@ -745,22 +775,37 @@ Dribbblish.on("ready", () => {
const albumLinkElem = /* html */ ` const albumLinkElem = /* html */ `
<span> <span>
<span draggable="true"> <span draggable="true">
<a draggable="false" dir="auto" href="${album_uri}">${Spicetify.Player.data.track.metadata.album_title}</a> <a draggable="false" dir="auto" href="${album_uri}">${track.metadata.album_title}</a>
</span> </span>
</span> </span>
`; `;
const albumDateElem = /* html */ `<span> • <span title="${albumDate.format("L")}">${albumDate.format(moment().diff(albumDate, "months") <= 6 ? "MMM YYYY" : "YYYY")}</span></span>`; const albumDateElem = /* html */ `<span> • <span title="${albumDate.format("L")}">${albumDate.format(moment().diff(albumDate, "months") <= 6 ? "MMM YYYY" : "YYYY")}</span></span>`;
albumInfoSpan.innerHTML = `${albumLinkElem}${albumDateElem}`; albumInfoSpan.innerHTML = `${albumLinkElem}${albumDateElem}`;
} else if (Spicetify.Player.data.track.uri.includes("spotify:episode")) {
let genres = "";
if (!album_uri.includes("spotify:episode")) {
genres = await getGenres(artist_uri.replace("spotify:artist:", ""));
}
genreInfoSpan.innerHTML = /* html */ `
<span>
<span draggable="true">
<span draggable="false" dir="auto">${genres.join(", ")}</span>
</span>
</span>
`;
} else if (track.uri.includes("spotify:episode")) {
// podcast // podcast
bgImage = bgImage.replace("spotify:image:", "https://i.scdn.co/image/"); bgImage = bgImage.replace("spotify:image:", "https://i.scdn.co/image/");
albumInfoSpan.innerHTML = Spicetify.Player.data.track.metadata.album_title; albumInfoSpan.innerHTML = track.metadata.album_title;
} else if (Spicetify.Player.data.track.metadata.is_local == "true") { genreInfoSpan.innerHTML = "";
} else if (track.metadata.is_local == "true") {
// local file // local file
albumInfoSpan.innerHTML = Spicetify.Player.data.track.metadata.album_title; albumInfoSpan.innerHTML = track.metadata.album_title;
} else if (Spicetify.Player.data.track.provider == "ad") { genreInfoSpan.innerHTML = "";
} else if (track.provider == "ad") {
// ad // ad
albumInfoSpan.innerHTML = "Advertisement"; albumInfoSpan.innerHTML = "Advertisement";
genreInfoSpan.innerHTML = "";
return; return;
} else { } else {
// When clicking a song from the homepage, songChange is fired with half empty metadata // When clicking a song from the homepage, songChange is fired with half empty metadata
@ -780,19 +825,13 @@ Dribbblish.on("ready", () => {
$("html").css("--image-brightness", getImageLightness(img) / 255); $("html").css("--image-brightness", getImageLightness(img) / 255);
let color = "#509bf5";
if (img.complete) { if (img.complete) {
let color = "#1ed760";
const colorSelectionAlgorithm = Dribbblish.config.get("colorSelectionAlgorithm"); const colorSelectionAlgorithm = Dribbblish.config.get("colorSelectionAlgorithm");
const colorSelectionMode = Dribbblish.config.get("colorSelectionMode"); const colorSelectionMode = Dribbblish.config.get("colorSelectionMode");
let palette = {}; let palette = {};
if (colorSelectionAlgorithm == "spotify") { if (colorSelectionAlgorithm == "colorthief") {
const swatches = await Spicetify.colorExtractor(Spicetify.Player.data.track.uri);
for (const col of ["VIBRANT", "VIBRANT_NON_ALARMING", "PROMINENT", "DARK_VIBRANT", "LIGHT_VIBRANT", "DESATURATED"]) {
const c = chroma(swatches[col]);
palette[c.luminance()] = c;
}
} else if (colorSelectionAlgorithm == "colorthief") {
palette = Object.fromEntries([colorThief.getColor(img), ...colorThief.getPalette(img, 24, 5)].map((c) => chroma(c)).map((c) => [c.luminance(), c])); palette = Object.fromEntries([colorThief.getColor(img), ...colorThief.getPalette(img, 24, 5)].map((c) => chroma(c)).map((c) => [c.luminance(), c]));
} else if (colorSelectionAlgorithm == "vibrant") { } else if (colorSelectionAlgorithm == "vibrant") {
const swatches = await new Promise((resolve, reject) => new Vibrant(img, 5).getPalette().then(resolve).catch(reject)); const swatches = await new Promise((resolve, reject) => new Vibrant(img, 5).getPalette().then(resolve).catch(reject));
@ -818,10 +857,10 @@ Dribbblish.on("ready", () => {
const wantedLuminance = $("html").css("--is_light") == "1" ? Dribbblish.config.get("lightModeLuminance") : Dribbblish.config.get("darkModeLuminance"); const wantedLuminance = $("html").css("--is_light") == "1" ? Dribbblish.config.get("lightModeLuminance") : Dribbblish.config.get("darkModeLuminance");
color = palette[getClosestToNum(Object.keys(palette), wantedLuminance)].hex(); color = palette[getClosestToNum(Object.keys(palette), wantedLuminance)].hex();
} }
}
updateColors(false, color); updateColors(false, color);
} }
}
var coverListener; var coverListener;
function registerCoverListener() { function registerCoverListener() {
@ -852,7 +891,7 @@ Dribbblish.on("ready", () => {
fetch("https://api.github.com/repos/JulienMaille/dribbblish-dynamic-theme/releases/latest") fetch("https://api.github.com/repos/JulienMaille/dribbblish-dynamic-theme/releases/latest")
.then((response) => response.json()) .then((response) => response.json())
.then((data) => { .then((data) => {
Dribbblish.info.set("dribbblish-update", data.tag_name > process.env.DRIBBBLISH_VERSION ? { text: `v${data.tag_name}`, tooltip: "Nev Dribbblish version available", icon: "palette", onClick: () => window.open("https://github.com/JulienMaille/dribbblish-dynamic-theme/releases/latest", "_blank") } : null); Dribbblish.info.set("dribbblish-update", data.tag_name > process.env.DRIBBBLISH_VERSION ? { text: `v${data.tag_name}`, tooltip: "New Dribbblish version available", icon: "palette", onClick: () => window.open("https://github.com/JulienMaille/dribbblish-dynamic-theme/releases/latest", "_blank") } : null);
}) })
.catch(console.error); .catch(console.error);

View file

@ -44,16 +44,3 @@ $props-to-transition: ("sidebar", "main", "text", "button");
transition: all var(--song-transition-speed) linear; transition: all var(--song-transition-speed) linear;
transition-property: $props; transition-property: $props;
} }
// $key: the key of the color, e.g. "main"
// $alpha: tha alpha in rgba()
// $light-offset: added when in light mode
@function spiceColor($key, $alpha: 1, $light-offset: 0) {
@if $alpha == 1 {
@return var(--spice-#{$key});
} @else if $light-offset == 0 {
@return rgba(var(--spice-rgb-#{$key}), $alpha);
} @else {
@return rgba(var(--spice-rgb-#{$key}), lightOffset($alpha, $light-offset));
}
}

View file

@ -23,16 +23,13 @@
z-index: 1; z-index: 1;
position: relative; position: relative;
width: clamp(500px, 50%, 650px); width: clamp(500px, 50%, 650px);
background-color: spiceColor("main", 0.95);
backdrop-filter: blur(3px);
padding: 20px 15px; padding: 20px 15px;
border-radius: var(--main-corner-radius);
display: flex; display: flex;
gap: 8px; gap: 8px;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@include spiceShadow(); @include spiceGlass();
> h2 { > h2 {
font-size: 32px; font-size: 32px;

View file

@ -1,13 +1,6 @@
.connect-device-list-container { .connect-device-list-container {
$border-color: spiceColor("subtext", 0.1, 0.1);
border: 1px solid $border-color;
border-radius: var(--main-corner-radius);
padding: 8px; padding: 8px;
background-color: spiceColor("main", 0.75, -0.1) !important; @include spiceGlass(lightOffset(0.8, -0.1));
backdrop-filter: blur(10px);
border-radius: var(--main-corner-radius);
@include spiceShadow();
&::before { &::before {
display: none; display: none;

View file

@ -1,8 +1,7 @@
.main-contextMenu-menu { .main-contextMenu-menu {
$border-color: spiceColor("subtext", 0.1, 0.1); position: relative;
overflow: visible;
background-color: transparent !important; background-color: transparent !important;
border: 1px solid $border-color;
border-radius: var(--main-corner-radius); border-radius: var(--main-corner-radius);
padding: 8px; padding: 8px;
@ -11,10 +10,7 @@
content: ""; content: "";
position: absolute; position: absolute;
inset: 0px; inset: 0px;
background-color: spiceColor("main", 0.75, -0.1) !important; @include spiceGlass(lightOffset(0.8, -0.1));
backdrop-filter: blur(10px);
border-radius: var(--main-corner-radius);
@include spiceShadow();
} }
.main-contextMenu-menuItem { .main-contextMenu-menuItem {
@ -32,7 +28,7 @@
&::after { &::after {
left: 8px; left: 8px;
right: 8px; right: 8px;
border-color: $border-color; border-color: spiceColor("subtext", 0.1, 0.1);
} }
} }
} }

View file

@ -0,0 +1,32 @@
.main-topBar-topbarContent {
height: 100%;
& > nav[class*="-tabBar"][class*="-tabBar-nav"] {
height: 100%;
& > ul {
display: flex;
gap: 8px;
height: 100%;
li[class*="-tabBar-headerItem"] {
height: 100%;
& > a {
display: flex;
align-items: center;
justify-content: center;
margin: 0px;
height: 100%;
padding: 0px 8px;
border-radius: var(--sidebar-icons-border-radius);
& > span {
line-height: 13px;
@include spiceFont("glue", 14px, "Medium");
}
}
}
}
}
}

View file

@ -1,4 +1,4 @@
button.main-button { .main-button {
&-primary { &-primary {
$color: spiceColor("subtext"); $color: spiceColor("subtext");
color: $color; color: $color;

View file

@ -1,75 +0,0 @@
// From https://codepen.io/mrrocks/pen/EiplA
@use "sass:math";
$offset: 187;
$duration: 1.4s;
#dribbblish-loader {
z-index: 999999;
position: fixed;
inset: 0px;
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
opacity: 0;
transition: opacity 1s ease-in;
color: spiceColor("subtext");
@include spiceFont("glue", 32px, "Bold");
&[active] {
opacity: 1;
pointer-events: all;
}
&::before {
z-index: -1;
content: "";
position: absolute;
inset: 0px;
background-color: spiceColor("main", 0.9, -0.1);
backdrop-filter: blur(10px);
}
& > div {
display: flex;
flex-direction: column;
gap: 16px;
align-items: center;
svg {
animation: rotator $duration linear infinite;
circle {
stroke: spiceColor("text");
stroke-dasharray: $offset;
stroke-dashoffset: 0;
transform-origin: center;
animation: dash $duration ease-in-out infinite;
}
}
}
}
@keyframes rotator {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(270deg);
}
}
@keyframes dash {
0% {
stroke-dashoffset: $offset;
}
50% {
stroke-dashoffset: math.div($offset, 4);
transform: rotate(135deg);
}
100% {
stroke-dashoffset: $offset;
transform: rotate(450deg);
}
}

1
src/styles/Lyrics.scss Normal file
View file

@ -0,0 +1 @@
// TODO: improve default lyrics styles when the classes are mapped `see: https://github.com/JulienMaille/dribbblish-dynamic-theme/issues/144`

View file

@ -5,15 +5,12 @@
.GenericModal { .GenericModal {
z-index: 1; z-index: 1;
position: relative; position: relative;
background-color: spiceColor("main", 0.95);
backdrop-filter: blur(3px);
padding: 24px 16px; padding: 24px 16px;
border-radius: var(--main-corner-radius);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@include spiceShadow(); @include spiceGlass();
// Popups (`Spicetify.PopupModal.display()`) // Popups (`Spicetify.PopupModal.display()`)
generic-modal & { generic-modal & {
@ -30,13 +27,18 @@
} }
} }
.main-trackCreditsModal-container { .main- {
&trackCreditsModal,
&embedWidgetGenerator {
&-container {
display: flex; display: flex;
gap: 16px; gap: 16px;
background-color: transparent; background-color: transparent;
width: 100%; width: 100%;
box-shadow: none;
}
.main-trackCreditsModal-header { &-header {
display: flex; display: flex;
gap: 16px; gap: 16px;
padding: 0px; padding: 0px;
@ -56,9 +58,10 @@
} }
} }
.main-trackCreditsModal-mainSection { &-mainSection {
padding: 0px 26px; padding: 0px 26px;
} }
} }
} }
}
} }

View file

@ -1,17 +1,31 @@
#main[hide-ads="true"] { // Hide ads
/* Remove upgrade button*/ #main[hide-ads~="ads"] {
.main-topBar-UpgradeButton { // Remove ad placeholder in main screen
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 { .main-leaderboardComponent-container {
display: none; display: none;
} }
} }
// Hide premium features
#main[hide-ads~="premium"] {
// Remove upgrade button
.main-topBar-UpgradeButton {
display: none;
}
// Remove upgrade to premium button in user menu
// prettier-ignore
.main-contextMenu-menuItemButton[href="https://www.spotify.com/premium/"] {
display: none;
}
// Hide fullscreen button
.volume-bar + .control-button {
display: none;
}
// Hide download buttons
.main-actionBar-ActionBarRow button[role="switch"] {
display: none;
}
}

42
src/styles/Overlay.scss Normal file
View file

@ -0,0 +1,42 @@
#dribbblish-overlay {
z-index: 999999;
position: fixed;
inset: 0px;
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
opacity: 0;
transition: opacity 1s ease-in;
color: spiceColor("subtext");
@include spiceFont("glue", 32px, "Bold");
&[active] {
opacity: 1;
pointer-events: all;
}
&::before {
z-index: -1;
content: "";
position: absolute;
inset: 0px;
background-color: spiceColor("main", 0.9, -0.1);
backdrop-filter: blur(10px);
}
& > div {
display: flex;
flex-direction: column;
gap: 16px;
align-items: center;
& > * {
color: var(--color);
&:empty {
display: none;
}
}
}
}

View file

@ -1,50 +0,0 @@
.GenericModal__overlay {
backdrop-filter: blur(3px) brightness(60%);
background-color: transparent;
.GenericModal {
z-index: 1;
position: relative;
width: clamp(500px, 50%, 650px);
background-color: spiceColor("main", 0.95);
backdrop-filter: blur(3px);
padding: 24px 16px;
border-radius: var(--main-corner-radius);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
@include spiceShadow();
.main-trackCreditsModal-container {
display: flex;
gap: 16px;
background-color: transparent;
width: 100%;
.main-trackCreditsModal-header {
display: flex;
gap: 16px;
padding: 0px;
width: 100%;
border: none;
h1 {
text-align: center;
width: 100%;
@include spiceFont("glue", 2em, "Bold");
}
button {
position: absolute;
top: 16px;
right: 16px;
}
}
.main-trackCreditsModal-mainSection {
padding: 0px 26px;
}
}
}
}

View file

@ -3,11 +3,14 @@
@return #{"invert("}$v#{")"}; @return #{"invert("}$v#{")"};
} }
// returns $n and adds $offset when the theme is light
@function lightOffset($n, $offset) {
@return calc($n + $offset * var(--is_light));
}
@mixin spiceShadow() { @mixin spiceShadow() {
box-shadow: 0px 0px 8px spiceColor("subtext", 0.1, 0.1); box-shadow: 0px 0px 8px spiceColor("subtext", 0.1, 0.1);
} }
@mixin spiceGlass($opacity: 0.95) {
background-color: spiceColor("main", $opacity);
backdrop-filter: blur(10px);
border: 1px solid spiceColor("subtext", 0.1, 0.1);
border-radius: var(--main-corner-radius);
@include spiceShadow();
}

View file

@ -7,10 +7,12 @@
@import "NoAds"; @import "NoAds";
@import "Markdown"; @import "Markdown";
@import "Info"; @import "Info";
@import "Loader";
@import "Modals"; @import "Modals";
@import "ConnectDeviceList"; @import "ConnectDeviceList";
@import "Lyrics";
@import "Icons"; @import "Icons";
@import "Overlay";
@import "CustomAppTabBar";
:root { :root {
--bar-height: 70px; --bar-height: 70px;
@ -20,7 +22,7 @@
--scrollbar-vertical-size: 8px; --scrollbar-vertical-size: 8px;
--cover-border-radius: 8px; --cover-border-radius: 8px;
--playbar-movement-anim-speed: 0.5s; --playbar-movement-anim-speed: 0.5s;
--image-radius: 10px; --image-radius: 4px;
--sidebar-icons-border-radius: 50vh; // 50vh = round / pill --sidebar-icons-border-radius: 50vh; // 50vh = round / pill
--song-transition-speed: 3s; --song-transition-speed: 3s;
--is_dark: calc(1 - var(--is_light)); --is_dark: calc(1 - var(--is_light));
@ -133,13 +135,27 @@
box-shadow: 0 4px 20px #21212130; box-shadow: 0 4px 20px #21212130;
} }
.main-trackList-rowMarker {
color: spiceColor("text");
}
.main-trackList-playingIcon { .main-trackList-playingIcon {
-webkit-mask-image: url(icon64("equaliser", '{"size": 14}'));
background-color: currentColor;
content-visibility: hidden;
image-rendering: pixelated; image-rendering: pixelated;
filter: grayscale(1); }
.main-trackList-trackListRowGrid {
background-color: var(--spice-main);
}
.main-trackList-active {
background-color: spiceColor("selected-row", 0.25) !important;
} }
.main-trackList-trackListRow:hover { .main-trackList-trackListRow:hover {
background-color: spiceColor("selected-row", 0.2) !important; background-color: spiceColor("selected-row", 0.15) !important;
} }
.main-trackList-trackListRow:focus-within, .main-trackList-trackListRow:focus-within,
@ -154,6 +170,11 @@ span.artist-artistVerifiedBadge-badge svg > path:last-of-type {
fill: spiceColor("text"); fill: spiceColor("text");
} }
/* Playlist text color */
.main-entityHeader-subtitle.main-entityHeader-gra {
color: spiceColor("subtext");
}
/* Full window artist background */ /* Full window artist background */
.main-entityHeader-background.main-entityHeader-gradient { .main-entityHeader-background.main-entityHeader-gradient {
opacity: 0.3; opacity: 0.3;
@ -555,6 +576,7 @@ html.sidebar-hide-text .GlueDropTarget span {
.progress-bar { .progress-bar {
--progress-bar-height: 2px; --progress-bar-height: 2px;
--is-active-fg-color: #{spiceColor("button-active")};
--fg-color: #{spiceColor("button")}; --fg-color: #{spiceColor("button")};
--bg-color: #{spiceColor("text", 0.2)}; --bg-color: #{spiceColor("text", 0.2)};
} }
@ -594,13 +616,8 @@ html.sidebar-hide-text .GlueDropTarget span {
text-align: center; text-align: center;
white-space: nowrap; white-space: nowrap;
pointer-events: none; pointer-events: none;
border: 1px solid spiceColor("subtext", 0.1, 0.1);
border-radius: var(--main-corner-radius);
color: spiceColor("subtext"); color: spiceColor("subtext");
background-color: spiceColor("main", 0.75, -0.1) !important; @include spiceGlass();
backdrop-filter: blur(10px);
border-radius: var(--main-corner-radius);
@include spiceShadow();
} }
.minimal-player .player-controls__buttons { .minimal-player .player-controls__buttons {
@ -655,13 +672,17 @@ html.sidebar-hide-text .GlueDropTarget span {
} }
#main-trackInfo-year { #main-trackInfo-year {
display: block;
#main[playbar-album-info="false"] & { #main[playbar-album-info="false"] & {
display: none; display: none;
} }
} }
#main-trackInfo-genre {
#main[playbar-genre-info="false"] & {
display: none;
}
}
.main-topBar-topbarContent .main-playButton-PlayButton { .main-topBar-topbarContent .main-playButton-PlayButton {
--size: 35px !important; --size: 35px !important;
} }
@ -800,6 +821,10 @@ li.GlueDropTarget {
gap: 8px; gap: 8px;
max-width: unset; max-width: unset;
padding: 16px 32px !important; padding: 16px 32px !important;
& > * {
height: 100%;
}
} }
/** Custom elements */ /** Custom elements */
@ -1268,13 +1293,14 @@ html.right-expanded-cover.buddyfeed-visible .main-coverSlotExpanded-container {
} }
} }
// Fix lyrics-plus having a scrollbar when top-bar is `solid` or `transparent` // Fix main-view-containers having a scrollbar when top-bar is `solid` or `transparent`
.lyrics-lyricsContainer-LyricsContainer {
height: 100% !important;
}
.main-view-container__scroll-node-child { .main-view-container__scroll-node-child {
height: 100%; height: 100%;
padding-bottom: 0px; padding-bottom: 0px;
& > * {
height: 100% !important;
}
} }
// Hide default Spotify "Offline" notice // Hide default Spotify "Offline" notice
@ -1282,6 +1308,14 @@ html.right-expanded-cover.buddyfeed-visible .main-coverSlotExpanded-container {
display: none; display: none;
} }
// Notifications
.main-notificationBubbleContainer-NotificationBubbleContainer {
.main-notificationBubble-NotificationBubble {
color: spiceColor("subtext");
@include spiceGlass();
}
}
// ! WORKAROUNDS / TEMP FIXES // ! WORKAROUNDS / TEMP FIXES
// Spotify UI breaks after advertisements #63 // Spotify UI breaks after advertisements #63
canvas[width="250"][height="250"] { canvas[width="250"][height="250"] {
@ -1299,3 +1333,20 @@ canvas[width="250"][height="250"] {
display: flex; display: flex;
justify-content: center; justify-content: center;
} }
// fix albumn name font size (#171)
.main-trackInfo-release {
font-size: 10px;
}
// fix play buttons color going green (#174)
.encore-bright-accent-set {
--background-highlight: var(--spice-button) !important;
--background-press: var(--spice-button-active) !important;
color: var(--spice-sidebar-text);
}
// hide the triangle under the connect button
.control-button--active::after {
display: none;
}

View file

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

View file

@ -6,7 +6,7 @@ set -e
echo "UN-INSTALLING" echo "UN-INSTALLING"
cd "$(dirname "$(spicetify -c)")" cd "$(dirname "$(spicetify -c)")"
spicetify config current_theme " " extensions dribbblish-dynamic.js- spicetify config current_theme "SpicetifyDefault" extensions dribbblish-dynamic.js-
echo "UN-PATCHING" echo "UN-PATCHING"
if cat config-xpui.ini | grep -o '\[Patch\]'; then if cat config-xpui.ini | grep -o '\[Patch\]'; then

View file

@ -1,14 +1,17 @@
const webpack = require("webpack"); import webpack from "webpack";
const sass = require("sass"); import sass from "sass";
const { CleanWebpackPlugin } = require("clean-webpack-plugin"); import { CleanWebpackPlugin } from "clean-webpack-plugin";
const path = require("path"); import path from "path";
const fs = require("fs"); import fs from "fs";
const icons = {}; import Icons from "./src/js/Icons";
import { lightOffset, spiceColor } from "./src/js/SassUtil";
const _icons = {};
function addIcon(name, style, path) { function addIcon(name, style, path) {
name = name.replace(/_/g, "-"); name = name.replace(/_/g, "-");
if (!icons.hasOwnProperty(name)) icons[name] = {}; if (!_icons.hasOwnProperty(name)) _icons[name] = {};
icons[name][style] = fs.readFileSync(path, { encoding: "utf8" }); _icons[name][style] = fs.readFileSync(path, { encoding: "utf8" });
} }
// Add Material Icons // Add Material Icons
let iconDir = path.resolve(__dirname, "node_modules/@material-icons/svg/svg"); let iconDir = path.resolve(__dirname, "node_modules/@material-icons/svg/svg");
@ -23,8 +26,11 @@ for (const icon of fs.readdirSync(iconDir)) {
addIcon(icon.replace(/\..*?$/, ""), "custom", path.resolve(iconDir, icon)); addIcon(icon.replace(/\..*?$/, ""), "custom", path.resolve(iconDir, icon));
} }
const icons = new Icons(_icons);
/** @type {import('webpack').Configuration} */ /** @type {import('webpack').Configuration} */
module.exports = { const config = {
mode: "development",
entry: [path.resolve(__dirname, "src/js/main.js"), path.resolve(__dirname, "src/styles/main.scss"), path.resolve(__dirname, "src/styles/Colors.scss")], entry: [path.resolve(__dirname, "src/js/main.js"), path.resolve(__dirname, "src/styles/main.scss"), path.resolve(__dirname, "src/styles/Colors.scss")],
output: { output: {
path: path.resolve(__dirname, "dist"), path: path.resolve(__dirname, "dist"),
@ -50,9 +56,20 @@ module.exports = {
sourceMap: true, sourceMap: true,
sassOptions: { sassOptions: {
functions: { functions: {
"lightOffset($n, $offset)": (n, offset) => {
return new sass.types.String(lightOffset(n.getValue(), offset.getValue()));
},
"spiceColor($key, $alpha: 1, $light-offset: 0)": (key, alpha, _lightOffset) => {
return new sass.types.String(spiceColor(key.getValue(), alpha.getValue(), _lightOffset.getValue()));
},
"font64($font)": (font) => { "font64($font)": (font) => {
const file = path.resolve(__dirname, "src/fonts", font.getValue()); const file = path.resolve(__dirname, "src/fonts", font.getValue());
return new sass.types.String(`"data:font/truetype;charset=utf-8;base64,${fs.readFileSync(file, { encoding: "base64" })}"`); return new sass.types.String(`"data:font/truetype;charset=utf-8;base64,${fs.readFileSync(file, { encoding: "base64" })}"`);
},
'icon64($name, $options: "{}")': (name, options) => {
name = name.getValue();
options = JSON.parse(options.getValue());
return new sass.types.String(icons.get(name, { ...options, base64: true }));
} }
} }
} }
@ -79,7 +96,7 @@ module.exports = {
.replace(/^---(.*?\r?\n)+---(.*?\r?\n)*?(?=\S)/, "") // Replace GitHub header .replace(/^---(.*?\r?\n)+---(.*?\r?\n)*?(?=\S)/, "") // Replace GitHub header
.replace(/\*\*Desktop Setup\*\*\r?\n(- .*?\r?\n)+\r?\n/, "") // Replace **Desktop Setup** block .replace(/\*\*Desktop Setup\*\*\r?\n(- .*?\r?\n)+\r?\n/, "") // Replace **Desktop Setup** block
), ),
"process.env.DRIBBBLISH_ICONS": JSON.stringify(icons), "process.env.DRIBBBLISH_ICONS": JSON.stringify(_icons),
"process.env.DRIBBBLISH_VERSION": JSON.stringify(process.env.DRIBBBLISH_VERSION ?? "Dev"), "process.env.DRIBBBLISH_VERSION": JSON.stringify(process.env.DRIBBBLISH_VERSION ?? "Dev"),
"process.env.COMMIT_HASH": JSON.stringify(process.env.COMMIT_HASH ?? "local") "process.env.COMMIT_HASH": JSON.stringify(process.env.COMMIT_HASH ?? "local")
}), }),
@ -89,3 +106,5 @@ module.exports = {
}) })
] ]
}; };
export default config;