Compare commits

...

149 commits
4.0.0 ... 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
66 changed files with 11672 additions and 1267 deletions

3
.babelrc Normal file
View file

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

View file

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

View file

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

View file

@ -12,12 +12,38 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
with:
repository: ${{ github.event.repository.full_name }}
ref: ${{ github.event.repository.default_branch }}
fetch-depth: 0
- name: Put version in env
run: echo "DRIBBBLISH_VERSION=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV
- name: Get commit SHA
run: echo "LATEST_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: "14"
- name: Build Webpack
run: |
npm install
npm run build
echo "${{ env.DRIBBBLISH_VERSION }}-${{ env.LATEST_SHA }}" > dist/VERSION
env:
DRIBBBLISH_VERSION: ${{ env.DRIBBBLISH_VERSION }}
COMMIT_HASH: ${{ env.LATEST_SHA }}
- name: Push to release branch
uses: s0/git-publish-subdir-action@develop
env:
REPO: self
BRANCH: release-stable
FOLDER: dist
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Set package.json version
run: |
sed -i 's/"version":.*",/"version": "${{ env.DRIBBBLISH_VERSION }}",/' package.json

View file

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

View file

@ -28,13 +28,13 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: "14"
node-version: "15"
- name: Build Webpack
run: |
npm install
npm run build
echo "${{ github.event.inputs.version }}" > dist/VERSION
echo "${{ github.event.inputs.version }}-${{ env.LATEST_SHA }}" > dist/VERSION
env:
DRIBBBLISH_VERSION: ${{ github.event.inputs.version }}
COMMIT_HASH: ${{ env.LATEST_SHA }}

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

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

View file

