Compare commits

...

271 commits
3.0.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
Send_Nukez
0e50a92431 refactor icons a bit 2021-12-10 20:11:46 +01:00
Send_Nukez
4dd2d42ca4 changed default sidebar color to be darker & improve loader styles 2021-12-10 20:05:07 +01:00
Send_Nukez
ead9615ef3 Merge branch 'main' of https://github.com/JulienMaille/dribbblish-dynamic-theme 2021-12-10 19:23:29 +01:00
Send_Nukez
cd325a3f05 fix #137 2021-12-10 18:20:31 +00:00
Send_Nukez
6c84a848fc fix #137 2021-12-10 19:20:07 +01:00
github-actions
b3e108f2bf Release v4.1.0 2021-12-10 15:41:22 +00:00
Julien Maille
61cbee1c1d improved settings description 2021-12-06 20:34:30 +01:00
Send_Nukez
8edba57dd5 make System the default theme 2021-12-04 15:49:51 +01:00
Send_Nukez
55e6ef0ca2 add ability to get icons as raw svg string without type annd stuff 2021-12-04 15:24:01 +01:00
Send_Nukez
25b5b771dd Channge font of "Home Shelf" titles 2021-12-04 15:21:25 +01:00
Send_Nukez
9e7349712b add ability to disable config inputs & make disabled inputs be styled correctly 2021-12-04 15:20:53 +01:00
Send_Nukez
0e946cf05a Reword and refactor Theme > Theme System 2021-12-04 15:17:59 +01:00
Julien
f69afbb882
Update README.md 2021-12-04 14:40:51 +01:00
Julien Maille
467a971d31 FIX uninstall.sh 2021-12-04 14:29:02 +01:00
Julien Maille
55b714dbda added patch-dark-mode.ps1 2021-12-04 14:13:46 +01:00
Julien Maille
34bd5fe4f8 IMP: description for 'Based on System Theme' option 2021-12-02 20:43:29 +01:00
Julien
76fda08a71 Merge pull request #128 from evanbrierton/main
Implement functionality to use system theme for dark/light theme.
2021-12-02 19:30:29 +00:00
Send_Nukez
651896418c refactor / improve settings 2021-12-02 16:02:20 +01:00
Send_Nukez
e6ef0953ea add ability to search settings 2021-12-02 14:01:56 +01:00
Send_Nukez
8968202a3e move sass invert into Util 2021-12-01 06:15:33 +01:00
Send_Nukez
8747efdd7b reword some descriptions 2021-12-01 06:10:25 +01:00
Send_Nukez
3278f1fbe0 fix alignment of color inputs 2021-12-01 06:07:59 +01:00
Send_Nukez
0794a51b56 (hopefully) fix actions breaking on PRs 2021-12-01 02:55:59 +01:00
Evan Brierton
200b112fe1
Add fix to switch to system theme when option is selected 2021-11-30 12:00:41 +00:00
Julien
193953dc91
Merge pull request #129 from JulienMaille/bw-themes
WIP: add 3 b&w themes
2021-11-30 09:39:19 +01:00
Send_Nukez
1c4ba1a29d make bgTheme a child of theme and reword stuff a little 2021-11-30 02:57:02 +01:00
Julien
9ed25390c0
Update main.js
typo
2021-11-29 22:46:09 +01:00
Julien Maille
164058b875 WIP: add 3 b&w themes 2021-11-29 21:44:32 +01:00
Evan Brierton
eb87c41128
Actually read theme from config 2021-11-29 20:31:40 +00:00
Evan Brierton
5d5d1bc3c1
Use event listener for match system theme 2021-11-29 20:24:55 +00:00
Evan Brierton
836c13abec
Revert setInterval to once per minute for checkDarkLightMode 2021-11-29 20:22:08 +00:00
Evan Brierton
7e96398958
Delete .DS_Store 2021-11-29 13:32:16 +00:00
Evan Brierton
1a61984656
Increase frequency of checkDarkLightMode interval to 1s 2021-11-29 13:28:16 +00:00
Evan Brierton
7e59834be4
Implement system theme check in checkDarkLightMode 2021-11-29 13:28:01 +00:00
Evan Brierton
80b4d68835
Add System Theme option to config register 2021-11-29 13:27:32 +00:00
Send_Nukez
3629e883a5 add option to insert custom CSS 2021-11-29 07:00:44 +01:00
Send_Nukez
7f559d2a2c only show js not installed message after 3s 2021-11-29 04:37:46 +01:00
Send_Nukez
27cd9a8a0e automatically inject bug_report.md into js 2021-11-29 04:19:49 +01:00
Send_Nukez
b2cfe07578 overhaul modals and casting to device ui 2021-11-28 04:57:07 +01:00
Send_Nukez
e18320acb3 prevent js from being executed multiple times 2021-11-28 01:59:53 +01:00
Send_Nukez
2d30ff934e update readme to use absolute urls 2021-11-28 01:31:14 +01:00
Send_Nukez
cd24ce8f06 update manifest readme url 2021-11-28 00:59:47 +01:00
Erik
a00193acde
Merge pull request #125 from CharlieS1103/patch-1
Update manifest preview gif
2021-11-28 00:58:09 +01:00
CharlieS1103
cf9f6e154b
Update manifest preview gif
Should work with new changes to allow links, currently only did it for previews since that seems to be the only one with a demand for it.
2021-11-27 12:34:13 -05:00
Send_Nukez
6ac36c80a3 refactor Icons.js a bit 2021-11-27 13:23:15 +01:00
Send_Nukez
b60682cac5 change actions to include commit hash in version file 2021-11-27 13:18:09 +01:00
Send_Nukez
fdfe690b79 show beta build info & add spicetify icon 2021-11-27 13:14:03 +01:00
Send_Nukez
83546ad90c fix manifest 2021-11-27 12:57:13 +01:00
Send_Nukez
098a112819 change default info icon size 2021-11-27 12:45:53 +01:00
Send_Nukez
7c2fbea06f update / refactor styles a bit 2021-11-27 10:11:55 +01:00
Send_Nukez
9994713e55 only display option to reset setting if it was changed 2021-11-27 10:11:05 +01:00
Send_Nukez
d206317f45 remove redundant info from automatic but reports 2021-11-27 10:06:16 +01:00
Send_Nukez
2e1b7a05b5 update bug / feature report templates to use comments 2021-11-27 10:00:31 +01:00
Send_Nukez
8e702a49e1 rename release branches 2021-11-27 09:49:49 +01:00
Send_Nukez
5d537336a9 use material-icons 2021-11-27 08:22:27 +00:00
Send_Nukez
bcafa8b5a4 improve custom search bar handling 2021-11-27 00:44:15 +01:00
Send_Nukez
6cc4b7d492 remove redundant "Theme" in manifest title 2021-11-27 00:42:58 +01:00
Julien Maille
af5af92b62 test manifest for marketplace 2021-11-26 22:56:46 +01:00
Send_Nukez
5e0236dff5 improve offline icon handling 2021-11-26 04:04:30 +01:00
Send_Nukez
ea1ba797f6 make default popup styles fit inline with custom ones 2021-11-26 04:01:37 +01:00
Send_Nukez
a5b92ed62c also show spicetify update & fix Info.js 2021-11-25 13:16:48 +01:00
Send_Nukez
3798bc2723 update Loader.scss 2021-11-25 12:36:49 +01:00
Send_Nukez
61db2917f8 refactor icons to be imported as function 2021-11-25 12:21:25 +01:00
Send_Nukez
89742ae74a add hover & active state to config area headers 2021-11-25 09:59:36 +01:00
Send_Nukez
4328a81a1c add tooltip to playlist / folder images & refactor 2021-11-25 03:57:01 +01:00
Send_Nukez
219c16872c remove markdown-it-bracketed-spans 2021-11-24 23:03:49 +01:00
Send_Nukez
1296e13c73 tweak context menus a bit 2021-11-24 10:00:21 +01:00
Send_Nukez
c471531e71 update manifest 2021-11-23 21:48:13 +01:00
Send_Nukez
e4110f8df3 add maifest for spicetify-marketplace 2021-11-23 21:31:37 +01:00
Send_Nukez
38d70581ac prepare for spicetify-marketplace 2021-11-23 21:22:21 +01:00
Send_Nukez
3f308a2c9e add ability to disable the progress transition to improve performance 2021-11-23 20:39:10 +01:00
Send_Nukez
328cf2a59a re-add old search bar 2021-11-23 07:28:18 +01:00
Send_Nukez
4b251dfa8d add ability for loading screen to be disabled 2021-11-22 02:13:35 +01:00
Send_Nukez
f0a562f70a add basic event functionality and a loader at startup 2021-11-21 22:54:41 +00:00
Send_Nukez
83b2801a36 fix config item childrens order being wrong 2021-11-21 23:47:16 +01:00
Send_Nukez
675ff2d94d inject fonts into user.css as base64 url 2021-11-21 22:30:46 +01:00
Send_Nukez
8264eb33dd change default folder & playlist image and refactor 2021-11-20 22:02:03 +00:00
Send_Nukez
fe0503e9f8 fix #114 2021-11-20 18:18:25 +01:00
Julien
9afccf31c3
Update README.md 2021-11-18 22:12:10 +01:00
Julien Maille
980ed8d747 FIX: missing image for playlist without cover 2021-11-18 20:34:30 +01:00
Send_Nukez
71ce39abdb fix #116 2021-11-18 17:49:43 +01:00
Send_Nukez
c2d7f851b1 move Hide Ads into main settings 2021-11-18 02:45:05 +01:00
Send_Nukez
61db261115 refactor styles a bit 2021-11-18 02:44:15 +01:00
Send_Nukez
18f0d454be refactor update checking 2021-11-17 23:30:00 +01:00
Send_Nukez
f45f570730 fix checking for updates every 10m 2021-11-17 23:25:05 +01:00
github-actions
a6c1a89e5c Release v4.0.0 2021-11-17 20:42:29 +00:00
Send_Nukez
8ba60b477a update changelog 2021-11-17 21:35:11 +01:00
Send_Nukez
4538c1bb4b update Color Selection Algorithm description 2021-11-17 21:28:37 +01:00
Erik
f6aa2a5c7d
Merge pull request #113 from JulienMaille/colorthief
Colorthief
2021-11-17 21:26:51 +01:00
Send_Nukez
3448e7cffc add Request Feature button to about 2021-11-17 21:20:01 +01:00
Julien Maille
2b6853bc70 improve select dropdowns background color 2021-11-17 21:18:57 +01:00
Send_Nukez
58324a3ff2 update Color Selection Algorithm description 2021-11-17 21:09:01 +01:00
Send_Nukez
055510a7ad change markdownn muted styles to only affect text, not links and stuff 2021-11-17 21:05:25 +01:00
Send_Nukez
884ca0baea reorder markdown-it plugins 2021-11-17 21:04:39 +01:00
Send_Nukez
da404f4c7b Merge branch 'main' into colorthief 2021-11-17 20:22:58 +01:00
Send_Nukez
e1d393e2a0 Improve color settings UX and re-add vibrant as option 2021-11-17 20:22:53 +01:00
Send_Nukez
3088e8a71d add markdown-it-bracketed-spans 2021-11-17 20:18:21 +01:00
Send_Nukez
1b66ab044f remove uneccesary padding 2021-11-17 19:20:21 +01:00
Send_Nukez
3d96810b72 remove accidental log 2021-11-17 19:20:03 +01:00
Julien
3b248c465b
Merge pull request #112 from JulienMaille/JulienMaille-patch-1
set default theme mode to "Based on time"
2021-11-17 07:48:19 +01:00
Send_Nukez
a3cea3012e fix #111 2021-11-17 03:46:55 +01:00
Send_Nukez
784947e3a5 add option to select color extraction method 2021-11-17 03:40:09 +01:00
Julien Maille
3e41a07907 FIX unreadable buttons in modals 2021-11-17 00:07:41 +01:00
Julien
c48984b9ec
Fix z-index of left-side svgs 2021-11-16 22:48:55 +01:00
Julien
74140f1d42
set default theme mode to "Based on time" 2021-11-16 22:20:58 +01:00
Julien Maille
fd01cc02ce changelog 2021-11-16 22:15:46 +01:00
Send_Nukez
43452294db fix alignment of right expanded cover 2021-11-16 20:18:24 +01:00
Julien Maille
b610a0dd8b IMP: try to avoid black/white colors extracted from cover 2021-11-16 18:56:06 +01:00
Send_Nukez
1eddb9655e add ability for config validation to return strings as error to be displayed 2021-11-16 03:39:31 +01:00
Julien Maille
dfc2c79921 IMP: increase luminance of text 2021-11-15 21:22:18 +01:00
Send_Nukez
415efc5731 added option to have quick access to settings 2021-11-15 04:59:59 +01:00
Send_Nukez
f50ccaaf10 improve settings ui 2021-11-15 04:53:57 +01:00
Julien Maille
90662cd66d first shot (in the dark) 2021-11-14 23:07:03 +01:00
Send_Nukez
aaeb51a6ca rename DribbblishShared to Dribbblish and expose it to the window object to be used in other extensions 2021-11-14 00:25:44 +01:00
Send_Nukez
9d05365ffb refactor webpack config a bit and use dart-sass instead of node-sass 2021-11-14 00:24:05 +01:00
Send_Nukez
59daccdbf2 add ready state to info so it won't throw an error when registering items too early 2021-11-13 21:49:11 +01:00
Send_Nukez
a880871db0 add custom "offline" icon 2021-11-13 17:33:18 +01:00
Send_Nukez
215fbad04e make info text bolder 2021-11-13 17:25:20 +01:00
Send_Nukez
a77033801d don't use md for info and add more options to it 2021-11-13 17:24:37 +01:00
Send_Nukez
b1765a1e3c make the md prop private 2021-11-13 17:22:55 +01:00
Send_Nukez
284b3cc9f2 fix "invalid date" and show full date on hover 2021-11-12 22:25:21 +01:00
Julien
3b60f46002
Improve text color when sidebar color is light #106 2021-11-12 20:57:10 +01:00
Send_Nukez
5ebc888d42 don't minify css 2021-11-12 19:58:05 +01:00
Send_Nukez
b8d6d98883 update changelog 2021-11-12 19:40:54 +01:00
Send_Nukez
759c0e058f fix playing icon position being wrong when listening to a playlist that is inside a folder 2021-11-12 18:56:35 +01:00
Send_Nukez
d3b9577237 adjust padding of info items 2021-11-12 18:13:08 +01:00
Send_Nukez
0c318e1f21 Add Info class to show buttons / text next to the user icon (fixes #108) 2021-11-12 16:57:41 +00:00
Send_Nukez
ec84ad0c79 remove raw-loader and add resolve alias for svgs 2021-11-12 16:25:09 +01:00
Send_Nukez
5256ff6b7a add textarea config type, refactor config a bit and improve checkbox styles to be more inline with other inputs 2021-11-12 02:48:14 +01:00
Julien
e00027bb69
workaround to remove gradient near album info #103 2021-11-11 23:31:10 +01:00
Send_Nukez
b5033f8872 refactor ConfigMenu to use jquery 2021-11-11 22:56:25 +01:00
Send_Nukez
574a8332aa Merge branch 'main' of https://github.com/JulienMaille/dribbblish-dynamic-theme 2021-11-11 22:55:35 +01:00
Send_Nukez
7b393b7873 fix slider tooltip being too low 2021-11-11 21:53:36 +00:00
Send_Nukez
4aa0998924 fix slider tooltip being too low 2021-11-11 22:53:16 +01:00
Send_Nukez
6de1a4e9e0 Merge branch 'main' of https://github.com/JulienMaille/dribbblish-dynamic-theme 2021-11-11 21:42:28 +01:00
Send_Nukez
d078c29ab6 update changelog 2021-11-11 20:41:44 +00:00
Send_Nukez
d4780d6220 update changelog 2021-11-11 21:41:23 +01:00
Send_Nukez
8b2a366c95 update changelog 2021-11-11 19:46:02 +01:00
Send_Nukez
eb60df1a9a fix typos in readme 2021-11-11 19:44:32 +01:00
Send_Nukez
48d57498aa add markdown styles and change the way the config backdrop is shown so notifications are above it 2021-11-11 19:34:48 +01:00
Send_Nukez
8b2d6fa141 fix #107 2021-11-11 19:26:30 +01:00
Send_Nukez
19aeb86204 remove redundant setting of color vars and rename rgb vars back to default spicetify names 2021-11-11 19:10:13 +01:00
Send_Nukez
d1d15d2425 add spiceFont mixin 2021-11-11 01:23:21 +01:00
Send_Nukez
fc6ac2e35c add markdown parsing to settings descriptions 2021-11-11 00:31:09 +01:00
Send_Nukez
5404036ee4 add more font-weights so fonts are not rendered blurry 2021-11-11 00:11:33 +01:00
Send_Nukez
1131ccbf91 refactor a tiny bit 2021-11-10 23:02:26 +01:00
Send_Nukez
2e00ea05a0 add descriptions to bug report and changelog buttons 2021-11-10 01:02:17 +00:00
Send_Nukez
0e1e89fdec channge config.export to only return keys, not areas 2021-11-10 01:54:26 +01:00
Send_Nukez
81ac31ae25 update bug report template 2021-11-09 20:48:02 +01:00
Send_Nukez
d78848c29f update changelog 2021-11-09 20:45:46 +01:00
Send_Nukez
c272dcb788 add Report Bug and Changelog buttons to the about 2021-11-09 20:43:12 +01:00
Send_Nukez
2b41bd25cc remove titles from issue templates as they are redundant with tags 2021-11-09 20:40:20 +01:00
github-actions
aa33fe1b42 Release v3.1.1 2021-11-09 14:09:51 +00:00
Send_Nukez
6f7096baa3 fix #102 2021-11-09 15:06:26 +01:00
Julien
1447fd9f3d Show spicetify version in About 2021-11-08 21:53:58 +01:00
github-actions
6118e1d9e7 Release v3.1.0 2021-11-08 19:07:57 +00:00
Send_Nukez
2f706ed347 improved user menu hover style 2021-11-08 00:27:57 +01:00
Send_Nukez
c5a1dd3f16 update changelog 2021-11-06 00:12:16 +01:00
Send_Nukez
05a0fb7dc8 fix Sidebar config buttons being below text 2021-11-06 00:12:06 +01:00
Erik
984cc78159
Merge pull request #100 from JulienMaille/glassy-context-menu
Glassy context menus
2021-11-06 00:00:51 +01:00
Send_Nukez
272d82fdbf update readme 2021-11-05 23:55:08 +01:00
Send_Nukez
96ea4f2660 add settings reset buttons and always show about 2021-11-05 23:48:16 +01:00
Send_Nukez
d91902b301 Append commit hash to version to debug re-releases with the same version number 2021-11-05 00:41:59 +01:00
Erik
7ef21577f0
fix "Invalid date" 2021-11-05 00:07:46 +01:00
Send_Nukez
be34f71a00 fix bookmarks not being visible 2021-11-04 21:21:39 +01:00
Julien
50b4a73654
resized image in readme 2021-11-04 20:36:34 +01:00
Erik
bc76fa92a1
make webpack-test upload built artifact 2021-11-04 13:50:45 +01:00
Send_Nukez
a39d7b99f2 restyle context menus 2021-11-04 13:21:14 +01:00
Send_Nukez
8b7e96bf39 add queue icon hover and active state 2021-11-04 13:12:26 +01:00
Send_Nukez
14294113d0 fix cover listener again 2021-11-04 00:23:20 +01:00
Send_Nukez
c3f1f235c1 improve cover listener 2021-11-04 00:01:44 +01:00
Send_Nukez
9b19b3c9af only transition a few colors to improve performance 2021-11-03 23:03:21 +01:00
Send_Nukez
77f39351d0 rename css variables and generate @properties to transition 2021-11-03 16:43:34 +01:00
Julien
23b83c2fdf
Merge pull request #94 from JulienMaille/patch-1
Scroll extent calculated wrongly - clean version of PR #89
2021-11-03 14:42:43 +01:00
Send_Nukez
fffb05ff46 change sidebar gap inputs to type number 2021-11-03 14:30:32 +01:00
Send_Nukez
9a47d3bbfc add webpack test-build on pr and push 2021-11-03 14:03:28 +01:00
Erik
294e69b3d8
Merge pull request #96 from JulienMaille/colors-in-scss
change colors to be handled inside scss
2021-11-03 13:59:23 +01:00
Send_Nukez
010f32a1fb change spiceColor() calls to use strings 2021-11-03 13:58:48 +01:00
Julien Maille
61abb92af6 update issue template 2021-11-03 08:53:19 +01:00
Julien Maille
e22fa848ab updated readme 2021-11-03 07:57:11 +01:00
Send_Nukez
3c64c12a64 fix color.ini generation 2021-11-03 01:40:54 +01:00
Send_Nukez
79eb030a5f change everything to use spiceColor() 2021-11-03 01:16:50 +01:00
Send_Nukez
e5c3847cd9 change colors to be handled inside scss 2021-11-02 23:32:06 +00:00
Julien Maille
e948f8d120 call spicetify apply after patching in install scripts 2021-11-02 23:26:15 +01:00
Julien Maille
2a861c9e7c changelog 2021-11-02 22:21:06 +01:00
Julien Maille
f2b7f20a14 simplify spicetify config extensions 2021-11-02 22:20:41 +01:00
Julien Maille
cdba4468ba added uninstall.sh 2021-11-02 22:20:41 +01:00
Julien Maille
7401f0ef24 update install.sh with patching 2021-11-02 22:20:41 +01:00
Julien Maille
a0d18b38f7 clean version of PR #89 2021-11-02 22:20:41 +01:00
Send_Nukez
c099d5c205 improve playbar controls centering 2021-11-02 18:27:22 +01:00
Send_Nukez
21f3bac34f update issue templates 2021-11-02 17:07:04 +01:00
Send_Nukez
18287d3a62 fix #69 and improve playbar album hover 2021-11-02 16:56:44 +01:00
Erik
b8841be47d
Merge pull request #93 from JulienMaille/coverlistener
Change Cover Listener
2021-10-31 20:15:28 +01:00
Send_Nukez
a6ea73fb9d Add functionality for #90 and #92 2021-10-31 20:14:46 +01:00
Send_Nukez
bff439eaa3 change cover listener 2021-10-31 11:09:17 +01:00
Send_Nukez
a7d99725c2 add name and version to package.json 2021-10-31 00:58:47 +02:00
Send_Nukez
0bcc804089 update changelog 2021-10-30 00:36:11 +02:00
Send_Nukez
c9533afe10 update .prettierignore 2021-10-29 21:27:44 +00:00
Send_Nukez
ad60b817b5 push package-lock.json 2021-10-29 23:25:32 +02:00
Send_Nukez
552c434e99 workaround for #73 and fix some playbar colors 2021-10-29 21:21:21 +00:00
Send_Nukez
3014fa68a9 Format actions and make empty-changelog add version number in commit message 2021-10-29 22:53:04 +02:00
Send_Nukez
f24015e3c5 make the release action append full changelog (commits) since last release 2021-10-29 15:05:27 +02:00
Send_Nukez
2e50173c45 improve background image rendering 2021-10-29 15:04:32 +02:00
Send_Nukez
9ddbac2510 Add functionality for #85 and add settings area "Consistency" 2021-10-29 12:21:35 +02:00
Send_Nukez
07f684db20 fix #87 and it's comment 2021-10-29 02:39:30 +02:00
github-actions
2353623187 Empty CHANGELOG.md 2021-10-28 16:46:56 +00:00
Send_Nukez
483c8051eb Fix update message stacking for real this time 2021-10-28 18:45:05 +02:00
github-actions
7ae4018432 Empty CHANGELOG.md 2021-10-28 16:38:13 +00:00
70 changed files with 12778 additions and 1600 deletions

3
.babelrc Normal file
View file

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

View file

@ -1,33 +1,39 @@
---
name: Bug report
about: Create a report to help us improve
title: "[BUG]"
labels: ''
assignees: ''
about: Create a report to help us improve (After v3.1.1, preferably open this report from inside the Dribbblish settings `About > Report Bugs` as it adds additional info automatically)
title: ""
labels: ["bug"]
assignees: ""
---
**Describe the bug**
A clear and concise description of what the bug is.
<!-- A clear and concise description of what the bug is. -->
**To Reproduce**
Steps to reproduce the behavior:
1.
**Screenshots**
If applicable, add screenshots to help explain your problem.
<!-- If applicable, add screenshots to help explain your problem. -->
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Version [e.g. 22]
**Desktop Setup**
- Operating System: <!-- [e.g. Windows 10, MacOS 10.14] -->
- Spotify: <!-- [e.g. 1.1.71.560] from `Menu (Three dots top left) > Help > About` -->
- Spicetify: <!-- [e.g. 2.4.1] run `spicetify -v` -->
- Dribbblish: <!-- [e.g. 3.0.1] from `Dribbblish Settings > About` -->
**Logs**
Add logs from console. To do that
1. Run `spicetify enable-devtool` in terminal
2. Spotify will be restarted
3. Hit <kbd>Ctrl + Shift + I</kbd> to open DevTools window
4. Navigate to tab Console
5. Copy console window content.
<!--
Add logs from console. To do that
1. Run `spicetify enable-devtool` in terminal
2. Spotify will be restarted
3. Hit <kbd>Ctrl + Shift + I</kbd> to open DevTools window
4. Navigate to tab Console
5. Copy console window content.
-->
<!-- Paste logs below or attach a screenshot -->
```console
(Please paste here console logs or attach a screenshot)
[paste logs here]
```

View file

@ -1,20 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
title: ""
labels: ["enhancement"]
assignees: ""
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
<!-- A clear and concise description of what you want to happen. -->
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
**Additional context**
Add any other context or screenshots about the feature request here.
<!-- Add any other context or screenshots about the feature request here. -->

View file

@ -1,34 +0,0 @@
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

63
.github/workflows/post-release.yaml vendored Normal file
View file

@ -0,0 +1,63 @@
name: Post-Release
on:
release:
types: [published]
jobs:
post-release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
repository: ${{ github.event.repository.full_name }}
ref: ${{ github.event.repository.default_branch }}
fetch-depth: 0
- name: Put version in env
run: echo "DRIBBBLISH_VERSION=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV
- name: Get commit SHA
run: echo "LATEST_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: "14"
- name: Build Webpack
run: |
npm install
npm run build
echo "${{ env.DRIBBBLISH_VERSION }}-${{ env.LATEST_SHA }}" > dist/VERSION
env:
DRIBBBLISH_VERSION: ${{ env.DRIBBBLISH_VERSION }}
COMMIT_HASH: ${{ env.LATEST_SHA }}
- name: Push to release branch
uses: s0/git-publish-subdir-action@develop
env:
REPO: self
BRANCH: release-stable
FOLDER: dist
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Set package.json version
run: |
sed -i 's/"version":.*",/"version": "${{ env.DRIBBBLISH_VERSION }}",/' package.json
- name: Empty CHANGELOG.md
run: |
rm CHANGELOG.md
touch CHANGELOG.md
- name: Commit
run: |
git config --global user.email "action@github.com"
git config --global user.name "github-actions"
git add .
git commit --allow-empty -m "Release v${{ env.DRIBBBLISH_VERSION }}"
git push

View file

@ -1,26 +1,25 @@
name: Prettier
on:
on:
pull_request:
push:
branches:
- main
- main
jobs:
prettier:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
# Make sure the actual branch is checked out when running on pull requests
ref: ${{ github.head_ref }}
# This is important to fetch the changes to the previous commit
fetch-depth: 0
- name: Checkout
uses: actions/checkout@v2
with:
repository: ${{ github.event.repository.full_name }}
ref: ${{ github.head_ref }}
fetch-depth: 0
- name: Prettify code
uses: creyD/prettier_action@v4.0
with:
prettier_options: --write **/*.{js,css}
same_commit: True
- name: Prettify code
uses: creyD/prettier_action@v4.1.1
with:
prettier_options: --write **/*.{js,css}
same_commit: True

View file

@ -1,10 +1,10 @@
name: Release
on:
on:
workflow_dispatch:
inputs:
version:
description: 'Version of Release (format: X.X.X)'
description: "Version of Release (format: X.X.X)"
required: true
jobs:
@ -12,62 +12,71 @@ jobs:
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: 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: Checkout
uses: actions/checkout@v2
with:
ref: ${{ github.head_ref }}
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '14'
- name: Get commit SHA
run: echo "LATEST_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- name: Build Webpack
run: |
npm install
npm run build
echo "${{ github.event.inputs.version }}" > dist/VERSION
env:
DRIBBBLISH_VERSION: ${{ github.event.inputs.version }}
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: "15"
- 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: Build Webpack
run: |
npm install
npm run build
echo "${{ github.event.inputs.version }}-${{ env.LATEST_SHA }}" > dist/VERSION
env:
DRIBBBLISH_VERSION: ${{ github.event.inputs.version }}
COMMIT_HASH: ${{ env.LATEST_SHA }}
- name: Read CHANGELOG.md
run: |
[ -s CHANGELOG.md ] && CHANGELOG=$(< CHANGELOG.md) || CHANGELOG="*Empty.*"
- 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 ..
echo "CHANGELOG<<EOF" >> $GITHUB_ENV
echo "$CHANGELOG" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
- name: Read CHANGELOG.md and get latest Tag
run: |
[ -s CHANGELOG.md ] && CHANGELOG=$(< CHANGELOG.md) || CHANGELOG="*Empty.*"
- 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 }}
echo "CHANGELOG<<EOF" >> $GITHUB_ENV
echo "$CHANGELOG" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
---
### 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
```
echo "LATEST_TAG=$(git describe --tags --abbrev=0)" >> $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 }}
Full changelog [here](https://github.com/JulienMaille/dribbblish-dynamic-theme/compare/${{ env.LATEST_TAG }}...${{ github.event.inputs.version }})
---
### 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
```

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