@ -10,6 +10,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
with:
repository: ${{ github.event.repository.full_name }}
ref: ${{ github.head_ref }}
- name: Get commit SHA
@ -18,18 +19,28 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: "14"
node-version: "15"
- name: Test-Build Webpack
run: |
npm install
npm run build
echo "Dev" > dist/VERSION
echo "Beta-${{ env.LATEST_SHA }}" > dist/VERSION
env:
DRIBBBLISH_VERSION: Beta
COMMIT_HASH: ${{ env.LATEST_SHA }}
- name: Push to beta-release branch
if: github.event_name == 'push'
uses: s0/git-publish-subdir-action@develop
env:
REPO: self
BRANCH: release-beta
FOLDER: dist
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload Artifact
uses: actions/upload-artifact@v2
with:
name: DribbblishDynamic_vDev-${{ env.LATEST_SHA }}
name: DribbblishDynamic_vBeta-${{ env.LATEST_SHA }}
path: dist/**

View file

@ -1,24 +1,13 @@
### Important changes
This release changes the color selection algorithm.
If you liked the old one better you can just change `Theme > Color Selection Algorithm` to `Vibrant`.
Added:
- `Report Bugs`, `Request Feature` and `Changelog` buttons to `Settings > About`
- Markdown parsing for settings descriptions
- Option to have a button to open the settings next to your profile picture
- Ability to hide premium features in addition to ads
- Ability to show current artist's genres
Fixed:
- Fonts looking blurry
- Notification popups are being invisible when the (dribbblish) settings are open
- Missing on/off times settings for `Based on Time` dark mode (#107)
- Playing icon position being wrong when listening to a playlist that is inside a folder ([#106 (comment)](https://github.com/JulienMaille/dribbblish-dynamic-theme/issues/106#issuecomment-967208507))
- Alignment of right expanded cover
- Slider tooltip is incorrect after a reset (#111)
- Custom search input not being focussed after clicking
- When changing songs, the color shifts to blue for a second (#179)
Improved:
- The settings UI now better represents grouped items
- Settings that have been changed from default will now show a line next to them. Inspired by the [Visual Studio Code settings UI](https://d33wubrfki0l68.cloudfront.net/d1f1ea4def506997ced23d3d912154794e530e1c/063d2/assets/img/blog/2020-09-17-vscode-settings/settings-ui.png)
- Checkbox / Switch input styles are now more in line with other input styles
- Available updates are now shown as a clickable button next to your user icon instead of having to open the user menu
- The "offline" icon is now handled by dribbblish and fits in with the other info icons
- Hovering over the release date in the album info now shows the full date
- Add `embedWidgetGenerator` modals to custom modal styles. (Things like the [spicetify-marketplace](https://github.com/CharlieS1103/spicetify-marketplace) options)
- Changed custom app tab styles to fit in with the theme
- Changed notification styles to fit in with other elements of the theme
- The visualizer icon when playing a song from a playlist is now colored

View file

@ -1,26 +1,29 @@
# Dribbblish Dynamic
A theme for [Spicetify](https://github.com/khanhas/spicetify-cli)
<a href="https://github.com/JulienMaille/dribbblish-dynamic-theme/releases/latest"><img src="https://img.shields.io/github/release/JulienMaille/dribbblish-dynamic-theme/all.svg"></a>
<a href="https://github.com/JulienMaille/dribbblish-dynamic-theme/releases"><img src="https://img.shields.io/github/downloads/JulienMaille/dribbblish-dynamic-theme/total.svg"></a>
### Preview
<img src="showcase-images/preview.gif" alt="img" width="500px">
<img src="https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/main/showcase-images/preview.gif" alt="img" width="500px">
## Features
### Resizable sidebar
<img src="showcase-images/resize-sidebar.png" alt="img" width="500px">
<img src="https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/main/showcase-images/resize-sidebar.png" alt="img" width="500px">
### Customizable sidebar
Rearrange icons positions, stick icons to header or hide unnecessary to save space.
Turn on "Sidebar config" mode in Profile menu and hover on icon to show control buttons.
After you finish customizing, turn off Config mode in Profile menu to save.
<img src="showcase-images/customize-sidebar.png" alt="img" width="500px">
<img src="https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/main/showcase-images/customize-sidebar.png" alt="img" width="500px">
### Playlist Folder image
Right click at folder and choose images for your playlist folder. Every image formats supported by Chrome can be used, but do keep image size small and in compressed format.
<img src="showcase-images/playlist-folders.gif" alt="img" width="500px">
<img src="https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/main/showcase-images/playlist-folders.gif" alt="img" width="500px">
### Left/Right expanded cover
In profile menu, toggle option "Right expanded cover" to change expanded current track cover image to left or right side, wherever you prefer.
@ -42,7 +45,7 @@ curl -fsSL https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-the
1. Download the latest [DribbblishDynamic_vX.X.X.zip](https://github.com/JulienMaille/dribbblish-dynamic-theme/releases/latest)
2. Extract the files to your [Spicetify/Themes folder](https://github.com/khanhas/spicetify-cli/wiki/Customization#themes)
3. Copy `dribbblish-dynamic.js` to your [Spicetify/Extensions folder](https://github.com/khanhas/spicetify-cli/wiki/Extensions#installing)
4. Add the 2 lines in Patch section of the config file (see details below)
4. Add the 2 lines in `[Patch]` section of the config file (see details below)
5. Run:
```
spicetify config extensions dribbblish-dynamic.js
@ -62,13 +65,20 @@ xpui.js_repl_8008 = ,${1}58,
## Hide Window Controls
Windows user, please edit your Spotify shortcut and add flag `--transparent-window-controls` after the Spotify.exe:
To edit an taskbar shortcut, right click it, then right click Spotify in the list again.
To edit a taskbar shortcut, right click it, then right click Spotify in the list again.
<img src="showcase-images/windows-shortcut-instruction.png" alt="img">
<img src="https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/main/showcase-images/windows-shortcut-instruction.png" alt="img">
In addition to `--transparent-window-controls` you can set `Windows Top Bars` to `Solid` or `Transparent` to look like this:
<img src="showcase-images/top-bars.png" alt="img" width="500px">
<img src="https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/main/showcase-images/top-bars.png" alt="img" width="500px">
## Follow system dark/light theme (Powershell)
Automatic dark mode should work on MacOs and Linux out of the box.
From Spotify > v1.1.70, dark mode is forced in Windows builds. You will need to patch Spotify.exe using this script:
```powershell
Invoke-WebRequest -UseBasicParsing "https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/master/patch-dark-mode.ps1" | Invoke-Expression
```
## Uninstall
### Windows (PowerShell)
@ -85,6 +95,6 @@ curl -fsSL https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-the
1. Remove Patch lines you added in config file earlier.
2. Run:
```
spicetify config extensions dribbblish-dynamic.js-
spicetify config current_theme " " extensions dribbblish-dynamic.js-
spicetify apply
```

View file

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

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

39
patch-dark-mode.ps1 Normal file
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"
}

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,15 +1,15 @@
import $ from "jquery";
import MarkdownIt from "markdown-it";
import MarkdownItAttrs from "markdown-it-attrs";
import MarkdownItBracketedSpans from "markdown-it-bracketed-spans";
import svgUndo from "svg/undo";
import { renderMD, defaults } from "./Util";
import { icons } from "./Icons";
export default class ConfigMenu {
/** @typedef {"checkbox" | "select" | "button" | "slider" | "number" | "text" | "textarea" | "time" | "color"} DribbblishConfigType */
/**
* @typedef {Object} DribbblishConfigItem
* @property {"checkbox" | "select" | "button" | "slider" | "number" | "text" | "textarea" | "time" | "color"} type
* @property {String|DribbblishConfigArea} [area={name: "Main Settings", order: 0}]
* @property {DribbblishConfigType} type
* @property {String | DribbblishConfigArea} [area={name: "Main Settings", order: 0}]
* @property {any} [data={}]
* @property {Number} [order=0] order < 0 = Higher up | order > 0 = Lower Down
* @property {String} key
@ -26,7 +26,7 @@ export default class ConfigMenu {
* @property {onAppended} [onAppended]
* @property {onChange} [onChange]
* @property {DribbblishConfigItem[]} [children=[]]
* @property {String} [childOf=null] key of parent (set automatically)
* @property {String} [parent=null] key of parent (set automatically)
*/
/**
@ -69,30 +69,18 @@ export default class ConfigMenu {
/** @type {Spicetify.Menu.Item} */
#configButton;
/** @type {MarkdownIt} */
#md;
constructor() {
this.#config = {};
this.#configButton = new Spicetify.Menu.Item("Dribbblish Settings", false, () => this.open());
this.#configButton.register();
this.#md = MarkdownIt({
html: true,
breaks: true,
linkify: true,
typographer: true
});
this.#md.use(MarkdownItBracketedSpans);
this.#md.use(MarkdownItAttrs);
const container = document.createElement("div");
container.id = "dribbblish-config";
container.innerHTML = /* html */ `
<div class="dribbblish-config-container">
<button aria-label="Close" class="dribbblish-config-close main-trackCreditsModal-closeBtn">
<svg width="18" height="18" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><path d="M31.098 29.794L16.955 15.65 31.097 1.51 29.683.093 15.54 14.237 1.4.094-.016 1.508 14.126 15.65-.016 29.795l1.414 1.414L15.54 17.065l14.144 14.143" fill="currentColor" fill-rule="evenodd"></path></svg>
</button>
<button aria-label="Close" class="dribbblish-config-close main-trackCreditsModal-closeBtn">${icons.get("close", { size: 24 })}</button>
<h1>Dribbblish Settings</h1>
<input type="search" placeholder="Search" class="dribbblish-config-search">
<div class="dribbblish-config-areas"></div>
</div>
<div class="dribbblish-config-backdrop"></div>
@ -101,6 +89,7 @@ export default class ConfigMenu {
document.body.appendChild(container);
$(".dribbblish-config-close").on("click", () => this.close());
$(".dribbblish-config-backdrop").on("click", () => this.close());
$(".dribbblish-config-search").on("input", (e) => this.#search(e.target.value));
}
open() {
@ -111,67 +100,6 @@ export default class ConfigMenu {
$("#dribbblish-config").removeAttr("active");
}
/**
* @private
* @param {DribbblishConfigItem} options
*/
#addInputHTML(options) {
this.registerArea(options.area);
const parent = document.querySelector(`.dribbblish-config-area[name="${options.area.name}"] .dribbblish-config-area-items`);
const elem = document.createElement("div");
if (options.order != 0) elem.style.order = options.order;
elem.classList.add("dribbblish-config-item");
elem.setAttribute("key", options.key);
elem.setAttribute("type", options.type);
if (options.hidden) elem.setAttribute("hidden", true);
if (options.childOf) elem.setAttribute("parent", options.childOf);
if (options.children.length > 0) elem.setAttribute("children", options.children.map((c) => c.key).join(" "));
elem.innerHTML = /* html */ `
${
options.name != null && options.description != null
? /* html */ `
<div class="dribbblish-config-item-header">
<h2 class="x-settings-title main-type-cello" as="h2" empty="${options.name == null}">
${options.name}
${options.resetButton ? /* html */ `<button aria-label="Reset" class="dribbblish-config-item-reset main-trackCreditsModal-closeBtn">${svgUndo}</button>` : ""}
</h2>
<label class="main-type-mesto" empty="${options.description == null}" markdown>${this.#md.render(options.description)}</label>
</div>
`
: ""
}
<div class="dribbblish-config-item-input">
<label class="x-toggle-wrapper x-settings-secondColumn">
${options.input}
</label>
</div>
`;
if (options.insertOnTop && parent.children.length > 0) {
parent.insertBefore(elem, parent.children[0]);
} else {
parent.appendChild(elem);
}
const $inputElem = $(elem).find("input, textarea, select");
const $resetButton = $(elem).find(".dribbblish-config-item-reset");
if ($resetButton.length > 0) {
$resetButton.on("click", () => {
this.reset(options.key);
const defaultVal = this.get(options.key);
if (options.type == "checkbox") {
$inputElem.prop("checked", defaultVal);
} else {
$inputElem.prop("value", defaultVal);
if (options.type == "slider") $inputElem.attr("tooltip", defaultVal);
}
options.onChange(defaultVal);
});
}
}
/**
* @param {DribbblishConfigItem} options
*/
@ -194,10 +122,10 @@ export default class ConfigMenu {
onAppended: () => {},
onChange: () => {},
children: [],
childOf: null
parent: null
};
// Set Defaults
options = { ...defaultOptions, ...options };
options = defaults(options, defaultOptions);
if (typeof options.area == "string") options.area = { name: options.area, order: 0 };
options.description = options.description
.split("\n")
@ -206,17 +134,17 @@ export default class ConfigMenu {
.join("\n");
options._onChange = options.onChange;
options.onChange = (val) => {
const isValid = validate(val);
$(`.dribbblish-config-item[key="${options.key}"]`).attr("changed", isValid === true && val != options.defaultValue ? "" : null);
const isValid = validate(val) === true;
$(`.dribbblish-config-item[key="${options.key}"]`).attr("changed", options.type != "button" && isValid && val != options.defaultValue ? "" : null);
if (!isValid) return;
this.set(options.key, val, options.save);
options._onChange.call(options, val);
const show = options.showChildren.call(options, val);
options.children.forEach((child) => this.#setHidden(child.key, Array.isArray(show) ? !show.includes(child.key) : !show));
options.children.forEach((child) => this.setHidden(child.key, Array.isArray(show) ? !show.includes(child.key) : !show));
};
options.children = options.children.map((child) => {
return { ...child, area: options.area, childOf: options.key };
return { ...child, area: options.area, parent: options.key, order: options.order ?? 0 + child.order ?? 0 };
});
this.#config[options.key] = options;
@ -233,33 +161,33 @@ export default class ConfigMenu {
return isValid;
}
let elem;
let input;
if (options.type == "checkbox") {
const input = /* html */ `
<input id="dribbblish-config-input-${options.key}" class="x-toggle-input" type="checkbox"${this.get(options.key) ? " checked" : ""}>
<span class="x-toggle-indicatorWrapper">
<span class="x-toggle-indicator"></span>
</span>
`;
this.#addInputHTML({ ...options, input });
elem = document.createDocumentFragment();
$(`#dribbblish-config-input-${options.key}`).on("change", (e) => {
input = document.createElement("input");
input.classList.add("dribbblish-config-input");
input.type = "checkbox";
input.classList.add("x-toggle-input");
input.checked = this.get(options.key);
input.addEventListener("change", (e) => {
options.onChange(e.target.checked);
});
elem.appendChild(input);
const indicator = document.createElement("span");
indicator.classList.add("x-toggle-indicatorWrapper");
indicator.innerHTML = /* html */ `<span class="x-toggle-indicator"></span>`;
elem.appendChild(indicator);
} else if (options.type == "select") {
// Validate
const val = this.get(options.key);
if (!Object.keys(options.data).includes(val)) this.reset(options.key);
const input = /* html */ `
<select class="main-dropDown-dropDown" id="dribbblish-config-input-${options.key}">
${Object.entries(options.data)
.map(([key, name]) => `<option value="${key}"${this.get(options.key) == key ? " selected" : ""}>${name}</option>`)
.join("")}
</select>
`;
this.#addInputHTML({ ...options, input });
$(`#dribbblish-config-input-${options.key}`).on("change", (e) => {
input = document.createElement("select");
input.classList.add("dribbblish-config-input");
input.classList.add("main-dropDown-dropDown");
input.innerHTML = Object.entries(options.data)
.map(([key, name]) => `<option value="${key}"${this.get(options.key) == key ? " selected" : ""}>${name}</option>`)
.join("");
input.addEventListener("change", (e) => {
options.onChange(e.target.value);
});
} else if (options.type == "button") {
@ -268,16 +196,12 @@ export default class ConfigMenu {
options.resetButton = false;
options.save = false;
const input = /* html */ `
<button class="main-buttons-button main-button-primary" type="button" id="dribbblish-config-input-${options.key}">
<div class="x-settings-buttonContainer">
<span>${options.data}</span>
</div>
</button>
`;
this.#addInputHTML({ ...options, input });
$(`#dribbblish-config-input-${options.key}`).on("click", (e) => {
input = document.createElement("button");
input.classList.add("dribbblish-config-input");
input.type = "button";
input.classList.add("main-buttons-button", "main-button-primary");
input.innerHTML = /* html */ `<div class="x-settings-buttonContainer"><span>${options.data}</span></div>`;
input.addEventListener("click", (e) => {
options.onChange(true);
});
} else if (options.type == "number") {
@ -287,24 +211,18 @@ export default class ConfigMenu {
if (options.data.min != null && _val < options.data.min) this.set(options.key, options.data.min, options.save);
if (options.data.max != null && _val > options.data.max) this.set(options.key, options.data.max, options.save);
const input = /* html */ `
<input
type="number"
id="dribbblish-config-input-${options.key}"
${options.data.min != null ? `min="${options.data.min}"` : ""}
${options.data.max != null ? `max="${options.data.max}"` : ""}
step="${options.data.step ?? 1}"
value="${this.get(options.key)}"
>
`;
this.#addInputHTML({ ...options, input });
// Prevent inputting +, - and e. Why is it even possible in the first place?
$(`#dribbblish-config-input-${options.key}`).on("keypress", (e) => {
if (["+", "-", "e"].includes(e.key)) e.preventDefault();
input = document.createElement("input");
input.classList.add("dribbblish-config-input");
input.type = "number";
input.value = this.get(options.key);
input.step = options.data.step ?? 1;
if (options.data.min != null) input.min = options.data.min;
if (options.data.max != null) input.max = options.data.max;
input.addEventListener("keypress", (e) => {
// Prevent inputting + and e.
if (["+", "e"].includes(e.key)) e.preventDefault();
});
$(`#dribbblish-config-input-${options.key}`).on("input", (e) => {
input.addEventListener("input", (e) => {
if (options.data.min != null && e.target.value < options.data.min) e.target.value = options.data.min;
if (options.data.max != null && e.target.value > options.data.max) e.target.value = options.data.max;
@ -313,26 +231,20 @@ export default class ConfigMenu {
} else if (options.type == "text") {
if (options.defaultValue == null) options.defaultValue = "";
const input = /* html */ `
<input type="text" id="dribbblish-config-input-${options.key}" value="${this.get(options.key)}">
`;
this.#addInputHTML({ ...options, input });
$(`#dribbblish-config-input-${options.key}`).on("input", (e) => {
const val = e.target.value;
if (!validate(val)) return;
this.set(options.key, val, options.save);
options.onChange(val);
input = document.createElement("input");
input.classList.add("dribbblish-config-input");
input.type = "text";
input.value = this.get(options.key);
input.addEventListener("input", (e) => {
options.onChange(e.target.value);
});
} else if (options.type == "textarea") {
if (options.defaultValue == null) options.defaultValue = "";
const input = /* html */ `
<textarea id="dribbblish-config-input-${options.key}">${this.get(options.key)}</textarea>
`;
this.#addInputHTML({ ...options, input });
$(`#dribbblish-config-input-${options.key}`).on("input", (e) => {
input = document.createElement("textarea");
input.classList.add("dribbblish-config-input");
input.value = this.get(options.key);
input.addEventListener("input", (e) => {
options.onChange(e.target.value);
});
} else if (options.type == "slider") {
@ -342,54 +254,47 @@ export default class ConfigMenu {
if (options.data.min != null && val < options.data.min) this.set(options.key, options.data.min, options.save);
if (options.data.max != null && val > options.data.max) this.set(options.key, options.data.max, options.save);
const input = /* html */ `
<input
type="range"
id="dribbblish-config-input-${options.key}"
name="${options.name}"
min="${options.data?.min ?? "0"}"
max="${options.data?.max ?? "100"}"
step="${options.data?.step ?? "1"}"
value="${this.get(options.key)}"
tooltip="${this.get(options.key)}${options.data?.suffix ?? ""}"
>
`;
this.#addInputHTML({ ...options, input });
$(`#dribbblish-config-input-${options.key}`).on("input", (e) => {
$(`#dribbblish-config-input-${options.key}`).attr("tooltip", `${e.target.value}${options.data?.suffix ?? ""}`);
$(`#dribbblish-config-input-${options.key}`).attr("value", e.target.value);
input = document.createElement("input");
input.classList.add("dribbblish-config-input");
input.type = "range";
input.step = options.data.step ?? 1;
input.min = options.data.min ?? 0;
input.max = options.data.max ?? 100;
input.value = this.get(options.key);
input.setAttribute("tooltip", `${this.get(options.key)}${options.data.suffix ?? ""}`);
input.addEventListener("input", (e) => {
options.onChange(Number(e.target.value));
$(`#dribbblish-config-input-${options.key}`).attr("tooltip", `${e.target.value}${options.data?.suffix ?? ""}`);
});
} else if (options.type == "time") {
// Validate
if (options.defaultValue == null) options.defaultValue = "00:00";
const input = /* html */ `
<input type="time" id="dribbblish-config-input-${options.key}" name="${options.name}" value="${this.get(options.key)}">
`;
this.#addInputHTML({ ...options, input });
$(`#dribbblish-config-input-${options.key}`).on("input", (e) => {
$(`#dribbblish-config-input-${options.key}`).attr("value", e.target.value);
input = document.createElement("input");
input.classList.add("dribbblish-config-input");
input.type = "time";
input.value = this.get(options.key);
input.addEventListener("input", (e) => {
options.onChange(e.target.value);
});
} else if (options.type == "color") {
// Validate
if (options.defaultValue == null) options.defaultValue = "#000000";
const input = /* html */ `
<input type="color" id="dribbblish-config-input-${options.key}" name="${options.name}" value="${this.get(options.key)}">
`;
this.#addInputHTML({ ...options, input });
$(`#dribbblish-config-input-${options.key}`).on("input", (e) => {
input = document.createElement("input");
input.classList.add("dribbblish-config-input");
input.type = "color";
input.value = this.get(options.key);
input.addEventListener("input", (e) => {
options.onChange(e.target.value);
});
} else {
throw new Error(`Config Type "${options.type}" invalid`);
}
this.#addInputHTML({ ...options, input: elem ?? input });
options.input = input;
// Re-write internal config since some values may have changed
this.#config[options.key] = options;
@ -414,33 +319,9 @@ export default class ConfigMenu {
area = { ...defaultOptions, ...area };
if (!document.querySelector(`.dribbblish-config-area[name="${area.name}"]`)) {
const areaElem = document.createElement("div");
areaElem.classList.add("dribbblish-config-area");
if (area.order != 0) areaElem.style.order = area.order;
const uncollapsedAreas = JSON.parse(localStorage.getItem("dribbblish:config-areas:uncollapsed") ?? "[]");
if (area.toggleable && !uncollapsedAreas.includes(area.name)) areaElem.toggleAttribute("collapsed");
areaElem.setAttribute("name", area.name);
areaElem.innerHTML = /* html */ `
<h2 class="dribbblish-config-area-header">
${area.name}
${!area.toggleable ? "" : /* html */ `<svg height="24" width="24" viewBox="0 0 24 24" class="main-topBar-icon"><polyline points="16 4 7 12 16 20" fill="none" stroke="currentColor"></polyline></svg>`}
</h2>
<div class="dribbblish-config-area-items"></div>
`;
document.querySelector(".dribbblish-config-areas").appendChild(areaElem);
if (area.toggleable) {
areaElem.querySelector("h2").addEventListener("click", () => {
areaElem.toggleAttribute("collapsed");
let uncollapsedAreas = JSON.parse(localStorage.getItem("dribbblish:config-areas:uncollapsed") ?? "[]");
if (areaElem.hasAttribute("collapsed")) {
uncollapsedAreas = uncollapsedAreas.filter((areaName) => areaName != area.name);
} else {
uncollapsedAreas.push(area.name);
}
localStorage.setItem("dribbblish:config-areas:uncollapsed", JSON.stringify(uncollapsedAreas));
});
}
this.#addAreaHTML(area);
} else {
throw new Error(`Area "${area.name}" already exists`);
}
}
@ -451,10 +332,10 @@ export default class ConfigMenu {
* @returns {any}
*/
get(key, defaultValueOverride) {
const val = JSON.parse(this.#config[key]?.storageCache ?? localStorage.getItem(`dribbblish:config:${key}`) ?? null); // Turn undefined into null because `JSON.parse()` dosen't like undefined
const val = JSON.parse(this.#config[key]?.storageCache ?? localStorage.getItem(`dribbblish:config:${key}`) ?? null); // Turn undefined into null because `JSON.parse()` doesn't like undefined
if (val == null || val?.type != this.#config[key]?.type) {
localStorage.removeItem(`dribbblish:config:${key}`);
return defaultValueOverride ?? this.#config[key].defaultValue;
return defaultValueOverride ?? this.#config[key]?.defaultValue;
}
return val.value;
}
@ -478,6 +359,17 @@ export default class ConfigMenu {
reset(key) {
delete this.#config[key].storageCache;
localStorage.removeItem(`dribbblish:config:${key}`);
const options = this.#config[key];
const defaultVal = this.get(options.key);
if (options.type == "checkbox") {
options.input.checked = defaultVal;
} else {
options.input.value = defaultVal;
if (options.type == "slider") options.input.setAttribute("tooltip", `${defaultVal}${options.data.suffix ?? ""}`);
}
options.onChange(defaultVal);
}
/**
@ -486,10 +378,10 @@ export default class ConfigMenu {
* @param {Boolean} hidden
* @private
*/
#setHidden(key, hidden) {
setHidden(key, hidden, search = false) {
this.#config[key].hidden = hidden;
const $elem = $(`.dribbblish-config-item[key="${key}"]`);
$elem.attr("hidden", hidden ? "" : null);
$elem.attr(search ? "hidden-override" : "hidden", hidden ? "" : null);
// If element has children or a parent
if ($elem.attr("parent") != null || $elem.attr("children") != null) {
@ -498,7 +390,7 @@ export default class ConfigMenu {
const $nextChildren = $parent.nextAll(`[parent="${$parent.attr("key")}"]`);
// Make parent connect on bottom when children are visible
$parent.attr("connect-bottom", $nextChildren.filter(":not([hidden])").length > 0 ? "" : null);
$parent.attr("connect-bottom", $nextChildren.filter(":not([hidden]):not([hidden-override])").length > 0 ? "" : null);
// Reset all children's bottom connection
$nextChildren.each(function () {
@ -506,7 +398,7 @@ export default class ConfigMenu {
});
// Add bottom connection to all but the last visible child
$nextChildren
.filter(":not([hidden])")
.filter(":not([hidden]):not([hidden-override])")
.slice(0, -1)
.each(function () {
$(this).attr("connect-bottom", "");
@ -516,6 +408,10 @@ export default class ConfigMenu {
}
}
setDisabled(key, disabled) {
this.#config[key].input.disabled = disabled;
}
getOptions(key) {
return this.#config[key];
}
@ -530,4 +426,136 @@ export default class ConfigMenu {
return obj;
}
/**
* @param {String} text
*/
#search(text) {
text = text.trim().toLowerCase();
$(".dribbblish-config-area-header svg").css("display", text != "" ? "none" : "");
$(".dribbblish-config-area").attr("search", text != "" ? "" : null);
const cmp = (s) => s.trim().toLowerCase().includes(text.trim().toLowerCase());
/**
* @param {DribbblishConfigItem} options
*/
function cmpOpts(options) {
let matches = cmp(options.name) || cmp($(`.dribbblish-config-item[key="${options.key}"] .dribbblish-config-item-header label`).text());
if (!matches && options.type == "select") matches = Object.values(options.data).some(cmp);
return matches;
}
// Check every item
for (const [key, options] of Object.entries(this.#config)) {
this.setHidden(key, false, true);
if (text == "") continue;
const show = cmpOpts(options);
this.setHidden(key, !show, true);
}
// Check every item's children and show it when any child is visible
for (const [key, options] of Object.entries(this.#config)) {
if (options.children.length != 0 && $(`.dribbblish-config-item[parent="${options.key}"]`).filter(":not([hidden]):not([hidden-override])").length != 0) this.setHidden(key, false, true);
}
// Hide areas without visible children
for (const area of $(".dribbblish-config-area").toArray()) {
const $area = $(area);
const itemsVisible = $area.children(".dribbblish-config-area-items").children(":not([hidden]):not([hidden-override])").length;
$area.css("display", text != "" && itemsVisible == 0 ? "none" : "");
}
$(`.dribbblish-config-item`).filter(":not([hidden]):not([hidden-override])").length;
}
/**
* @private
* @param {DribbblishConfigItem} options
*/
#addInputHTML(options) {
if (!document.querySelector(`.dribbblish-config-area[name="${options.area.name}"]`)) this.registerArea(options.area);
const parent = document.querySelector(`.dribbblish-config-area[name="${options.area.name}"] .dribbblish-config-area-items`);
const elem = document.createElement("div");
if (options.order != 0) elem.style.order = options.order;
elem.classList.add("dribbblish-config-item");
elem.setAttribute("key", options.key);
elem.setAttribute("type", options.type);
if (options.hidden) elem.setAttribute("hidden", true);
if (options.parent) elem.setAttribute("parent", options.parent);
if (options.children.length > 0) elem.setAttribute("children", options.children.map((c) => c.key).join(" "));
elem.innerHTML = /* html */ `
${
options.name != null && options.description != null
? /* html */ `
<div class="dribbblish-config-item-header">
<h2 class="x-settings-title main-type-cello" as="h2" empty="${options.name == null}">${options.name}</h2>
<label class="main-type-mesto" empty="${options.description == null}" markdown>${renderMD(options.description)}</label>
</div>
`
: ""
}
<div class="dribbblish-config-item-input">
<label class="x-toggle-wrapper x-settings-secondColumn"></label>
</div>
`;
elem.querySelector(".dribbblish-config-item-input > label").appendChild(options.input);
if (options.resetButton && options.name != null && options.description != null) {
const resetBtn = document.createElement("button");
resetBtn.ariaLabel = "Reset";
resetBtn.className = "dribbblish-config-item-reset main-trackCreditsModal-closeBtn";
resetBtn.innerHTML = icons.get("delete-outline", { size: 20, title: "Reset Setting" });
resetBtn.addEventListener("click", () => {
this.reset(options.key);
});
elem.querySelector(".dribbblish-config-item-header > h2").appendChild(resetBtn);
}
if (options.insertOnTop && parent.children.length > 0) {
parent.insertBefore(elem, parent.children[0]);
} else {
parent.appendChild(elem);
}
}
/**
*
* @param {DribbblishConfigArea} area
*/
#addAreaHTML(area) {
const areaElem = document.createElement("div");
areaElem.classList.add("dribbblish-config-area");
if (area.order != 0) areaElem.style.order = area.order;
const uncollapsedAreas = JSON.parse(localStorage.getItem("dribbblish:config-areas:uncollapsed") ?? "[]");
if (area.toggleable && !uncollapsedAreas.includes(area.name)) areaElem.toggleAttribute("collapsed");
areaElem.setAttribute("name", area.name);
areaElem.innerHTML = /* html */ `
<h2 class="dribbblish-config-area-header">
${area.name}
${!area.toggleable ? "" : icons.get("expand-more", { size: 24, scale: 1.2 })}
</h2>
<div class="dribbblish-config-area-items"></div>
`;
document.querySelector(".dribbblish-config-areas").appendChild(areaElem);
if (area.toggleable) {
areaElem.querySelector("h2").addEventListener("click", () => {
if (document.querySelector(".dribbblish-config-search").value.trim() != "") return;
areaElem.toggleAttribute("collapsed");
let uncollapsedAreas = JSON.parse(localStorage.getItem("dribbblish:config-areas:uncollapsed") ?? "[]");
if (areaElem.hasAttribute("collapsed")) {
uncollapsedAreas = uncollapsedAreas.filter((areaName) => areaName != area.name);
} else {
uncollapsedAreas.push(area.name);
}
localStorage.setItem("dribbblish:config-areas:uncollapsed", JSON.stringify(uncollapsedAreas));
});
}
}
}

80
src/js/Dribbblish.js Normal file
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();

View file

@ -1,4 +1,5 @@
import $ from "jquery";
import { icons } from "./Icons";
import { waitForElement } from "./Util";
@ -7,7 +8,7 @@ export default class Info {
* @typedef {Object} DribbblishInfo
* @property {String} [text]
* @property {String} [tooltip]
* @property {String} [icon]
* @property {String} [icon] svg string or icon name
* @property {DribbblishInfoColor} [color]
* @property {Number} [order=0] order < 0 = More to the Left | order > 0 = More to the Right
* @property {onClick} [onClick]
@ -43,6 +44,10 @@ export default class Info {
});
}
isReady() {
return this.#ready;
}
/**
* @param {String} key
* @param {DribbblishInfo} info
@ -54,6 +59,7 @@ export default class Info {
}
this.remove(key);
if (info == null) return;
if (info.text == null && info.icon == null) throw new Error("invalid info");
const elem = document.createElement("div");
@ -69,6 +75,7 @@ export default class Info {
if (bg != null) elem.style.backgroundColor = bg;
}
if (info.order != 0) elem.style.order = info.order;
if (!info.icon.startsWith("<svg")) info.icon = icons.get(info.icon, { size: 18 });
elem.innerHTML = `${info.text ?? ""}${info.icon ?? ""}`;
this.#container.appendChild(elem);

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

View file

@ -1,3 +1,8 @@
import MarkdownIt from "markdown-it";
import MarkdownItAttrs from "markdown-it-attrs";
import defaultsDeep from "lodash.defaultsdeep";
import { default as _debounce } from "lodash.debounce";
/**
* @callback waitForElCb
* @param {HTMLElement[]} queries
@ -37,3 +42,54 @@ export function capitalizeFirstLetter(string) {
export function getClosestToNum(arr, num) {
return arr.reduce((prev, curr) => (Math.abs(curr - num) < Math.abs(prev - num) ? curr : prev));
}
export function renderMD(src, env) {
const md = MarkdownIt("commonmark", {
html: true,
breaks: true,
linkify: true,
typographer: true
});
md.use(MarkdownItAttrs);
return md.render(src, env);
}
export function htmlToNode(htmlStr) {
var div = document.createElement("div");
div.innerHTML = htmlStr.trim();
return div.firstChild;
}
export function getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min;
}
export function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
/**
* @template T
* @param {T[]} arr
* @returns {T}
*/
export function randomFromArray(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}
/** @type {_debounce} */
export function debounce(fn, wait, opts) {
return _debounce(fn, wait, opts);
}
/**
* @param {Object} options
* @param {...Object} defaults
* @returns {Object}
*/
export function defaults(options, ...defaults) {
return defaultsDeep(options, ...defaults);
}

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,7 @@ $colors: (
subtext: #f0f0f0,
sidebar-text: #ffffff,
main: #000000,
sidebar: #1ed760,
sidebar: #121212,
player: #000000,
card: #000000,
shadow: #202020,
@ -44,15 +44,3 @@ $props-to-transition: ("sidebar", "main", "text", "button");
transition: all var(--song-transition-speed) linear;
transition-property: $props;
}
// Color Function
// $light-offset is added when in light mode
@function spiceColor($key, $alpha: 1, $light-offset: 0) {
@if $alpha == 1 {
@return var(--spice-#{$key});
} @else if $light-offset == 0 {
@return rgba(var(--spice-rgb-#{$key}), $alpha);
} @else {
@return rgba(var(--spice-rgb-#{$key}), calc($alpha + var(--is_light) * $light-offset));
}
}

View file

@ -23,21 +23,28 @@
z-index: 1;
position: relative;
width: clamp(500px, 50%, 650px);
background-color: spiceColor("main", 0.95);
backdrop-filter: blur(3px);
padding: 20px 15px;
border-radius: var(--main-corner-radius);
box-shadow: 0 0 10px 3px #0000003b;
display: flex;
gap: 5px;
gap: 8px;
flex-direction: column;
align-items: center;
justify-content: center;
@include spiceGlass();
> h2 {
font-size: 32px;
line-height: 32px;
}
.dribbblish-config-close {
position: absolute;
top: 15px;
right: 15px;
padding: 0px;
top: 24px;
right: 24px;
}
.dribbblish-config-search {
// TODO: improve styles
}
.dribbblish-config-areas {
@ -47,7 +54,7 @@
gap: 8px;
max-height: 60vh;
overflow-y: auto;
padding: 0px 25px;
padding: 0px 26px;
.dribbblish-config-area {
display: flex;
@ -55,13 +62,23 @@
align-items: center;
gap: 8px;
&[collapsed] {
&[collapsed]:not([search]) {
overflow: hidden;
min-height: 38px; //for some reason height alone isn't enough
height: 38px;
.dribbblish-config-area-header svg {
transform: rotate(270deg);
.dribbblish-config-area-header {
svg {
transform: rotate(0deg);
}
&:hover svg {
transform: rotate(0deg) scale(1.1);
}
&:active svg {
transform: rotate(0deg) scale(0.9);
}
}
}
@ -84,7 +101,15 @@
padding: 0px;
height: 100%;
stroke-width: 2px;
transform: rotate(90deg);
transform: rotate(180deg);
}
&:hover svg {
transform: rotate(180deg) scale(1.1);
}
&:active svg {
transform: rotate(180deg) scale(0.9);
}
}
@ -103,8 +128,10 @@
align-items: center;
gap: 10px;
padding: 8px 16px;
user-select: text;
&[hidden] {
&[hidden],
&[hidden-override] {
display: none;
}
@ -128,8 +155,16 @@
border-bottom-right-radius: 0px;
}
&[invalid]::before {
border: 2px solid rgba(red, 0.8);
&[invalid] {
&::before {
border: 2px solid rgba(red, 0.8);
}
.dribbblish-config-item-input::before {
content: var(--validation-error);
margin-right: 8px;
color: rgba(red, 0.8);
}
}
&[changed] {
@ -154,6 +189,10 @@
bottom: -4px;
border-bottom-left-radius: 0px;
}
.dribbblish-config-item-reset {
display: block !important;
}
}
.dribbblish-config-item-header {
@ -169,7 +208,7 @@
.x-settings-title {
display: flex;
gap: 10px;
gap: 5px;
align-items: center;
grid-area: header;
margin: 0px;
@ -178,10 +217,11 @@
bottom: 0px;
.dribbblish-config-item-reset {
display: none;
width: 20px;
height: 20px;
padding: 0px;
color: spiceColor("text");
width: 1em;
height: 1em;
}
}
@ -190,7 +230,6 @@
height: min-content;
color: spiceColor("subtext");
line-height: calc(1em + 6px); // To have line gaps
line-break: anywhere;
}
.x-settings-secondColumn {
@ -200,13 +239,8 @@
}
.dribbblish-config-item-input {
display: flex;
min-width: fit-content;
&::before {
content: var(--validation-error);
margin-right: 8px;
color: rgba(red, 0.8);
}
}
}
}

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

@ -1,7 +1,7 @@
.main-contextMenu-menu {
background-color: transparent !important;
overflow: visible;
position: relative;
overflow: visible;
background-color: transparent !important;
border-radius: var(--main-corner-radius);
padding: 8px;
@ -10,9 +10,7 @@
content: "";
position: absolute;
inset: 0px;
background-color: spiceColor("main", calc(0.75 - 0.1 * var(--is_light))) !important;
backdrop-filter: blur(10px);
border-radius: var(--main-corner-radius);
@include spiceGlass(lightOffset(0.8, -0.1));
}
.main-contextMenu-menuItem {
@ -21,16 +19,16 @@
color: spiceColor("subtext") !important;
&:hover,
&:focus,
&:active,
&[aria-expanded="true"] {
background-color: spiceColor("subtext", calc(0.1 + 0.3 * var(--is_light)));
background-color: spiceColor("subtext", 0.1, 0.3);
}
&::before,
&::after {
left: 5px;
right: 5px;
left: 8px;
right: 8px;
border-color: spiceColor("subtext", 0.1, 0.1);
}
}
}

View file

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

View file

@ -20,7 +20,7 @@ $font-weights: (
font-family: $font;
font-weight: $weight;
font-style: normal;
src: url("glue-resources/fonts/#{$font}-#{$style}.ttf") format("truetype");
src: url(font64("#{$font}-#{$style}.ttf")) format("truetype");
}
}
}
@ -49,7 +49,7 @@ $font-weights: (
}
// set font variables
body {
:root {
--glue-font-family: #{map-get($font-names, "glue")}, #{map-get($font-names, "info")}, spotify-circular, spotify-circular-cyrillic, spotify-circular-arabic, spotify-circular-hebrew, Helvetica Neue, helvetica, arial, Hiragino Kaku Gothic Pro, Meiryo, MS Gothic, sans-serif;
--info-font-family: #{map-get($font-names, "info")}, spotify-circular, spotify-circular-cyrillic, spotify-circular-arabic, spotify-circular-hebrew, Helvetica Neue, helvetica, arial, Hiragino Kaku Gothic Pro, Meiryo, MS Gothic, sans-serif;
font-family: var(--glue-font-family);

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

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

View file

@ -1,7 +1,6 @@
#dribbblish-info-container {
display: flex;
gap: 8px;
margin-right: 8px;
height: 100%;
&:empty {
@ -16,7 +15,7 @@
height: 100%;
padding: 0px 8px;
color: spiceColor("sidebar-text");
background-color: spiceColor("button");
background-color: spiceColor("sidebar");
border-radius: var(--sidebar-icons-border-radius);
line-height: 13px;
@include spiceFont("glue", 14px, "Medium");
@ -29,10 +28,5 @@
padding: 0px;
aspect-ratio: 1/1;
}
& svg {
width: 1em;
height: 1em;
}
}
}

View file

@ -1,24 +1,49 @@
button.main-button-primary {
background-color: spiceColor("selected-row", 0.4) !important;
.main-button {
&-primary {
$color: spiceColor("subtext");
color: $color;
background-color: spiceColor("selected-row", 0.4) !important;
&:hover,
&:active {
background-color: spiceColor("selected-row", 0.6) !important;
&:hover,
&:active {
background-color: spiceColor("selected-row", 0.6) !important;
}
span {
color: $color !important;
}
}
span {
color: spiceColor("subtext") !important;
}
}
// ? Don't know if this exists
// &-secondary {
// $color: spiceColor("subtext");
// color: $color;
// background-color: spiceColor("selected-row", 0.4) !important;
// Modals
.GenericModal button.main-button-primary {
background-color: spiceColor("subtext", 0.6) !important;
color: spiceColor("main") !important;
// &:hover,
// &:active {
// background-color: spiceColor("selected-row", 0.6) !important;
// }
&:hover,
&:active {
background-color: spiceColor("subtext") !important;
// span {
// color: $color !important;
// }
// }
// ? the `:not(...)` is to fix #137
&-tertiary:not(.main-entityHeader-titleButton) {
$color: spiceColor("subtext");
color: $color;
background-color: spiceColor("selected-row", 0.2, 0.05) !important;
&:hover,
&:active {
background-color: spiceColor("selected-row", 0.6) !important;
}
span {
color: $color !important;
}
}
}
@ -26,7 +51,7 @@ button.main-button-primary {
.x-toggle-indicatorWrapper {
background-color: spiceColor("subtext", 0.1);
input:hover ~ & {
input:not(:disabled):hover ~ & {
background-color: spiceColor("subtext", 0.15) !important;
}
@ -34,7 +59,7 @@ button.main-button-primary {
background-color: spiceColor("selected-row", 0.4) !important;
}
input:hover:checked ~ & {
input:not(:disabled):hover:checked ~ & {
background-color: spiceColor("selected-row", 0.6) !important;
}
}
@ -48,9 +73,21 @@ input {
outline: none;
border: none;
&:hover,
&:active {
background-color: spiceColor("selected-row", 0.6) !important;
&:not(:disabled) {
&:hover,
&:active {
background-color: spiceColor("selected-row", 0.6) !important;
}
}
&:disabled {
cursor: not-allowed;
opacity: 0.5 !important;
}
&::placeholder {
color: spiceColor("subtext");
opacity: lightOffset(0.8, 0.1);
}
}
@ -58,23 +95,17 @@ textarea {
padding: 6px 10px;
}
select {
background-color: spiceColor("selected-row", 0.4) !important;
color: spiceColor("subtext");
&:hover,
&:active {
background-color: spiceColor("selected-row", 0.6) !important;
}
option {
background: spiceColor("main") !important;
}
select > option {
background: spiceColor("main") !important;
}
input {
padding: 6px 10px 6px 48px;
&[type="checkbox"] {
opacity: 0 !important;
}
&[type="range"] {
-webkit-appearance: none;
background: transparent;
@ -128,6 +159,7 @@ input {
&[type="number"],
&[type="text"],
&[type="search"],
&[type="time"] {
height: 32px;
border-radius: 4px !important;
@ -136,26 +168,12 @@ input {
&[type="time"] {
&::-webkit-calendar-picker-indicator {
filter: invert(calc(1 - var(--is_light)));
filter: invert(var(--is_dark));
}
}
&[type="color"] {
position: relative;
padding: 0px;
&::before {
z-index: -1;
content: "";
position: absolute;
inset: -5px;
border-radius: 4px;
background-color: spiceColor("selected-row", 0.4);
}
&:hover::before,
&:active::before {
background-color: spiceColor("selected-row", 0.6);
}
padding: 1px 3px;
}
}

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

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

View file

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

67
src/styles/Modals.scss Normal file
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();
}

View file

@ -1,16 +1,18 @@
// SASS overwrites the CSS invert function so we overwrite it back
@function invert($v) {
@return #{"invert("}$v#{")"};
}
@import "Util";
@import "Colors";
@import "Fonts";
@import "Inputs";
@import "ConfigMenu";
@import "ContextMenu.scss";
@import "ContextMenu";
@import "NoAds";
@import "Markdown";
@import "Info";
@import "Modals";
@import "ConnectDeviceList";
@import "Lyrics";
@import "Icons";
@import "Overlay";
@import "CustomAppTabBar";
:root {
--bar-height: 70px;
@ -20,31 +22,46 @@
--scrollbar-vertical-size: 8px;
--cover-border-radius: 8px;
--playbar-movement-anim-speed: 0.5s;
--image-radius: 10px;
--image-radius: 4px;
--sidebar-icons-border-radius: 50vh; // 50vh = round / pill
--song-transition-speed: 3s;
}
--is_dark: calc(1 - var(--is_light));
.os-scrollbar-handle {
background-color: spiceColor("text") !important;
border-radius: calc(var(--scrollbar-vertical-size) / 2);
}
.os-scrollbar-handle:hover {
filter: brightness(80%);
// Display warning for incorrectly installed js
@keyframes noJsShow {
to {
opacity: 1;
pointer-events: all;
}
}
&:not([dribbblish-js-installed])::after {
content: "dribbblish-dynamic.js not installed correctly";
position: fixed;
inset: 0px;
color: red;
background-color: black;
text-align: center;
line-height: 100vh;
opacity: 0;
pointer-events: none;
animation: noJsShow 0s ease-in 3s forwards;
@include spiceFont("glue", 32px, "Bold");
}
}
::-webkit-scrollbar {
width: var(--scrollbar-vertical-size);
}
.os-scrollbar-handle,
::-webkit-scrollbar-thumb {
background-color: spiceColor("text") !important;
border-radius: calc(var(--scrollbar-vertical-size) / 2);
background-color: spiceColor("sidebar") !important;
border-radius: 50vw;
}
.os-scrollbar-handle:hover,
::-webkit-scrollbar-thumb:hover {
filter: brightness(80%);
background-color: spiceColor("sidebar", 0.8) !important;
}
.main-type-mesto,
@ -74,6 +91,24 @@
color: spiceColor("text") !important;
}
#dribbblish-search-box {
display: none;
order: 99;
height: 28px;
border-radius: var(--sidebar-icons-border-radius) !important;
@include spiceFont("info", 14px, "Regular");
}
#main[search-box] {
#dribbblish-search-box {
display: block;
}
.main-topBar-topbarContent form[role="search"] {
display: none;
}
}
.main-home-homeHeader,
.x-entityHeader-overlay,
.x-actionBarBackground-background,
@ -88,15 +123,6 @@
color: white;
}
.connect-title,
.connect-header {
display: none;
}
.connect-device-list {
margin: 0px -5px;
}
/* Remove Topbar background colour */
.main-topBar-background {
background-color: unset !important;
@ -105,18 +131,31 @@
background-color: spiceColor("main");
}
.main-entityHeader-shadow,
.connect-device-list-container {
.main-entityHeader-shadow {
box-shadow: 0 4px 20px #21212130;
}
.main-trackList-rowMarker {
color: spiceColor("text");
}
.main-trackList-playingIcon {
-webkit-mask-image: url(icon64("equaliser", '{"size": 14}'));
background-color: currentColor;
content-visibility: hidden;
image-rendering: pixelated;
filter: grayscale(1);
}
.main-trackList-trackListRowGrid {
background-color: var(--spice-main);
}
.main-trackList-active {
background-color: spiceColor("selected-row", 0.25) !important;
}
.main-trackList-trackListRow:hover {
background-color: spiceColor("selected-row", 0.2) !important;
background-color: spiceColor("selected-row", 0.15) !important;
}
.main-trackList-trackListRow:focus-within,
@ -131,6 +170,11 @@ span.artist-artistVerifiedBadge-badge svg > path:last-of-type {
fill: spiceColor("text");
}
/* Playlist text color */
.main-entityHeader-subtitle.main-entityHeader-gra {
color: spiceColor("subtext");
}
/* Full window artist background */
.main-entityHeader-background.main-entityHeader-gradient {
opacity: 0.3;
@ -290,6 +334,7 @@ span.artist-artistVerifiedBadge-badge svg > path:last-of-type {
}
.main-nowPlayingBar-container {
position: relative;
border-radius: 0 0 var(--main-corner-radius) var(--main-corner-radius);
background-color: spiceColor("main");
border-top: 0;
@ -305,11 +350,27 @@ span.artist-artistVerifiedBadge-badge svg > path:last-of-type {
}
.main-connectBar-connectBar {
border-radius: 0 0 var(--main-corner-radius) var(--main-corner-radius);
border: 2px solid spiceColor("main");
border-top: 0;
background-color: spiceColor("button") !important;
color: spiceColor("text") !important;
// These styles are useless, but maybe we'll change this back to the way it was before so I'll leave them
display: none;
// position: absolute;
// display: flex;
// flex-direction: row-reverse;
// gap: 8px;
// height: fit-content;
// right: 4px;
// bottom: 4px;
// padding: 2px 8px;
// background-color: spiceColor("text", 0.2) !important;
// border-radius: var(--main-corner-radius);
// &::after {
// display: none;
// }
// svg {
// margin: 0px;
// }
}
.Root__nav-bar {
@ -472,11 +533,17 @@ html.sidebar-hide-text .GlueDropTarget span {
display: none;
}
/** */
.main-topBar-historyButtons .main-topBar-button {
background-color: unset;
width: 24px;
height: 24px;
.main-topBar-historyButtons {
display: flex;
align-items: center;
gap: 8px;
.main-topBar-button {
margin: 0px;
background-color: unset;
width: 24px;
height: 24px;
}
}
.main-topBar-historyButtons svg {
@ -492,13 +559,9 @@ html.sidebar-hide-text .GlueDropTarget span {
.playback-bar {
position: absolute;
width: var(--main-view-width);
left: var(--sidebar-width);
bottom: calc(var(--main-gap) + var(--bar-height) - 12px / 2);
}
.Root.is-connectBarVisible .playback-bar {
bottom: calc(var(--main-gap) + var(--bar-height) + 24px - 12px / 2);
left: 0px;
right: 0px;
top: calc(0px - 12px / 2);
}
.main-nowPlayingWidget-coverArt .cover-art {
@ -513,6 +576,7 @@ html.sidebar-hide-text .GlueDropTarget span {
.progress-bar {
--progress-bar-height: 2px;
--is-active-fg-color: #{spiceColor("button-active")};
--fg-color: #{spiceColor("button")};
--bg-color: #{spiceColor("text", 0.2)};
}
@ -528,12 +592,18 @@ html.sidebar-hide-text .GlueDropTarget span {
opacity: 1;
}
.progress-bar:not(:active) .x-progressBar-progressBarBg > div:first-child > div {
transition: transform var(--playbar-movement-anim-speed) ease;
}
#main[playbar-transition] {
.progress-bar:not(:active) .x-progressBar-progressBarBg > div:first-child > div {
transition: transform var(--playbar-movement-anim-speed) ease;
}
.progress-bar:not(:active) .progress-bar__slider {
transition-property: left, opacity;
.progress-bar:not(:active) .progress-bar__slider {
transition-property: left, opacity;
}
.playback-bar:not(:active) .prog-tooltip {
transition: transform var(--playbar-movement-anim-speed) ease;
}
}
.playback-bar .prog-tooltip {
@ -543,15 +613,11 @@ html.sidebar-hide-text .GlueDropTarget span {
left: 50%;
transform: translateX(calc(-50%));
padding: 0 5px;
border-radius: 4px;
text-align: center;
color: spiceColor("text");
background-color: spiceColor("button");
white-space: nowrap;
pointer-events: none;
}
.playback-bar:not(:active) .prog-tooltip {
transition: transform var(--playbar-movement-anim-speed) ease;
color: spiceColor("subtext");
@include spiceGlass();
}
.minimal-player .player-controls__buttons {
@ -606,13 +672,17 @@ html.sidebar-hide-text .GlueDropTarget span {
}
#main-trackInfo-year {
display: block;
#main[playbar-album-info="false"] & {
display: none;
}
}
#main-trackInfo-genre {
#main[playbar-genre-info="false"] & {
display: none;
}
}
.main-topBar-topbarContent .main-playButton-PlayButton {
--size: 35px !important;
}
@ -643,7 +713,7 @@ html.sidebar-hide-text .GlueDropTarget span {
padding: 0 12px;
}
img.playlist-picture {
.playlist-picture {
width: 32px;
height: 32px;
flex: 0 0 32px;
@ -651,6 +721,10 @@ img.playlist-picture {
background-size: cover;
background-position: center;
border-radius: var(--sidebar-icons-border-radius);
&[type="icon"] {
padding: 2px;
}
}
.main-rootlist-rootlistItem a span {
@ -742,12 +816,15 @@ li.GlueDropTarget {
padding-top: 32px;
}
#main[top-bar="none-padding"] .spotify__container--is-desktop.spotify__os--is-windows[dir="ltr"] .Root__top-bar + .main-buddyFeed-buddyFeedRoot .main-topBar-container {
padding-right: 167px;
}
.main-topBar-container {
display: flex;
gap: 8px;
max-width: unset;
padding: 16px 32px !important;
& > * {
height: 100%;
}
}
/** Custom elements */
@ -974,7 +1051,8 @@ span.main-userWidget-displayName,
.main-rootlist-wrapper > div:nth-child(2) > li img,
.main-navBar-navBarLink > svg,
.main-navBar-navBarLink > .icon {
.main-navBar-navBarLink > .icon,
.main-rootlist-rootlistItemLink > svg {
z-index: 1;
}
@ -994,7 +1072,7 @@ span.main-userWidget-displayName,
left: -200%;
opacity: 0.4;
background-color: black;
border-radius: var(--image-radius);
border-radius: var(--sidebar-icons-border-radius);
pointer-events: none;
transition: all calc(var(--sidebar-icons-hover-animation) * 0.2s) ease;
transition-property: left, opacity;
@ -1051,12 +1129,8 @@ div.GlueDropTarget.personal-library > *.active {
left: calc(var(--sidebar-width) + 10px);
}
.Root.is-connectBarVisible .main-coverSlotExpanded-container {
bottom: calc(var(--main-gap) + var(--bar-height) + 24px + 10px);
}
html.right-expanded-cover .main-coverSlotExpanded-container {
right: var(--main-gap);
right: calc(var(--main-gap) * 2);
left: unset;
}
@ -1113,11 +1187,6 @@ html.right-expanded-cover.buddyfeed-visible .main-coverSlotExpanded-container {
border-radius: 5px;
}
.connect-device-list-item:focus,
.connect-device-list-item:hover {
background-color: spiceColor("selected-row", 0.3);
}
/* 1.1.56 */
.main-navBar-navBar {
width: var(--sidebar-width) !important;
@ -1163,10 +1232,6 @@ html.right-expanded-cover.buddyfeed-visible .main-coverSlotExpanded-container {
.main-tag-container {
background-color: spiceColor("text");
}
/* progressbar tooltip text color */
.playback-bar .prog-tooltip {
color: spiceColor("sidebar-text") !important;
}
/* edit button of CustomApps */
.reddit-sort-container button.switch,
@ -1203,6 +1268,10 @@ html.right-expanded-cover.buddyfeed-visible .main-coverSlotExpanded-container {
color: spiceColor("sidebar-text") !important;
}
.main-shelf-title {
@include spiceFont("glue");
}
/* translucent background cover */
.Root__top-container::before {
z-index: 3;
@ -1216,24 +1285,37 @@ html.right-expanded-cover.buddyfeed-visible .main-coverSlotExpanded-container {
background-position: center center;
filter: blur(15px);
border-radius: var(--main-corner-radius);
opacity: calc(0.07 + 0.03 * var(--is_light, 0));
opacity: lightOffset(0.07, 0.03);
transition: background-image var(--song-transition-speed) linear;
html:not(.buddyfeed-visible) & {
inset: var(--main-gap) 0px var(--main-gap) var(--sidebar-width);
}
}
// Fix lyrics-plus having a scrollbar when top-bar is `solid` or `transparent`
.lyrics-lyricsContainer-LyricsContainer {
height: 100% !important;
}
// Fix main-view-containers having a scrollbar when top-bar is `solid` or `transparent`
.main-view-container__scroll-node-child {
height: 100%;
padding-bottom: 0px;
& > * {
height: 100% !important;
}
}
// Hide default Sporify "Offline" notice
// Hide default Spotify "Offline" notice
.main-noConnection {
display: none;
}
// Notifications
.main-notificationBubbleContainer-NotificationBubbleContainer {
.main-notificationBubble-NotificationBubble {
color: spiceColor("subtext");
@include spiceGlass();
}
}
// ! WORKAROUNDS / TEMP FIXES
// Spotify UI breaks after advertisements #63
canvas[width="250"][height="250"] {
@ -1251,3 +1333,20 @@ canvas[width="250"][height="250"] {
display: flex;
justify-content: center;
}
// fix albumn name font size (#171)
.main-trackInfo-release {
font-size: 10px;
}
// fix play buttons color going green (#174)
.encore-bright-accent-set {
--background-highlight: var(--spice-button) !important;
--background-press: var(--spice-button-active) !important;
color: var(--spice-sidebar-text);
}
// hide the triangle under the connect button
.control-button--active::after {
display: none;
}

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="16" height="16">
<path fill="currentColor" d="M413.1 222.5l22.2 22.2c9.4 9.4 9.4 24.6 0 33.9L241 473c-9.4 9.4-24.6 9.4-33.9 0L12.7 278.6c-9.4-9.4-9.4-24.6 0-33.9l22.2-22.2c9.5-9.5 25-9.3 34.3.4L184 343.4V56c0-13.3 10.7-24 24-24h32c13.3 0 24 10.7 24 24v287.4l114.8-120.5c9.3-9.8 24.8-10 34.3-.4z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 383 B

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512" width="16" height="16">
<path fill="currentColor" d="M278.9 511.5l-61-17.7c-6.4-1.8-10-8.5-8.2-14.9L346.2 8.7c1.8-6.4 8.5-10 14.9-8.2l61 17.7c6.4 1.8 10 8.5 8.2 14.9L293.8 503.3c-1.9 6.4-8.5 10.1-14.9 8.2zm-114-112.2l43.5-46.4c4.6-4.9 4.3-12.7-.8-17.2L117 256l90.6-79.7c5.1-4.5 5.5-12.3.8-17.2l-43.5-46.4c-4.5-4.8-12.1-5.1-17-.5L3.8 247.2c-5.1 4.7-5.1 12.8 0 17.5l144.1 135.1c4.9 4.6 12.5 4.4 17-.5zm327.2.6l144.1-135.1c5.1-4.7 5.1-12.8 0-17.5L492.1 112.1c-4.8-4.5-12.4-4.3-17 .5L431.6 159c-4.6 4.9-4.3 12.7.8 17.2L523 256l-90.6 79.7c-5.1 4.5-5.5 12.3-.8 17.2l43.5 46.4c4.5 4.9 12.1 5.1 17 .6z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 675 B

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="16" height="16">
<path fill="currentColor" d="M487.4 315.7l-42.6-24.6c4.3-23.2 4.3-47 0-70.2l42.6-24.6c4.9-2.8 7.1-8.6 5.5-14-11.1-35.6-30-67.8-54.7-94.6-3.8-4.1-10-5.1-14.8-2.3L380.8 110c-17.9-15.4-38.5-27.3-60.8-35.1V25.8c0-5.6-3.9-10.5-9.4-11.7-36.7-8.2-74.3-7.8-109.2 0-5.5 1.2-9.4 6.1-9.4 11.7V75c-22.2 7.9-42.8 19.8-60.8 35.1L88.7 85.5c-4.9-2.8-11-1.9-14.8 2.3-24.7 26.7-43.6 58.9-54.7 94.6-1.7 5.4.6 11.2 5.5 14L67.3 221c-4.3 23.2-4.3 47 0 70.2l-42.6 24.6c-4.9 2.8-7.1 8.6-5.5 14 11.1 35.6 30 67.8 54.7 94.6 3.8 4.1 10 5.1 14.8 2.3l42.6-24.6c17.9 15.4 38.5 27.3 60.8 35.1v49.2c0 5.6 3.9 10.5 9.4 11.7 36.7 8.2 74.3 7.8 109.2 0 5.5-1.2 9.4-6.1 9.4-11.7v-49.2c22.2-7.9 42.8-19.8 60.8-35.1l42.6 24.6c4.9 2.8 11 1.9 14.8-2.3 24.7-26.7 43.6-58.9 54.7-94.6 1.5-5.5-.7-11.3-5.6-14.1zM256 336c-44.1 0-80-35.9-80-80s35.9-80 80-80 80 35.9 80 80-35.9 80-80 80z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 945 B

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="16" height="16">
<path fill="currentColor" d="M255.545 8c-66.269.119-126.438 26.233-170.86 68.685L48.971 40.971C33.851 25.851 8 36.559 8 57.941V192c0 13.255 10.745 24 24 24h134.059c21.382 0 32.09-25.851 16.971-40.971l-41.75-41.75c30.864-28.899 70.801-44.907 113.23-45.273 92.398-.798 170.283 73.977 169.484 169.442C423.236 348.009 349.816 424 256 424c-41.127 0-79.997-14.678-110.63-41.556-4.743-4.161-11.906-3.908-16.368.553L89.34 422.659c-4.872 4.872-4.631 12.815.482 17.433C133.798 479.813 192.074 504 256 504c136.966 0 247.999-111.033 248-247.998C504.001 119.193 392.354 7.755 255.545 8z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 679 B

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512" width="16" height="16">
<path fill="currentColor" d="M5.09 154.87c-6.66 6.16-6.79 16.59-.35 22.97l34.24 33.96c6.14 6.09 16.02 6.23 22.4.38 6.99-6.4 14.31-12.22 21.65-18.01l-64.96-50.21c-4.3 3.71-8.79 7.04-12.98 10.91zm471.75 181.9l45.42-45.21c6.52-6.46 6.29-17.06-.57-23.17-64.94-57.74-148.91-82.66-230.34-74.98l-83.16-64.27c125.94-38.36 267.75-11.01 370.43 83.05 6.38 5.85 16.26 5.71 22.4-.38l34.24-33.96c6.44-6.39 6.3-16.82-.35-22.97C496.46 26.82 298.08-.76 133.42 71.35L45.47 3.37C38.49-2.05 28.43-.8 23.01 6.18L3.37 31.45C-2.05 38.42-.8 48.47 6.18 53.9l588.36 454.73c6.98 5.43 17.03 4.17 22.46-2.81l19.64-25.27c5.42-6.97 4.17-17.03-2.81-22.45L476.84 336.77zm-358.53-68.38c-6.86 6.1-7.08 16.7-.57 23.17l34.28 34.01c5.97 5.93 15.59 6.32 21.94.8 13.35-11.6 28.01-20.66 43.15-28.55l-68.36-52.83c-10.48 7.15-20.74 14.78-30.44 23.4zM256 416c0 35.35 28.65 64 64 64 31.91 0 58.15-23.42 62.99-53.98l-88.7-68.56C271.77 367.37 256 389.82 256 416z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 1,021 B

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"

View file

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

110
webpack.config.babel.js Normal file
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,71 +0,0 @@
const webpack = require("webpack");
const sass = require("sass");
const CopyPlugin = require("copy-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const path = require("path");
/** @type {import('webpack').Configuration} */
module.exports = {
entry: [path.resolve(__dirname, "./src/js/main.js"), path.resolve(__dirname, "./src/styles/main.scss"), path.resolve(__dirname, "./src/styles/Colors.scss")],
output: {
path: path.resolve(__dirname, "dist"),
filename: "dribbblish-dynamic.js"
},
resolve: {
extensions: [".js", ".svg"],
alias: {
svg: path.resolve(__dirname, "./src/svg")
}
},
module: {
rules: [
{
include: path.resolve(__dirname, "./src/js/main.js"),
use: []
},
{
include: path.resolve(__dirname, "./src/styles/main.scss"),
type: "asset/resource",
generator: {
filename: "user.css"
},
use: [
{
loader: "sass-loader",
options: {
implementation: sass,
sourceMap: true
}
}
]
},
{
include: path.resolve(__dirname, "./src/styles/Colors.scss"),
type: "asset/resource",
generator: {
filename: "color.ini"
},
use: [path.resolve(__dirname, "./src/loaders/color-loader.js")]
},
{
test: /\.svg/,
exclude: /node_modules/,
type: "asset/source"
}
]
},
devtool: "inline-source-map",
plugins: [
new CleanWebpackPlugin({
protectWebpackAssets: false,
cleanAfterEveryBuildPatterns: ["*.LICENSE.txt"]
}),
new webpack.DefinePlugin({
"process.env.DRIBBBLISH_VERSION": JSON.stringify(process.env.DRIBBBLISH_VERSION || "Dev"),
"process.env.COMMIT_HASH": JSON.stringify(process.env.COMMIT_HASH || "local")
}),
new CopyPlugin({
patterns: [{ from: "src/assets", to: "assets" }]
})
]
};