46
.github/workflows/webpack-test.yml vendored Normal file
View file

@ -0,0 +1,46 @@
name: Webpack Test
on: [pull_request, push]
jobs:
webpack:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
repository: ${{ github.event.repository.full_name }}
ref: ${{ github.head_ref }}
- name: Get commit SHA
run: echo "LATEST_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: "15"
- name: Test-Build Webpack
run: |
npm install
npm run build
echo "Beta-${{ env.LATEST_SHA }}" > dist/VERSION
env:
DRIBBBLISH_VERSION: Beta
COMMIT_HASH: ${{ env.LATEST_SHA }}
- name: Push to beta-release branch
if: github.event_name == 'push'
uses: s0/git-publish-subdir-action@develop
env:
REPO: self
BRANCH: release-beta
FOLDER: dist
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload Artifact
uses: actions/upload-artifact@v2
with:
name: DribbblishDynamic_vBeta-${{ env.LATEST_SHA }}
path: dist/**

View file

@ -1,2 +1,3 @@
Vibrant.min.js
dist/
.github/
package.json
package-lock.json

View file

@ -1,2 +1,13 @@
Added:
- Ability to hide premium features in addition to ads
- Ability to show current artist's genres
Fixed:
- Update message stacking
- Custom search input not being focussed after clicking
- When changing songs, the color shifts to blue for a second (#179)
Improved:
- 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

@ -1,28 +1,32 @@
# Dribbblish Dynamic
A theme for [Spicetify](https://github.com/khanhas/spicetify-cli)
<a href="https://github.com/JulienMaille/dribbblish-dynamic-theme/releases/latest"><img src="https://img.shields.io/github/release/JulienMaille/dribbblish-dynamic-theme/all.svg"></a>
<a href="https://github.com/JulienMaille/dribbblish-dynamic-theme/releases"><img src="https://img.shields.io/github/downloads/JulienMaille/dribbblish-dynamic-theme/total.svg"></a>
### Preview
<img src="showcase-images/preview.gif" alt="img" width="500px">
<img src="https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/main/showcase-images/preview.gif" alt="img" width="500px">
## Features
### Resizable sidebar
<img src="showcase-images/resize-sidebar.png" alt="img" width="500px">
<img src="https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/main/showcase-images/resize-sidebar.png" alt="img" width="500px">
### Customizable sidebar
Rearrange icons positions, stick icons to header or hide unnecessary to save space.
Turn on "Sidebar config" mode in Profile menu and hover on icon to show control buttons.
After you finish customizing, turn off Config mode in Profile menu to save.
<img src="showcase-images/customize-sidebar.png" alt="img" width="500px">
<img src="https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/main/showcase-images/customize-sidebar.png" alt="img" width="500px">
### Playlist Folder image
Right click at folder and choose images for your playlist folder. Every image formats supported by Chrome can be used, but do keep image size small and in compressed format.
<img src="showcase-images/playlist-folders.gif" alt="img" width="500px">
<img src="https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/main/showcase-images/playlist-folders.gif" alt="img" width="500px">
### Left/Right expanded cover
In profile menu, toggle option "Right expanded cover" to change expaned current track cover image to left or right side, whereever you prefer.
In profile menu, toggle option "Right expanded cover" to change expanded current track cover image to left or right side, wherever you prefer.
## Install / Update
Make sure you are using spicetify >= v2.6.0 and Spotify >= v1.1.67.
@ -41,7 +45,8 @@ curl -fsSL https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-the
1. Download the latest [DribbblishDynamic_vX.X.X.zip](https://github.com/JulienMaille/dribbblish-dynamic-theme/releases/latest)
2. Extract the files to your [Spicetify/Themes folder](https://github.com/khanhas/spicetify-cli/wiki/Customization#themes)
3. Copy `dribbblish-dynamic.js` to your [Spicetify/Extensions folder](https://github.com/khanhas/spicetify-cli/wiki/Extensions#installing)
4. Run:
4. Add the 2 lines in `[Patch]` section of the config file (see details below)
5. Run:
```
spicetify config extensions dribbblish-dynamic.js
spicetify config current_theme DribbblishDynamic
@ -51,22 +56,29 @@ curl -fsSL https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-the
```
## IMPORTANT!
From Spotify > v1.1.62, in sidebar, they use an adaptive render mechanic to actively show and hide items on scroll. It helps reducing number of items to render, hence there is significant performance boost if you have a large playlists collection. But the drawbacks is that item height is hard-coded, it messes up user interaction when we explicity change, in CSS, playlist item height bigger than original value. So you need to add these 2 lines in Patch section in config file:
From Spotify > v1.1.62, in sidebar, they use an adaptive render mechanic to actively show and hide items on scroll. It helps reducing number of items to render, hence there is significant performance boost if you have a large playlists collection. But the drawbacks is that item height is hard-coded, it messes up user interaction when we explicitly 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]
xpui.js_find_8008 = ,(\w+=)32,
xpui.js_repl_8008 = ,${1}56,
xpui.js_repl_8008 = ,${1}58,
```
## Hide Window Controls
Windows user, please edit your Spotify shortcut and add flag `--transparent-window-controls` after the Spotify.exe:
To edit an taskbar shortcut, right click it, then right click Spotify in the list again.
To edit a taskbar shortcut, right click it, then right click Spotify in the list again.
<img src="showcase-images/windows-shortcut-instruction.png" alt="img" width="500px">
<img src="https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/main/showcase-images/windows-shortcut-instruction.png" alt="img">
In addition to `--transparent-window-controls` you can set `Windows Top Bars` to `Solid` or `Transparent` to look like this:
<img src="showcase-images/top-bars.png" alt="img" width="500px">
<img src="https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/main/showcase-images/top-bars.png" alt="img" width="500px">
## Follow system dark/light theme (Powershell)
Automatic dark mode should work on MacOs and Linux out of the box.
From Spotify > v1.1.70, dark mode is forced in Windows builds. You will need to patch Spotify.exe using this script:
```powershell
Invoke-WebRequest -UseBasicParsing "https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/master/patch-dark-mode.ps1" | Invoke-Expression
```
## Uninstall
### Windows (PowerShell)
@ -74,10 +86,15 @@ In addition to `--transparent-window-controls` you can set `Windows Top Bars` to
Invoke-WebRequest -UseBasicParsing "https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/master/uninstall.ps1" | Invoke-Expression
```
### Linux/MacOS (Bash)
```bash
curl -fsSL https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/master/uninstall.sh | sh
```
### Manual Uninstall
1. Remove Patch lines you added in config file earlier.
2. Run:
```
spicetify config extensions dribbblish-dynamic.js-
spicetify config current_theme " " extensions dribbblish-dynamic.js-
spicetify apply
```
```

View file

@ -7,7 +7,7 @@ param (
$PSMinVersion = 3
if ($v) {
$version = $v
$version = $v
}
# Helper functions for pretty terminal output.
@ -32,8 +32,8 @@ if ($PSVersionTable.PSVersion.Major -gt $PSMinVersion) {
$checkSpice = Get-Command spicetify -ErrorAction Silent
if ($null -eq $checkSpice) {
Write-Host -ForegroundColor Red "Spicetify not found"
Invoke-WebRequest -UseBasicParsing "https://raw.githubusercontent.com/khanhas/spicetify-cli/master/install.ps1" | Invoke-Expression
Write-Host -ForegroundColor Red "Spicetify not found"
Invoke-WebRequest -UseBasicParsing "https://raw.githubusercontent.com/khanhas/spicetify-cli/master/install.ps1" | Invoke-Expression
}
if (-not $version) {
@ -91,29 +91,36 @@ if ($PSVersionTable.PSVersion.Major -gt $PSMinVersion) {
Write-Part "INSTALLING";
cd $sp_dot_dir
Copy-Item dribbblish-dynamic.js ..\..\Extensions
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 extensions default-dynamic.js- extensions dribbblish.js- extensions Vibrant.min.js- 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
Write-Done
# Add patch
Write-Part "PATCHING "; Write-Emphasized "config-xpui.ini"
$configFile = Get-Content "$spicePath\config-xpui.ini"
if (-not ($configFile -match "xpui.js_find_8008")) {
$rep = @"
$rep = @"
[Patch]
xpui.js_find_8008=,(\w+=)32,
xpui.js_repl_8008=,`${1}56,
xpui.js_repl_8008=,`${1}58,
"@
# In case missing Patch section
if (-not ($configFile -match "\[Patch\]")) {
$configFile += "`n[Patch]`n"
}
$configFile = $configFile -replace "\[Patch\]",$rep
Set-Content "$spicePath\config-xpui.ini" $configFile
# In case missing Patch section
if (-not ($configFile -match "\[Patch\]")) {
$configFile += "`n[Patch]`n"
}
$configFile = $configFile -replace "\[Patch\]",$rep
Set-Content "$spicePath\config-xpui.ini" $configFile
}
Write-Done
Write-Part "APPLYING";
$backupVer = $configFile -match "^version"
if ($backupVer.Length -gt 0) {
spicetify apply
} else {
spicetify backup apply
}
Write-Done
}

View file

@ -51,9 +51,20 @@ echo "INSTALLING"
cd "$(dirname "$(spicetify -c)")/Themes/DribbblishDynamic"
mkdir -p ../../Extensions
cp dribbblish-dynamic.js ../../Extensions/.
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 extensions default-dynamic.js- extensions dribbblish.js- extensions Vibrant.min.js- 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
echo "PATCHING"
PATCH='[Patch]
xpui.js_find_8008 = ,(\\w+=)32,
xpui.js_repl_8008 = ,\${1}58,'
if cat config-xpui.ini | grep -o '\[Patch\]'; then
perl -i -0777 -pe "s/\[Patch\].*?($|(\r*\n){2})/$PATCH\n\n/s" config-xpui.ini
else
echo -e "\n$PATCH" >> config-xpui.ini
fi
echo "APPLYING"
spicetify apply

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,19 +1,34 @@
{
"name": "dribbblish-dynamic-theme",
"version": "4.1.1",
"homepage": "https://github.com/JulienMaille/dribbblish-dynamic-theme",
"bugs": {
"url": "https://github.com/JulienMaille/dribbblish-dynamic-theme/issues"
},
"devDependencies": {
"@babel/core": "^7.17.0",
"@babel/preset-env": "^7.16.5",
"@babel/register": "^7.16.5",
"@material-icons/svg": "^1.0.22",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^9.0.1",
"css-minimizer-webpack-plugin": "^3.1.1",
"node-sass": "^6.0.1",
"sass": "^1.43.5",
"sass-loader": "^12.2.0",
"webpack": "^5.58.2",
"webpack-cli": "^4.9.0"
},
"scripts": {
"build": "webpack --mode=development"
"build": "webpack"
},
"dependencies": {
"chroma-js": "^2.1.2",
"colorthief": "^2.3.2",
"jquery": "^3.6.0",
"node-vibrant": "3.1.4"
"lodash.debounce": "^4.0.8",
"lodash.defaultsdeep": "^4.6.1",
"markdown-it": "^12.3.2",
"markdown-it-attrs": "^4.1.0",
"moment": "^2.29.2",
"node-vibrant": "^3.1.6",
"svgson": "^5.2.1"
}
}
}

39
patch-dark-mode.ps1 Normal file
View file

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

View file

@ -1,162 +0,0 @@
[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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
src/fonts/Roboto-Black.ttf Normal file

Binary file not shown.

BIN
src/fonts/Roboto-Bold.ttf Normal file

Binary file not shown.

BIN
src/fonts/Roboto-Light.ttf Normal file

Binary file not shown.

BIN
src/fonts/Roboto-Medium.ttf Normal file

Binary file not shown.

Binary file not shown.

BIN
src/fonts/Roboto-Thin.ttf Normal file

Binary file not shown.

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

3
src/icons/spicetify.svg Normal file
View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8700 8700">
<path d="M3659 8515 c-3 -2 -44 -9 -92 -14 -231 -27 -564 -143 -797 -278 -657 -384 -1069 -1019 -1149 -1773 -6 -52 -15 -126 -20 -165 -24 -162 -8 -434 39 -673 35 -174 149 -498 270 -767 284 -628 398 -939 435 -1180 61 -404 -59 -689 -349 -830 -99 -48 -126 -72 -126 -112 0 -40 49 -60 164 -68 142 -9 209 4 341 68 297 145 495 444 525 792 11 127 24 173 55 187 42 19 69 1 175 -118 142 -160 380 -392 530 -517 155 -128 319 -246 705 -504 425 -285 626 -443 851 -670 191 -193 347 -439 408 -644 34 -113 58 -298 52 -398 -12 -212 -30 -300 -100 -471 -62 -150 -68 -176 -49 -195 19 -20 29 -19 88 9 50 24 169 138 251 241 180 225 320 545 381 870 27 142 28 164 28 430 0 258 -3 293 -28 448 -81 490 -273 1011 -570 1549 -193 350 -241 472 -241 618 -1 89 24 146 76 178 80 49 163 36 240 -36 51 -48 91 -104 199 -284 104 -172 159 -233 292 -319 58 -37 217 -95 332 -120 78 -17 343 -13 407 6 71 21 90 33 86 58 -4 30 -49 53 -160 81 -124 33 -184 57 -262 106 -73 46 -191 165 -241 245 -90 140 -160 360 -191 600 -20 155 -15 430 11 650 23 188 31 549 16 695 -36 352 -122 701 -234 955 -238 537 -689 971 -1247 1197 -153 63 -389 124 -565 147 -78 11 -527 16 -536 6z m1241 -1230 c44 -23 70 -62 70 -107 0 -70 -21 -102 -97 -146 -430 -250 -899 -356 -1468 -332 -221 10 -373 27 -551 64 -66 14 -137 28 -158 31 -50 8 -102 58 -111 107 -10 50 23 121 65 143 39 20 97 17 255 -14 213 -41 587 -77 706 -68 320 25 438 42 621 93 148 40 239 77 430 174 146 74 184 83 238 55z m257 -610 c88 -40 124 -131 85 -216 -27 -61 -47 -77 -212 -162 -289 -150 -581 -245 -920 -301 -390 -64 -657 -68 -1070 -15 -81 10 -297 55 -407 85 -57 16 -106 52 -128 95 -24 45 -19 124 9 166 38 55 72 73 139 73 53 0 235 -35 317 -60 37 -12 132 -23 345 -41 229 -19 541 -1 785 46 283 54 579 153 756 251 112 63 200 102 229 103 11 0 44 -10 72 -24z m276 -696 c49 -13 105 -63 128 -114 38 -83 13 -185 -59 -245 -66 -55 -329 -175 -532 -244 -226 -77 -474 -137 -685 -165 -44 -6 -114 -16 -155 -22 -41 -6 -183 -17 -315 -24 -467 -27 -957 21 -1309 128 -118 36 -166 96 -166 208 0 85 31 138 102 173 61 30 119 33 205 11 110 -29 268 -63 348 -74 44 -6 114 -15 156 -21 95 -14 779 -13 834 0 22 5 60 10 84 10 43 0 104 9 256 36 78 15 127 25 268 59 134 32 367 117 513 186 242 116 253 119 327 98z"></path>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

3
src/icons/spotify.svg Normal file
View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512">
<path fill="currentColor" d="M248 8C111.1 8 0 119.1 0 256s111.1 248 248 248 248-111.1 248-248S384.9 8 248 8zm100.7 364.9c-4.2 0-6.8-1.3-10.7-3.6-62.4-37.6-135-39.2-206.7-24.5-3.9 1-9 2.6-11.9 2.6-9.7 0-15.8-7.7-15.8-15.8 0-10.3 6.1-15.2 13.6-16.8 81.9-18.1 165.6-16.5 237 26.2 6.1 3.9 9.7 7.4 9.7 16.5s-7.1 15.4-15.2 15.4zm26.9-65.6c-5.2 0-8.7-2.3-12.3-4.2-62.5-37-155.7-51.9-238.6-29.4-4.8 1.3-7.4 2.6-11.9 2.6-10.7 0-19.4-8.7-19.4-19.4s5.2-17.8 15.5-20.7c27.8-7.8 56.2-13.6 97.8-13.6 64.9 0 127.6 16.1 177 45.5 8.1 4.8 11.3 11 11.3 19.7-.1 10.8-8.5 19.5-19.4 19.5zm31-76.2c-5.2 0-8.4-1.3-12.9-3.9-71.2-42.5-198.5-52.7-280.9-29.7-3.6 1-8.1 2.6-12.9 2.6-13.2 0-23.3-10.3-23.3-23.6 0-13.6 8.4-21.3 17.4-23.9 35.2-10.3 74.6-15.2 117.5-15.2 73 0 149.5 15.2 205.4 47.8 7.8 4.5 12.9 10.7 12.9 22.6 0 13.6-11 23.3-23.2 23.3z"></path>
</svg>

After

Width:  |  Height:  |  Size: 901 B

View file

@ -1,8 +1,15 @@
import $ from "jquery";
import { renderMD, defaults } from "./Util";
import { icons } from "./Icons";
export default class ConfigMenu {
/** @typedef {"checkbox" | "select" | "button" | "slider" | "number" | "text" | "textarea" | "time" | "color"} DribbblishConfigType */
/**
* @typedef {Object} DribbblishConfigItem
* @property {"checkbox" | "select" | "button" | "slider" | "number" | "text" | "time" | "color"} type
* @property {String|DribbblishConfigArea} [area={name: "Main Settings", order: 0}]
* @property {DribbblishConfigType} type
* @property {String | DribbblishConfigArea} [area={name: "Main Settings", order: 0}]
* @property {any} [data={}]
* @property {Number} [order=0] order < 0 = Higher up | order > 0 = Lower Down
* @property {String} key
@ -10,19 +17,30 @@ export default class ConfigMenu {
* @property {String} [description=""]
* @property {any} [defaultValue]
* @property {Boolean} [hidden=false]
* @property {Boolean} [resetButton=true]
* @property {Boolean} [insertOnTop=false]
* @property {Boolean} [fireInitialChange=true]
* @property {Boolean} [save=true]
* @property {validate} [validate]
* @property {showChildren} [showChildren]
* @property {onAppended} [onAppended]
* @property {onChange} [onChange]
* @property {DribbblishConfigItem[]} [children=[]]
* @property {String} [childOf=null] key of parent (set automatically)
* @property {String} [parent=null] key of parent (set automatically)
*/
/**
* @typedef DribbblishConfigArea
* @property {String} name
* @property {Number} [order=0] order < 0 = Higher up | order > 0 = Lower Down
* @property {Boolean} [toggleable=true]
*/
/**
* @callback validate
* @this {DribbblishConfigItem}
* @param {any} value
* @returns {Boolean | String}
*/
/**
@ -48,65 +66,38 @@ export default class ConfigMenu {
/** @type {Object.<string, DribbblishConfigItem>} */
#config;
/** @type {Spicetify.Menu.Item} */
#configButton;
constructor() {
this.#config = {};
this.configButton = new Spicetify.Menu.Item("Dribbblish Settings", false, () => this.open());
this.configButton.register();
this.#configButton = new Spicetify.Menu.Item("Dribbblish Settings", false, () => this.open());
this.#configButton.register();
const container = document.createElement("div");
container.id = "dribbblish-config";
container.innerHTML = /* html */ `
<div class="dribbblish-config-container">
<button aria-label="Close" class="dribbblish-config-close main-trackCreditsModal-closeBtn">
<svg width="18" height="18" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><path d="M31.098 29.794L16.955 15.65 31.097 1.51 29.683.093 15.54 14.237 1.4.094-.016 1.508 14.126 15.65-.016 29.795l1.414 1.414L15.54 17.065l14.144 14.143" fill="currentColor" fill-rule="evenodd"></path></svg>
</button>
<button aria-label="Close" class="dribbblish-config-close main-trackCreditsModal-closeBtn">${icons.get("close", { size: 24 })}</button>
<h1>Dribbblish Settings</h1>
<input type="search" placeholder="Search" class="dribbblish-config-search">
<div class="dribbblish-config-areas"></div>
</div>
<div class="dribbblish-config-backdrop"></div>
`;
document.body.appendChild(container);
document.querySelector(".dribbblish-config-close").addEventListener("click", () => this.close());
document.querySelector(".dribbblish-config-backdrop").addEventListener("click", () => this.close());
$(".dribbblish-config-close").on("click", () => this.close());
$(".dribbblish-config-backdrop").on("click", () => this.close());
$(".dribbblish-config-search").on("input", (e) => this.#search(e.target.value));
}
open() {
document.getElementById("dribbblish-config").setAttribute("active", "");
$("#dribbblish-config").attr("active", true);
}
close() {
document.getElementById("dribbblish-config").removeAttribute("active");
}
/**
* @private
* @param {DribbblishConfigItem} options
*/
addInputHTML(options) {
this.registerArea(options.area);
const parent = document.querySelector(`.dribbblish-config-area[name="${options.area.name}"] .dribbblish-config-area-items`);
const elem = document.createElement("div");
elem.style.order = options.order;
elem.classList.add("dribbblish-config-item");
elem.setAttribute("key", options.key);
elem.setAttribute("type", options.type);
elem.setAttribute("hidden", options.hidden);
if (options.childOf) elem.setAttribute("parent", options.childOf);
elem.innerHTML = /* html */ `
<h2 class="x-settings-title main-type-cello${!options.description ? " no-desc" : ""}" as="h2">${options.name}</h2>
<label class="main-type-mesto">${options.description.replace(/\n/g, "<br>")}</label>
<label class="x-toggle-wrapper x-settings-secondColumn">
${options.input}
</label>
`;
if (options.insertOnTop && parent.children.length > 0) {
parent.insertBefore(elem, parent.children[0]);
} else {
parent.appendChild(elem);
}
$("#dribbblish-config").removeAttr("active");
}
/**
@ -121,16 +112,20 @@ export default class ConfigMenu {
data: {},
name: "",
description: "",
hidden: false,
resetButton: true,
insertOnTop: false,
fireInitialChange: true,
save: true,
validate: () => true,
showChildren: () => true,
onAppended: () => {},
onChange: () => {},
children: [],
childOf: null
parent: null
};
// Set Defaults
options = { ...defaultOptions, ...options };
options = defaults(options, defaultOptions);
if (typeof options.area == "string") options.area = { name: options.area, order: 0 };
options.description = options.description
.split("\n")
@ -139,156 +134,173 @@ export default class ConfigMenu {
.join("\n");
options._onChange = options.onChange;
options.onChange = (val) => {
const isValid = validate(val) === true;
$(`.dribbblish-config-item[key="${options.key}"]`).attr("changed", options.type != "button" && isValid && val != options.defaultValue ? "" : null);
if (!isValid) return;
this.set(options.key, val, options.save);
options._onChange.call(options, val);
const show = options.showChildren.call(options, val);
options.children.forEach((child) => this.setHidden(child.key, Array.isArray(show) ? !show.includes(child.key) : !show));
};
options.children = options.children.map((child) => {
return { ...child, area: options.area, childOf: options.key };
return { ...child, area: options.area, parent: options.key, order: options.order ?? 0 + child.order ?? 0 };
});
this.#config[options.key] = options;
function validate(val) {
const isValid = options.validate.call(options, val);
const $elem = $(`.dribbblish-config-item[key="${options.key}"]`);
if (isValid === true) {
$elem.attr("invalid", null).css("--validation-error", "");
} else {
const error = isValid === false ? "Invalid" : isValid;
$elem.attr("invalid", "").css("--validation-error", `"${error.replace(/"/g, `\\"`)}"`);
}
return isValid;
}
let elem;
let input;
if (options.type == "checkbox") {
const input = /* html */ `
<input id="dribbblish-config-input-${options.key}" class="x-toggle-input" type="checkbox"${this.get(options.key) ? " checked" : ""}>
<span class="x-toggle-indicatorWrapper">
<span class="x-toggle-indicator"></span>
</span>
`;
this.addInputHTML({ ...options, input });
elem = document.createDocumentFragment();
document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener("change", (e) => {
this.set(options.key, e.target.checked);
options.onChange(this.get(options.key));
input = document.createElement("input");
input.classList.add("dribbblish-config-input");
input.type = "checkbox";
input.classList.add("x-toggle-input");
input.checked = this.get(options.key);
input.addEventListener("change", (e) => {
options.onChange(e.target.checked);
});
elem.appendChild(input);
const indicator = document.createElement("span");
indicator.classList.add("x-toggle-indicatorWrapper");
indicator.innerHTML = /* html */ `<span class="x-toggle-indicator"></span>`;
elem.appendChild(indicator);
} else if (options.type == "select") {
// Validate
const val = this.get(options.key);
if (!Object.keys(options.data).includes(val)) this.reset(options.key);
const input = /* html */ `
<select class="main-dropDown-dropDown" id="dribbblish-config-input-${options.key}">
${Object.entries(options.data)
.map(([key, name]) => `<option value="${key}"${this.get(options.key) == key ? " selected" : ""}>${name}</option>`)
.join("")}
</select>
`;
this.addInputHTML({ ...options, input });
document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener("change", (e) => {
this.set(options.key, e.target.value);
options.onChange(this.get(options.key));
input = document.createElement("select");
input.classList.add("dribbblish-config-input");
input.classList.add("main-dropDown-dropDown");
input.innerHTML = Object.entries(options.data)
.map(([key, name]) => `<option value="${key}"${this.get(options.key) == key ? " selected" : ""}>${name}</option>`)
.join("");
input.addEventListener("change", (e) => {
options.onChange(e.target.value);
});
} else if (options.type == "button") {
options.fireInitialChange = false;
if (typeof options.data != "string") options.data = options.name;
options.fireInitialChange = false;
options.resetButton = false;
options.save = false;
const input = /* html */ `
<button class="main-buttons-button main-button-primary" type="button" id="dribbblish-config-input-${options.key}">
<div class="x-settings-buttonContainer">
<span>${options.data}</span>
</div>
</button>
`;
this.addInputHTML({ ...options, input });
document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener("click", (e) => {
input = document.createElement("button");
input.classList.add("dribbblish-config-input");
input.type = "button";
input.classList.add("main-buttons-button", "main-button-primary");
input.innerHTML = /* html */ `<div class="x-settings-buttonContainer"><span>${options.data}</span></div>`;
input.addEventListener("click", (e) => {
options.onChange(true);
});
} else if (options.type == "number") {
// Validate
if (options.defaultValue == null) options.defaultValue = 0;
const val = this.get(options.key);
if (options.data.min != null && val < options.data.min) this.set(options.key, options.data.min);
if (options.data.max != null && val > options.data.max) this.set(options.key, options.data.max);
const _val = this.get(options.key);
if (options.data.min != null && _val < options.data.min) this.set(options.key, options.data.min, options.save);
if (options.data.max != null && _val > options.data.max) this.set(options.key, options.data.max, options.save);
const input = /* html */ `
<input type="number" id="dribbblish-config-input-${options.key}" value="${this.get(options.key)}">
`;
this.addInputHTML({ ...options, input });
// Prevent inputting +, - and e. Why is it even possible in the first place?
document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener("keypress", (e) => {
if (["+", "-", "e"].includes(e.key)) e.preventDefault();
input = document.createElement("input");
input.classList.add("dribbblish-config-input");
input.type = "number";
input.value = this.get(options.key);
input.step = options.data.step ?? 1;
if (options.data.min != null) input.min = options.data.min;
if (options.data.max != null) input.max = options.data.max;
input.addEventListener("keypress", (e) => {
// Prevent inputting + and e.
if (["+", "e"].includes(e.key)) e.preventDefault();
});
document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener("input", (e) => {
input.addEventListener("input", (e) => {
if (options.data.min != null && e.target.value < options.data.min) e.target.value = options.data.min;
if (options.data.max != null && e.target.value > options.data.max) e.target.value = options.data.max;
this.set(options.key, Number(e.target.value));
options.onChange(this.get(options.key));
options.onChange(Number(e.target.value));
});
} else if (options.type == "text") {
if (options.defaultValue == null) options.defaultValue = "";
const input = /* html */ `
<input type="text" id="dribbblish-config-input-${options.key}" value="${this.get(options.key)}">
`;
this.addInputHTML({ ...options, input });
input = document.createElement("input");
input.classList.add("dribbblish-config-input");
input.type = "text";
input.value = this.get(options.key);
input.addEventListener("input", (e) => {
options.onChange(e.target.value);
});
} else if (options.type == "textarea") {
if (options.defaultValue == null) options.defaultValue = "";
document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener("input", (e) => {
// TODO: maybe add an validation function via `data.validate`
this.set(options.key, e.target.value);
options.onChange(this.get(options.key));
input = document.createElement("textarea");
input.classList.add("dribbblish-config-input");
input.value = this.get(options.key);
input.addEventListener("input", (e) => {
options.onChange(e.target.value);
});
} else if (options.type == "slider") {
// Validate
if (options.defaultValue == null) options.defaultValue = 0;
const val = this.get(options.key);
if (options.data.min != null && val < options.data.min) this.set(options.key, options.data.min);
if (options.data.max != null && val > options.data.max) this.set(options.key, options.data.max);
if (options.data.min != null && val < options.data.min) this.set(options.key, options.data.min, options.save);
if (options.data.max != null && val > options.data.max) this.set(options.key, options.data.max, options.save);
const input = /* html */ `
<input
type="range"
id="dribbblish-config-input-${options.key}"
name="${options.name}"
min="${options.data?.min ?? "0"}"
max="${options.data?.max ?? "100"}"
step="${options.data?.step ?? "1"}"
value="${this.get(options.key)}"
tooltip="${this.get(options.key)}${options.data?.suffix ?? ""}"
>
`;
this.addInputHTML({ ...options, input });
document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener("input", (e) => {
document.getElementById(`dribbblish-config-input-${options.key}`).setAttribute("tooltip", `${e.target.value}${options.data?.suffix ?? ""}`);
document.getElementById(`dribbblish-config-input-${options.key}`).setAttribute("value", e.target.value);
this.set(options.key, Number(e.target.value));
options.onChange(this.get(options.key));
input = document.createElement("input");
input.classList.add("dribbblish-config-input");
input.type = "range";
input.step = options.data.step ?? 1;
input.min = options.data.min ?? 0;
input.max = options.data.max ?? 100;
input.value = this.get(options.key);
input.setAttribute("tooltip", `${this.get(options.key)}${options.data.suffix ?? ""}`);
input.addEventListener("input", (e) => {
options.onChange(Number(e.target.value));
$(`#dribbblish-config-input-${options.key}`).attr("tooltip", `${e.target.value}${options.data?.suffix ?? ""}`);
});
} else if (options.type == "time") {
// Validate
if (options.defaultValue == null) options.defaultValue = "00:00";
const input = /* html */ `
<input type="time" id="dribbblish-config-input-${options.key}" name="${options.name}" value="${this.get(options.key)}">
`;
this.addInputHTML({ ...options, input });
document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener("input", (e) => {
document.getElementById(`dribbblish-config-input-${options.key}`).setAttribute("value", e.target.value);
this.set(options.key, e.target.value);
options.onChange(this.get(options.key));
input = document.createElement("input");
input.classList.add("dribbblish-config-input");
input.type = "time";
input.value = this.get(options.key);
input.addEventListener("input", (e) => {
options.onChange(e.target.value);
});
} else if (options.type == "color") {
// Validate
if (options.defaultValue == null) options.defaultValue = "#000000";
const input = /* html */ `
<input type="color" id="dribbblish-config-input-${options.key}" name="${options.name}" value="${this.get(options.key)}">
`;
this.addInputHTML({ ...options, input });
document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener("input", (e) => {
this.set(options.key, e.target.value);
options.onChange(this.get(options.key));
input = document.createElement("input");
input.classList.add("dribbblish-config-input");
input.type = "color";
input.value = this.get(options.key);
input.addEventListener("input", (e) => {
options.onChange(e.target.value);
});
} else {
throw new Error(`Config Type "${options.type}" invalid`);
}
this.#addInputHTML({ ...options, input: elem ?? input });
options.input = input;
// Re-write internal config since some values may have changed
this.#config[options.key] = options;
validate(this.get(options.key));
$(`.dribbblish-config-item[key="${options.key}"]`).attr("changed", options.save && this.get(options.key) != options.defaultValue ? "" : null);
options.children.forEach((child) => this.register(child));
options.onAppended.call(options);
@ -299,22 +311,242 @@ export default class ConfigMenu {
* @param {DribbblishConfigArea} area
*/
registerArea(area) {
/** @type {DribbblishConfigArea} */
const defaultOptions = {
toggleable: true,
order: 0
};
area = { ...defaultOptions, ...area };
if (!document.querySelector(`.dribbblish-config-area[name="${area.name}"]`)) {
const areaElem = document.createElement("div");
areaElem.classList.add("dribbblish-config-area");
areaElem.style.order = area.order;
const uncollapsedAreas = JSON.parse(localStorage.getItem("dribbblish:config-areas:uncollapsed") ?? "[]");
if (!uncollapsedAreas.includes(area.name)) areaElem.toggleAttribute("collapsed");
areaElem.setAttribute("name", area.name);
areaElem.innerHTML = /* html */ `
<h2 class="dribbblish-config-area-header">
${area.name}
<svg height="24" width="24" viewBox="0 0 24 24" class="main-topBar-icon"><polyline points="16 4 7 12 16 20" fill="none" stroke="currentColor"></polyline></svg>
</h2>
<div class="dribbblish-config-area-items"></div>
this.#addAreaHTML(area);
} else {
throw new Error(`Area "${area.name}" already exists`);
}
}
/**
*
* @param {String} key
* @param {any} defaultValueOverride
* @returns {any}
*/
get(key, defaultValueOverride) {
const val = JSON.parse(this.#config[key]?.storageCache ?? localStorage.getItem(`dribbblish:config:${key}`) ?? null); // Turn undefined into null because `JSON.parse()` doesn't like undefined
if (val == null || val?.type != this.#config[key]?.type) {
localStorage.removeItem(`dribbblish:config:${key}`);
return defaultValueOverride ?? this.#config[key]?.defaultValue;
}
return val.value;
}
/**
*
* @param {String} key
* @param {any} val
* @param {Boolean} [save=true] if setting should be stored in localStorage
*/
set(key, val, save = true) {
val = { type: this.#config[key].type, value: val ?? this.#config[key].defaultValue };
this.#config[key].storageCache = JSON.stringify(val);
if (save) localStorage.setItem(`dribbblish:config:${key}`, JSON.stringify(val));
}
/**
*
* @param {String} key
*/
reset(key) {
delete this.#config[key].storageCache;
localStorage.removeItem(`dribbblish:config:${key}`);
const options = this.#config[key];
const defaultVal = this.get(options.key);
if (options.type == "checkbox") {
options.input.checked = defaultVal;
} else {
options.input.value = defaultVal;
if (options.type == "slider") options.input.setAttribute("tooltip", `${defaultVal}${options.data.suffix ?? ""}`);
}
options.onChange(defaultVal);
}
/**
*
* @param {String} key
* @param {Boolean} hidden
* @private
*/
setHidden(key, hidden, search = false) {
this.#config[key].hidden = hidden;
const $elem = $(`.dribbblish-config-item[key="${key}"]`);
$elem.attr(search ? "hidden-override" : "hidden", hidden ? "" : null);
// If element has children or a parent
if ($elem.attr("parent") != null || $elem.attr("children") != null) {
// Get parent of element block
const $parent = $elem.attr("parent") != null ? $(`[children~="${key}"]`) : $elem;
const $nextChildren = $parent.nextAll(`[parent="${$parent.attr("key")}"]`);
// Make parent connect on bottom when children are visible
$parent.attr("connect-bottom", $nextChildren.filter(":not([hidden]):not([hidden-override])").length > 0 ? "" : null);
// Reset all children's bottom connection
$nextChildren.each(function () {
$(this).attr("connect-bottom", null);
});
// Add bottom connection to all but the last visible child
$nextChildren
.filter(":not([hidden]):not([hidden-override])")
.slice(0, -1)
.each(function () {
$(this).attr("connect-bottom", "");
});
//* NOTE: All children automatically have a top connection
}
}
setDisabled(key, disabled) {
this.#config[key].input.disabled = disabled;
}
getOptions(key) {
return this.#config[key];
}
export() {
const obj = {
EXPORTED_WITH: process.env.DRIBBBLISH_VERSION
};
Object.entries(this.#config).forEach(([key, options]) => {
if (options.save) obj[key] = this.get(key);
});
return obj;
}
/**
* @param {String} text
*/
#search(text) {
text = text.trim().toLowerCase();
$(".dribbblish-config-area-header svg").css("display", text != "" ? "none" : "");
$(".dribbblish-config-area").attr("search", text != "" ? "" : null);
const cmp = (s) => s.trim().toLowerCase().includes(text.trim().toLowerCase());
/**
* @param {DribbblishConfigItem} options
*/
function cmpOpts(options) {
let matches = cmp(options.name) || cmp($(`.dribbblish-config-item[key="${options.key}"] .dribbblish-config-item-header label`).text());
if (!matches && options.type == "select") matches = Object.values(options.data).some(cmp);
return matches;
}
// Check every item
for (const [key, options] of Object.entries(this.#config)) {
this.setHidden(key, false, true);
if (text == "") continue;
const show = cmpOpts(options);
this.setHidden(key, !show, true);
}
// Check every item's children and show it when any child is visible
for (const [key, options] of Object.entries(this.#config)) {
if (options.children.length != 0 && $(`.dribbblish-config-item[parent="${options.key}"]`).filter(":not([hidden]):not([hidden-override])").length != 0) this.setHidden(key, false, true);
}
// Hide areas without visible children
for (const area of $(".dribbblish-config-area").toArray()) {
const $area = $(area);
const itemsVisible = $area.children(".dribbblish-config-area-items").children(":not([hidden]):not([hidden-override])").length;
$area.css("display", text != "" && itemsVisible == 0 ? "none" : "");
}
$(`.dribbblish-config-item`).filter(":not([hidden]):not([hidden-override])").length;
}
/**
* @private
* @param {DribbblishConfigItem} options
*/
#addInputHTML(options) {
if (!document.querySelector(`.dribbblish-config-area[name="${options.area.name}"]`)) this.registerArea(options.area);
const parent = document.querySelector(`.dribbblish-config-area[name="${options.area.name}"] .dribbblish-config-area-items`);
const elem = document.createElement("div");
if (options.order != 0) elem.style.order = options.order;
elem.classList.add("dribbblish-config-item");
elem.setAttribute("key", options.key);
elem.setAttribute("type", options.type);
if (options.hidden) elem.setAttribute("hidden", true);
if (options.parent) elem.setAttribute("parent", options.parent);
if (options.children.length > 0) elem.setAttribute("children", options.children.map((c) => c.key).join(" "));
elem.innerHTML = /* html */ `
${
options.name != null && options.description != null
? /* html */ `
<div class="dribbblish-config-item-header">
<h2 class="x-settings-title main-type-cello" as="h2" empty="${options.name == null}">${options.name}</h2>
<label class="main-type-mesto" empty="${options.description == null}" markdown>${renderMD(options.description)}</label>
</div>
`
: ""
}
<div class="dribbblish-config-item-input">
<label class="x-toggle-wrapper x-settings-secondColumn"></label>
</div>
`;
document.querySelector(".dribbblish-config-areas").appendChild(areaElem);
elem.querySelector(".dribbblish-config-item-input > label").appendChild(options.input);
if (options.resetButton && options.name != null && options.description != null) {
const resetBtn = document.createElement("button");
resetBtn.ariaLabel = "Reset";
resetBtn.className = "dribbblish-config-item-reset main-trackCreditsModal-closeBtn";
resetBtn.innerHTML = icons.get("delete-outline", { size: 20, title: "Reset Setting" });
resetBtn.addEventListener("click", () => {
this.reset(options.key);
});
elem.querySelector(".dribbblish-config-item-header > h2").appendChild(resetBtn);
}
if (options.insertOnTop && parent.children.length > 0) {
parent.insertBefore(elem, parent.children[0]);
} else {
parent.appendChild(elem);
}
}
/**
*
* @param {DribbblishConfigArea} area
*/
#addAreaHTML(area) {
const areaElem = document.createElement("div");
areaElem.classList.add("dribbblish-config-area");
if (area.order != 0) areaElem.style.order = area.order;
const uncollapsedAreas = JSON.parse(localStorage.getItem("dribbblish:config-areas:uncollapsed") ?? "[]");
if (area.toggleable && !uncollapsedAreas.includes(area.name)) areaElem.toggleAttribute("collapsed");
areaElem.setAttribute("name", area.name);
areaElem.innerHTML = /* html */ `
<h2 class="dribbblish-config-area-header">
${area.name}
${!area.toggleable ? "" : icons.get("expand-more", { size: 24, scale: 1.2 })}
</h2>
<div class="dribbblish-config-area-items"></div>
`;
document.querySelector(".dribbblish-config-areas").appendChild(areaElem);
if (area.toggleable) {
areaElem.querySelector("h2").addEventListener("click", () => {
if (document.querySelector(".dribbblish-config-search").value.trim() != "") return;
areaElem.toggleAttribute("collapsed");
let uncollapsedAreas = JSON.parse(localStorage.getItem("dribbblish:config-areas:uncollapsed") ?? "[]");
if (areaElem.hasAttribute("collapsed")) {
@ -326,53 +558,4 @@ export default class ConfigMenu {
});
}
}
/**
*
* @param {String} key
* @param {any} defaultValueOverride
* @returns {any}
*/
get(key, defaultValueOverride) {
const val = JSON.parse(this.#config[key]?.storageCache ?? localStorage.getItem(`dribbblish:config:${key}`) ?? null); // Turn undefined into null because `JSON.parse()` dosen't like undefined
if (val == null || val?.type != this.#config[key]?.type) {
localStorage.removeItem(`dribbblish:config:${key}`);
return defaultValueOverride ?? this.#config[key].defaultValue;
}
return val.value;
}
/**
*
* @param {String} key
* @param {any} val
*/
set(key, 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
* @param {Boolean} hidden
*/
setHidden(key, hidden) {
this.#config[key].hidden = hidden;
document.querySelector(`.dribbblish-config-item[key="${key}"]`).setAttribute("hidden", hidden);
}
getOptions(key) {
return this.#config[key];
}
}

80
src/js/Dribbblish.js Normal file
View file

@ -0,0 +1,80 @@
import ConfigMenu from "./ConfigMenu";
import Info from "./Info";
import Loader from "./Loader";
import Overlay from "./Overlay";
import Icon, { icons } from "./Icons";
export default class Dribbblish {
/**
* @typedef {"ready"} Event
*/
/**
* @callback listener
* @param {any} [data]
* @returns {void}
*/
/** @type {ConfigMenu} */
config;
/** @type {Info} */
info;
/** @type {Loader} */
loader;
/** @type {Overlay} */
overlay;
/** @type {Icon} */
icons;
/** @type {Object.<string, listener[]>} */
#listeners = {};
/** @type {Boolean} */
#ready = false;
constructor() {
this.config = new ConfigMenu();
this.info = new Info();
this.loader = new Loader();
this.overlay = new Overlay();
this.icons = icons;
let tries = 0;
const interval = setInterval(() => {
if (++tries > 50) throw new Error("ready timeout");
if (document.querySelector("#main") == null || Spicetify?.showNotification == undefined || !this.info.isReady()) return;
this.#ready = true;
this.emit("ready");
clearInterval(interval);
}, 200);
}
/**
* @param {Event} event
* @param {any} data
*/
emit(event, data) {
this.#listeners[event]?.forEach((listener) => listener(data));
}
/**
* @param {Event} event
* @param {listener} listener
*/
on(event, listener) {
this.#listeners[event] = [...(this.#listeners[event] ?? []), listener];
if (event == "ready" && this.#ready) listener();
}
/**
* @param {Event} event
* @param {listener} listener
*/
off(event, listener) {
this.#listeners = this.#listeners[event].filter((f) => f != listener);
}
}

98
src/js/Folders.js Normal file
View file

@ -0,0 +1,98 @@
import { waitForElement, htmlToNode } from "./Util";
import { icons } from "./Icons";
waitForElement([`.main-rootlist-rootlistPlaylistsScrollNode ul[tabindex="0"]`, `.main-rootlist-rootlistPlaylistsScrollNode ul[tabindex="0"] li`], ([root, firstItem]) => {
const listElem = firstItem.parentElement;
root.classList.add("dribs-playlist-list");
/** Replace Playlist name with their pictures */
async function loadPlaylistImage() {
for (const item of listElem.children) {
let link = item.querySelector("a");
if (!link) continue;
let [_, app, uid] = link.pathname.split("/");
if (link.querySelector("svg")) link.querySelector("svg").remove();
if (link.querySelector("img")) link.querySelector("img").remove();
const title = link.querySelector("span").innerText;
let elem;
if (app === "playlist") {
const {
metadata: { picture }
} = await Spicetify.CosmosAsync.get(`sp://core-playlist/v1/playlist/${Spicetify.URI.playlistV2URI(uid).toURI()}/metadata`, { policy: { picture: true } });
if (picture != null && picture.trim() != "") {
if (!link.querySelector("img")) elem = document.createElement("img");
elem.src = picture;
} else {
if (!link.querySelector("svg")) elem = htmlToNode(icons.get("note", { title }));
}
} else if (app === "folder") {
const base64 = localStorage.getItem("dribbblish:folder-image:" + uid);
if (base64 != null) {
if (!link.querySelector("img")) elem = document.createElement("img");
elem.src = base64;
} else {
if (!link.querySelector("svg")) elem = htmlToNode(icons.get("folder", { title }));
}
} else {
continue;
}
elem.title = title;
elem.classList.add("playlist-picture");
link.prepend(elem);
}
}
loadPlaylistImage();
new MutationObserver(loadPlaylistImage).observe(listElem, { childList: true });
const filePickerForm = document.createElement("form");
filePickerForm.setAttribute("aria-hidden", true);
filePickerForm.innerHTML = '<input type="file" class="hidden-visually" />';
document.body.appendChild(filePickerForm);
/** @type {HTMLInputElement} */
const filePickerInput = filePickerForm.childNodes[0];
filePickerInput.accept = ["image/jpeg", "image/apng", "image/avif", "image/gif", "image/png", "image/svg+xml", "image/webp"].join(",");
filePickerInput.onchange = () => {
if (!filePickerInput.files.length) return;
const file = filePickerInput.files[0];
const reader = new FileReader();
reader.onload = (event) => {
const result = event.target.result;
const id = Spicetify.URI.from(filePickerInput.uri).id;
try {
localStorage.setItem("dribbblish:folder-image:" + id, result);
} catch {
Spicetify.showNotification("File too large");
}
loadPlaylistImage();
};
reader.readAsDataURL(file);
};
new Spicetify.ContextMenu.Item(
"Remove folder image",
([uri]) => {
const id = Spicetify.URI.from(uri).id;
localStorage.removeItem("dribbblish:folder-image:" + id);
loadPlaylistImage();
},
([uri]) => Spicetify.URI.isFolder(uri),
"x"
).register();
new Spicetify.ContextMenu.Item(
"Choose folder image",
([uri]) => {
filePickerInput.uri = uri;
filePickerForm.reset();
filePickerInput.click();
},
([uri]) => Spicetify.URI.isFolder(uri),
"edit"
).register();
});

124
src/js/Icons.js Normal file
View file

@ -0,0 +1,124 @@
import { defaults } from "./Util";
import { parseSync as parseSVG, stringify as stringifySVG } from "svgson";
export default class Icons {
/** @typedef {"custom" | "material:baseline" | "material:outline" | "material:round" | "material:sharp" | "material:twotone"} IconStyle */
/**
* @typedef {Object} IconOptions
* @property {IconStyle} [style="round"]
* @property {String} [className=""]
* @property {Number} [size=16]
* @property {Number} [scale=1]
* @property {String} [fill="currentColor"]
* @property {Boolean} [base64=false]
*/
/** @type {Object.<String, Object.<IconStyle, String>>} */
#icons;
constructor(icons) {
this.#icons = icons ?? process.env.DRIBBBLISH_ICONS;
}
/**
* @param {String} name
* @param {IconStyle} style
* @returns {String}
*/
getRawSVG(name, style) {
if (style == null) style = this.#getDefaultStyle(name);
if (!this.#icons.hasOwnProperty(name)) throw new Error(`Icon "${name}" does not exist`);
if (!this.#icons[name].hasOwnProperty(style)) {
const styles = Object.keys(this.#icons[name])
.map((s) => `"${s}"`)
.join(", ");
throw new Error(`Icon "${name}" does not have style "${style}". It is available in styles [${styles}].`);
}
return this.#icons[name][style];
}
getAvailableStyles(name) {
if (!this.#icons.hasOwnProperty(name)) throw new Error(`Icon "${name}" does not exist`);
return Object.keys(this.#icons[name]);
}
/**
* @param {String} name
* @returns
*/
#getDefaultStyle(name) {
const styles = this.getAvailableStyles(name);
for (const s of ["custom", "material:round"]) {
if (styles.includes(s)) return s;
}
return styles[0];
}
/**
* @param {String} name icon name lowercase with dashes like `ac-unit`
* @param {IconOptions} options
* @see https://fonts.google.com/icons?selected=Material+Icons
* @returns
*/
get(name, options) {
/** @type {IconOptions} */
const defaultOptions = {
style: this.#getDefaultStyle(name),
className: "",
size: 16,
scale: 1,
fill: "currentColor",
base64: false
};
options = defaults(options, defaultOptions);
const svg = parseSVG(this.getRawSVG(name, options.style));
// Add general / required attributes
svg.attributes["icon-type"] = "dribbblish";
svg.attributes["icon-name"] = name;
svg.attributes["icon-style"] = options.style;
svg.attributes.fill = options.fill;
svg.attributes.width = options.size;
svg.attributes.height = options.size;
// Add className
if (options.className != "") svg.attributes.class = options.className;
// Add Styles
const styles = {};
if (options.scale != 1) {
styles["transform"] = `scale(${options.scale})`;
styles["transform-origin"] = "center";
}
const styleStr = Object.entries(styles)
.map(([prop, val]) => `${prop}: ${val};`)
.join(" ");
svg.children = svg.children.map((child) => {
if (styleStr != "") child.attributes.style = styleStr;
return child;
});
// Add title
if (options.title != null) {
svg.children.push({
name: "title",
type: "element",
value: "",
children: [{ name: "", type: "text", value: options.title, attributes: {}, children: [] }]
});
}
if (options.base64) {
return `data:image/svg+xml;base64,${Buffer.from(stringifySVG(svg)).toString("base64")}`;
} else {
return stringifySVG(svg);
}
}
}
export const icons = new Icons();

90
src/js/Info.js Normal file
View file

@ -0,0 +1,90 @@
import $ from "jquery";
import { icons } from "./Icons";
import { waitForElement } from "./Util";
export default class Info {
/**
* @typedef {Object} DribbblishInfo
* @property {String} [text]
* @property {String} [tooltip]
* @property {String} [icon] svg string or icon name
* @property {DribbblishInfoColor} [color]
* @property {Number} [order=0] order < 0 = More to the Left | order > 0 = More to the Right
* @property {onClick} [onClick]
*/
/**
* @typedef {Object} DribbblishInfoColor
* @property {String} [fg]
* @property {String} [bg]
*/
/**
* @callback onClick
* @returns {void}
*/
/** @type {HTMLDivElement} */
#container;
/** @type {MarkdownIt} */
#md;
/** @type {Boolean} */
#ready = false;
constructor() {
waitForElement([".main-topBar-container", ".main-userWidget-box"], ([topBarContainer, userWidget]) => {
this.#container = document.createElement("div");
this.#container.id = "dribbblish-info-container";
topBarContainer.insertBefore(this.#container, userWidget);
this.#ready = true;
});
}
isReady() {
return this.#ready;
}
/**
* @param {String} key
* @param {DribbblishInfo} info
*/
set(key, info) {
if (!this.#ready) {
setTimeout(() => this.set(key, info), 200);
return;
}
this.remove(key);
if (info == null) return;
if (info.text == null && info.icon == null) throw new Error("invalid info");
const elem = document.createElement("div");
elem.classList.add("dribbblish-info-item");
elem.addEventListener("click", info.onClick);
elem.setAttribute("key", key);
if (info.text == null) elem.setAttribute("icon-only", "");
if (info.tooltip != null) elem.setAttribute("title", info.tooltip);
if (info.onClick != null) elem.setAttribute("clickable", "");
if (info.color != null) {
const { fg, bg } = info.color;
if (fg != null) elem.style.color = fg;
if (bg != null) elem.style.backgroundColor = bg;
}
if (info.order != 0) elem.style.order = info.order;
if (!info.icon.startsWith("<svg")) info.icon = icons.get(info.icon, { size: 18 });
elem.innerHTML = `${info.text ?? ""}${info.icon ?? ""}`;
this.#container.appendChild(elem);
}
/**
* @param {String} key
*/
remove(key) {
$(this.#container).find(`.dribbblish-info-item[key="${key}"]`).remove();
}
}

18
src/js/Loader.js Normal file
View file

@ -0,0 +1,18 @@
import Overlay from "./Overlay";
export default class Loader {
/** @type {Overlay} */
#overlay;
constructor() {
this.#overlay = new Overlay();
}
show(text) {
this.#overlay.show({ icon: "loading", text });
}
hide() {
this.#overlay.hide();
}
}

68
src/js/Overlay.js Normal file
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)})`;
}
}

95
src/js/Util.js Normal file
View file

@ -0,0 +1,95 @@
import MarkdownIt from "markdown-it";
import MarkdownItAttrs from "markdown-it-attrs";
import defaultsDeep from "lodash.defaultsdeep";
import { default as _debounce } from "lodash.debounce";
/**
* @callback waitForElCb
* @param {HTMLElement[]} queries
*/
/**
*
* @param {String[]} els
* @param {waitForElCb} cb
* @param {Number} [tries=100]
* @param {Number} [interval=300]
*/
export function waitForElement(els, cb, tries = 100, interval = 300) {
const queries = els.map((el) => document.querySelector(el));
if (queries.every((a) => a)) {
cb(queries);
} else if (tries > 0) {
setTimeout(waitForElement, interval, els, cb, --tries);
}
}
export 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;
}
export function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
export function getClosestToNum(arr, num) {
return arr.reduce((prev, curr) => (Math.abs(curr - num) < Math.abs(prev - num) ? curr : prev));
}
export function renderMD(src, env) {
const md = MarkdownIt("commonmark", {
html: true,
breaks: true,
linkify: true,
typographer: true
});
md.use(MarkdownItAttrs);
return md.render(src, env);
}
export function htmlToNode(htmlStr) {
var div = document.createElement("div");
div.innerHTML = htmlStr.trim();
return div.firstChild;
}
export function getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min;
}
export function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
/**
* @template T
* @param {T[]} arr
* @returns {T}
*/
export function randomFromArray(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}
/** @type {_debounce} */
export function debounce(fn, wait, opts) {
return _debounce(fn, wait, opts);
}
/**
* @param {Object} options
* @param {...Object} defaults
* @returns {Object}
*/
export function defaults(options, ...defaults) {
return defaultsDeep(options, ...defaults);
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,18 @@
const TRIM_REGEX = /colors: \((?<colors>.*?)\);/s;
const COLOR_REGEX = /(?<key>[\w-]*?):.*?#(?<color>.*?),?$/gm;
module.exports = function (content, map, meta) {
const colors = content
.match(TRIM_REGEX)
.groups.colors.split("\n")
.map((l) => l.trim())
.join("\n");
const matches = [...colors.matchAll(COLOR_REGEX)];
const ini = ["[base]"];
for (let i = 0; i < matches.length; i++) {
const { key, color } = matches[i].groups;
ini.push(`${key} = ${color}`);
}
return ini.join("\n");
};

46
src/styles/Colors.scss Normal file
View file

@ -0,0 +1,46 @@
$colors: (
text: #ffffff,
subtext: #f0f0f0,
sidebar-text: #ffffff,
main: #000000,
sidebar: #121212,
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
);
// Set Transitions
@each $key, $color in $colors {
@property --spice-#{$key} {
syntax: "<color>";
initial-value: $color;
inherits: true;
}
@property --spice-rgb-#{$key} {
syntax: "<number>, <number>, <number>";
initial-value: #{red($color)}, #{green($color)}, #{blue($color)};
inherits: true;
}
}
$props-to-transition: ("sidebar", "main", "text", "button");
:root {
$props: ();
@each $key in $props-to-transition {
$props: append($props, --spice-#{$key}, comma);
$props: append($props, --spice-rgb-#{$key}, comma);
}
transition: all var(--song-transition-speed) linear;
transition-property: $props;
}

View file

@ -5,54 +5,80 @@
inset: 0px;
align-items: center;
justify-content: center;
color: var(--spice-text);
color: spiceColor("text");
&[active] {
display: flex;
& ~ #main::after {
z-index: 4;
position: absolute;
content: "";
inset: 0px;
backdrop-filter: blur(3px) brightness(60%);
}
}
.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;
gap: 8px;
flex-direction: column;
align-items: center;
justify-content: center;
@include spiceGlass();
> h2 {
font-size: 32px;
line-height: 32px;
}
.dribbblish-config-close {
position: absolute;
top: 15px;
right: 15px;
padding: 0px;
top: 24px;
right: 24px;
}
.dribbblish-config-search {
// TODO: improve styles
}
.dribbblish-config-areas {
display: flex;
width: 100%;
flex-direction: column;
gap: 16px;
gap: 8px;
max-height: 60vh;
overflow-y: auto;
padding: 0px 50px;
padding: 0px 26px;
.dribbblish-config-area {
display: flex;
flex-direction: column;
gap: 16px;
align-items: center;
gap: 8px;
&[collapsed] {
&[collapsed]:not([search]) {
overflow: hidden;
min-height: 38px; //for some reason height alone isn't enough
height: 38px;
> h2 svg {
transform: rotate(270deg);
.dribbblish-config-area-header {
svg {
transform: rotate(0deg);
}
&:hover svg {
transform: rotate(0deg) scale(1.1);
}
&:active svg {
transform: rotate(0deg) scale(0.9);
}
}
}
@ -63,65 +89,158 @@
.dribbblish-config-area-header {
position: relative;
text-align: center;
width: fit-content;
height: 38px;
display: flex;
gap: 10px;
align-items: center;
justify-content: center;
svg {
position: absolute;
margin-left: 10px;
bottom: -2px;
color: var(--spice-text);
color: spiceColor("text");
padding: 0px;
height: 100%;
stroke-width: 2px;
transform: rotate(90deg);
transform: rotate(180deg);
}
&:hover svg {
transform: rotate(180deg) scale(1.1);
}
&:active svg {
transform: rotate(180deg) scale(0.9);
}
}
.dribbblish-config-area-items {
display: flex;
flex-direction: column;
gap: 16px;
gap: 8px;
width: 100%;
.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";
display: flex;
justify-content: space-between;
align-items: center;
gap: 10px;
padding: 8px 16px;
user-select: text;
&[parent] {
padding-left: 16px;
}
&[hidden="true"] {
&[hidden],
&[hidden-override] {
display: none;
}
> {
.x-settings-title {
grid-area: header;
margin: 0px;
height: min-content;
position: relative;
bottom: 0px;
&::before {
z-index: -1;
content: "";
position: absolute;
inset: 0px;
border-radius: var(--main-corner-radius);
background-color: spiceColor("subtext", 0.03, 0.04);
}
&.no-desc {
bottom: -10px;
&[parent]::before {
top: -8px;
border-top-left-radius: 0px;
border-top-right-radius: 0px;
}
&[connect-bottom]::before {
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
}
&[invalid] {
&::before {
border: 2px solid rgba(red, 0.8);
}
.dribbblish-config-item-input::before {
content: var(--validation-error);
margin-right: 8px;
color: rgba(red, 0.8);
}
}
&[changed] {
&::after {
content: "";
position: absolute;
left: 0px;
top: 0px;
bottom: 0px;
width: 5px;
background-color: spiceColor("text");
border-top-left-radius: var(--main-corner-radius);
border-bottom-left-radius: var(--main-corner-radius);
}
&[parent]::after {
top: -4px;
border-top-left-radius: 0px;
}
&[connect-bottom]::after {
bottom: -4px;
border-bottom-left-radius: 0px;
}
.dribbblish-config-item-reset {
display: block !important;
}
}
.dribbblish-config-item-header {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 5px;
> {
& [empty="true"] {
display: none;
}
.x-settings-title {
display: flex;
gap: 5px;
align-items: center;
grid-area: header;
margin: 0px;
height: min-content;
position: relative;
bottom: 0px;
.dribbblish-config-item-reset {
display: none;
width: 20px;
height: 20px;
padding: 0px;
color: spiceColor("text");
}
}
.main-type-mesto {
grid-area: description;
height: min-content;
color: spiceColor("subtext");
line-height: calc(1em + 6px); // To have line gaps
}
.x-settings-secondColumn {
grid-area: input;
}
}
}
.main-type-mesto {
grid-area: description;
height: min-content;
color: var(--spice-subtext);
}
.x-settings-secondColumn {
grid-area: input;
}
.dribbblish-config-item-input {
display: flex;
min-width: fit-content;
}
}
}
@ -133,6 +252,5 @@
position: absolute;
content: "";
inset: 0px;
backdrop-filter: blur(3px) brightness(60%);
}
}

View file

@ -0,0 +1,41 @@
.connect-device-list-container {
padding: 8px;
@include spiceGlass(lightOffset(0.8, -0.1));
&::before {
display: none;
}
.connect-device-list-content {
display: flex;
flex-direction: column;
gap: 8px;
width: 100%;
.connect-header {
padding: 0px;
filter: invert(var(--is_light));
}
.connect-device-list {
display: flex;
flex-direction: column;
gap: 8px;
overflow-y: auto;
.connect-device-list-item {
background-color: transparent;
border-radius: var(--main-corner-radius);
&:hover,
&:active {
background-color: spiceColor("subtext", 0.1, 0.1);
}
&--active {
background-color: spiceColor("selected-row", 0.2) !important;
}
}
}
}
}

View file

@ -0,0 +1,35 @@
.main-contextMenu-menu {
position: relative;
overflow: visible;
background-color: transparent !important;
border-radius: var(--main-corner-radius);
padding: 8px;
&::before {
z-index: -1;
content: "";
position: absolute;
inset: 0px;
@include spiceGlass(lightOffset(0.8, -0.1));
}
.main-contextMenu-menuItem {
.main-contextMenu-menuItemButton {
border-radius: var(--main-corner-radius);
color: spiceColor("subtext") !important;
&:hover,
&:active,
&[aria-expanded="true"] {
background-color: spiceColor("subtext", 0.1, 0.3);
}
&::before,
&::after {
left: 8px;
right: 8px;
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");
}
}
}
}
}
}

57
src/styles/Fonts.scss Normal file
View file

@ -0,0 +1,57 @@
// GoogleSans: https://www.cufonfonts.com/font/google-sans
// Roboto: https://fonts.google.com/specimen/Roboto
$font-names: (
"glue": "GoogleSans",
"info": "Roboto"
);
$font-weights: (
"Thin": 100,
"Light": 300,
"Regular": 400,
"Medium": 500,
"Bold": 700,
"Black": 900
);
// add @font-face rules
@each $prefix, $font in $font-names {
@each $style, $weight in $font-weights {
@font-face {
font-family: $font;
font-weight: $weight;
font-style: normal;
src: url(font64("#{$font}-#{$style}.ttf")) format("truetype");
}
}
}
// spiceFont mixin
@mixin spiceFont($type: "glue", $size: null, $weight: null) {
@if map-has-key($font-names, $type) {
font-family: var(--#{$type}-font-family);
} @else {
@error "$type is invalid";
}
@if $size != null {
font-size: $size;
}
@if $weight != null {
@if type-of($weight) == "number" and unit($weight) == "" {
font-weight: $weight;
} @else if map-has-key($font-weights, $weight) {
font-weight: #{map-get($font-weights, $weight)};
} @else {
@error "$weight is invalid";
}
}
}
// set font variables
:root {
--glue-font-family: #{map-get($font-names, "glue")}, #{map-get($font-names, "info")}, spotify-circular, spotify-circular-cyrillic, spotify-circular-arabic, spotify-circular-hebrew, Helvetica Neue, helvetica, arial, Hiragino Kaku Gothic Pro, Meiryo, MS Gothic, sans-serif;
--info-font-family: #{map-get($font-names, "info")}, spotify-circular, spotify-circular-cyrillic, spotify-circular-arabic, spotify-circular-hebrew, Helvetica Neue, helvetica, arial, Hiragino Kaku Gothic Pro, Meiryo, MS Gothic, sans-serif;
font-family: var(--glue-font-family);
letter-spacing: normal;
}

7
src/styles/Icons.scss Normal file
View file

@ -0,0 +1,7 @@
svg[icon-type="dribbblish"] {
overflow: visible;
// &[icon-style="custom"] {}
// &[icon-style^="material:"] {}
}

32
src/styles/Info.scss Normal file
View file

@ -0,0 +1,32 @@
#dribbblish-info-container {
display: flex;
gap: 8px;
height: 100%;
&:empty {
display: none;
}
& > div {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
height: 100%;
padding: 0px 8px;
color: spiceColor("sidebar-text");
background-color: spiceColor("sidebar");
border-radius: var(--sidebar-icons-border-radius);
line-height: 13px;
@include spiceFont("glue", 14px, "Medium");
&[clickable] {
cursor: pointer;
}
&[icon-only] {
padding: 0px;
aspect-ratio: 1/1;
}
}
}

View file

@ -1,26 +1,109 @@
button.main-button-primary {
background-color: rgba(var(--spice-rgb-selected-row), 0.4) !important;
.main-button {
&-primary {
$color: spiceColor("subtext");
color: $color;
background-color: spiceColor("selected-row", 0.4) !important;
&:hover,
&:active {
background-color: rgba(var(--spice-rgb-selected-row), 0.6) !important;
&:hover,
&:active {
background-color: spiceColor("selected-row", 0.6) !important;
}
span {
color: $color !important;
}
}
span {
color: var(--spice-subtext) !important;
// ? Don't know if this exists
// &-secondary {
// $color: spiceColor("subtext");
// color: $color;
// background-color: spiceColor("selected-row", 0.4) !important;
// &:hover,
// &:active {
// background-color: spiceColor("selected-row", 0.6) !important;
// }
// span {
// color: $color !important;
// }
// }
// ? the `:not(...)` is to fix #137
&-tertiary:not(.main-entityHeader-titleButton) {
$color: spiceColor("subtext");
color: $color;
background-color: spiceColor("selected-row", 0.2, 0.05) !important;
&:hover,
&:active {
background-color: spiceColor("selected-row", 0.6) !important;
}
span {
color: $color !important;
}
}
}
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;
// Checkbox
.x-toggle-indicatorWrapper {
background-color: spiceColor("subtext", 0.1);
&:hover,
&:active {
background-color: rgba(var(--spice-rgb-selected-row), 0.6) !important;
input:not(:disabled):hover ~ & {
background-color: spiceColor("subtext", 0.15) !important;
}
input:checked ~ & {
background-color: spiceColor("selected-row", 0.4) !important;
}
input:not(:disabled):hover:checked ~ & {
background-color: spiceColor("selected-row", 0.6) !important;
}
}
textarea,
select,
input {
background-color: spiceColor("selected-row", 0.4) !important;
border-radius: 4px !important;
color: spiceColor("subtext") !important;
outline: none;
border: none;
&:not(:disabled) {
&:hover,
&:active {
background-color: spiceColor("selected-row", 0.6) !important;
}
}
&:disabled {
cursor: not-allowed;
opacity: 0.5 !important;
}
&::placeholder {
color: spiceColor("subtext");
opacity: lightOffset(0.8, 0.1);
}
}
textarea {
padding: 6px 10px;
}
select > option {
background: spiceColor("main") !important;
}
input {
padding: 6px 10px 6px 48px;
&[type="checkbox"] {
opacity: 0 !important;
}
&[type="range"] {
@ -34,7 +117,7 @@ textarea {
height: 16px;
margin-top: -4px;
border-radius: 50%;
background-color: var(--spice-text);
background-color: spiceColor("text");
&:hover,
&:active {
@ -42,73 +125,55 @@ textarea {
}
}
&::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;
}
&[tooltip] {
&::after {
z-index: 9999;
content: attr(tooltip);
position: absolute;
min-width: 50px;
top: -22px;
left: 50%;
transform: translateX(calc(-50%));
padding: 0 5px;
border-radius: 4px;
text-align: center;
color: spiceColor("sidebar-text");
background-color: spiceColor("button");
transition: opacity 0.25s ease;
opacity: 0;
}
&:hover::after,
&:active::after {
opacity: 1;
}
&:focus {
outline: none;
&:hover::after,
&:active::after {
opacity: 1;
}
}
&::-webkit-slider-runnable-track {
width: 100%;
height: 8px;
background-color: rgba(var(--spice-rgb-text), 0.2);
background-color: spiceColor("text", 0.2);
border-radius: 50vw;
}
}
&[type="number"],
&[type="text"],
&[type="search"],
&[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)));
filter: invert(var(--is_dark));
}
}
&[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);
}
padding: 1px 3px;
}
}

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`

21
src/styles/Markdown.scss Normal file
View file

@ -0,0 +1,21 @@
*[markdown] {
ol,
ul {
list-style: inside;
> li::marker {
content: "";
font-weight: 900;
}
}
code {
background-color: spiceColor("subtext", 0.1);
padding: 0px 5px;
border-radius: 3px;
}
.muted {
color: spiceColor("subtext", 0.5, 0.2);
}
}

67
src/styles/Modals.scss Normal file
View file

@ -0,0 +1,67 @@
.GenericModal__overlay {
backdrop-filter: blur(3px) brightness(60%);
background-color: transparent;
.GenericModal {
z-index: 1;
position: relative;
padding: 24px 16px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
@include spiceGlass();
// Popups (`Spicetify.PopupModal.display()`)
generic-modal & {
color: spiceColor("text");
width: clamp(500px, 50%, 650px);
}
// Dialogs (Delete Folder)
.ReactModalPortal > & {
> * {
color: spiceColor("subtext");
background-color: transparent;
padding: 0px;
}
}
.main- {
&trackCreditsModal,
&embedWidgetGenerator {
&-container {
display: flex;
gap: 16px;
background-color: transparent;
width: 100%;
box-shadow: none;
}
&-header {
display: flex;
gap: 16px;
padding: 0px;
width: 100%;
border: none;
h1 {
text-align: center;
width: 100%;
@include spiceFont("glue", 2em, "Bold");
}
button {
position: absolute;
top: 16px;
right: 16px;
}
}
&-mainSection {
padding: 0px 26px;
}
}
}
}
}

View file

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

42
src/styles/Overlay.scss Normal file
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;
}
}
}
}

16
src/styles/Util.scss Normal file
View file

@ -0,0 +1,16 @@
// SASS overwrites the CSS invert function so we overwrite it back
@function invert($v) {
@return #{"invert("}$v#{")"};
}
@mixin spiceShadow() {
box-shadow: 0px 0px 8px spiceColor("subtext", 0.1, 0.1);
}
@mixin spiceGlass($opacity: 0.95) {
background-color: spiceColor("main", $opacity);
backdrop-filter: blur(10px);
border: 1px solid spiceColor("subtext", 0.1, 0.1);
border-radius: var(--main-corner-radius);
@include spiceShadow();
}

File diff suppressed because it is too large Load diff

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
$configFile = Get-Content "$spicePath\config-xpui.ini"

16
uninstall.sh Normal file
View file

@ -0,0 +1,16 @@
#!/bin/sh
# Copyright 2019 khanhas. GPL license.
# Edited from project Denoland install script (https://github.com/denoland/deno_install)
set -e
echo "UN-INSTALLING"
cd "$(dirname "$(spicetify -c)")"
spicetify config current_theme "SpicetifyDefault" extensions dribbblish-dynamic.js-
echo "UN-PATCHING"
if cat config-xpui.ini | grep -o '\[Patch\]'; then
perl -i -0777 -pe "s/\[Patch\].*?($|(\r*\n){2})//s" config-xpui.ini
fi
spicetify apply

110
webpack.config.babel.js Normal file
View file

@ -0,0 +1,110 @@
import webpack from "webpack";
import sass from "sass";
import { CleanWebpackPlugin } from "clean-webpack-plugin";
import path from "path";
import fs from "fs";
import Icons from "./src/js/Icons";
import { lightOffset, spiceColor } from "./src/js/SassUtil";
const _icons = {};
function addIcon(name, style, path) {
name = name.replace(/_/g, "-");
if (!_icons.hasOwnProperty(name)) _icons[name] = {};
_icons[name][style] = fs.readFileSync(path, { encoding: "utf8" });
}
// Add Material Icons
let iconDir = path.resolve(__dirname, "node_modules/@material-icons/svg/svg");
for (const dir of fs.readdirSync(iconDir)) {
for (const file of fs.readdirSync(path.resolve(iconDir, dir))) {
addIcon(dir, `material:${file.replace(/\..*?$/, "")}`, path.resolve(iconDir, dir, file));
}
}
// Add Custom Icons
iconDir = path.resolve(__dirname, "src/icons");
for (const icon of fs.readdirSync(iconDir)) {
addIcon(icon.replace(/\..*?$/, ""), "custom", path.resolve(iconDir, icon));
}
const icons = new Icons(_icons);
/** @type {import('webpack').Configuration} */
const config = {
mode: "development",
entry: [path.resolve(__dirname, "src/js/main.js"), path.resolve(__dirname, "src/styles/main.scss"), path.resolve(__dirname, "src/styles/Colors.scss")],
output: {
path: path.resolve(__dirname, "dist"),
filename: "dribbblish-dynamic.js"
},
module: {
rules: [
{
include: path.resolve(__dirname, "src/js/main.js"),
use: []
},
{
include: path.resolve(__dirname, "src/styles/main.scss"),
type: "asset/resource",
generator: {
filename: "user.css"
},
use: [
{
loader: "sass-loader",
options: {
implementation: sass,
sourceMap: true,
sassOptions: {
functions: {
"lightOffset($n, $offset)": (n, offset) => {
return new sass.types.String(lightOffset(n.getValue(), offset.getValue()));
},
"spiceColor($key, $alpha: 1, $light-offset: 0)": (key, alpha, _lightOffset) => {
return new sass.types.String(spiceColor(key.getValue(), alpha.getValue(), _lightOffset.getValue()));
},
"font64($font)": (font) => {
const file = path.resolve(__dirname, "src/fonts", font.getValue());
return new sass.types.String(`"data:font/truetype;charset=utf-8;base64,${fs.readFileSync(file, { encoding: "base64" })}"`);
},
'icon64($name, $options: "{}")': (name, options) => {
name = name.getValue();
options = JSON.parse(options.getValue());
return new sass.types.String(icons.get(name, { ...options, base64: true }));
}
}
}
}
}
]
},
{
include: path.resolve(__dirname, "src/styles/Colors.scss"),
type: "asset/resource",
generator: {
filename: "color.ini"
},
use: [path.resolve(__dirname, "src/loaders/color-loader.js")]
}
]
},
devtool: "inline-source-map",
plugins: [
new webpack.DefinePlugin({
"process.env.BUG_REPORT": JSON.stringify(
fs
.readFileSync(path.resolve(__dirname, ".github/ISSUE_TEMPLATE/bug_report.md"), { encoding: "utf8" })
.replace(/^---(.*?\r?\n)+---(.*?\r?\n)*?(?=\S)/, "") // Replace GitHub header
.replace(/\*\*Desktop Setup\*\*\r?\n(- .*?\r?\n)+\r?\n/, "") // Replace **Desktop Setup** block
),
"process.env.DRIBBBLISH_ICONS": JSON.stringify(_icons),
"process.env.DRIBBBLISH_VERSION": JSON.stringify(process.env.DRIBBBLISH_VERSION ?? "Dev"),
"process.env.COMMIT_HASH": JSON.stringify(process.env.COMMIT_HASH ?? "local")
}),
new CleanWebpackPlugin({
protectWebpackAssets: false,
cleanAfterEveryBuildPatterns: ["*.LICENSE.txt"]
})
]
};
export default config;

View file

@ -1,56 +0,0 @@
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()]
}
};