mirror of
https://github.com/danbulant/dribbblish-dynamic-theme
synced 2026-06-21 07:42:48 +00:00
Compare commits
216 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77182042dd | ||
|
|
7492c86124 | ||
|
|
55693078f6 | ||
|
|
2c8dea7f5d | ||
|
|
6d23932d0b | ||
|
|
d7e355bf46 | ||
|
|
c9ec3971ad | ||
|
|
bb7017960b | ||
|
|
1064087b48 | ||
|
|
3edfc3fa6b | ||
|
|
239033ad5e | ||
|
|
145cfe9b53 | ||
|
|
2e55b23b44 | ||
|
|
8c5a45c64a | ||
|
|
8d9cdf3df5 | ||
|
|
9f5a988eca | ||
|
|
167e253a86 | ||
|
|
ba5846bc41 | ||
|
|
d687a13cb7 | ||
|
|
33134709ed | ||
|
|
13e1f64054 | ||
|
|
827c9f3ba0 | ||
|
|
f13a3c1ff9 | ||
|
|
14da00bf74 | ||
|
|
23fe7deb15 | ||
|
|
a146134b1d | ||
|
|
4c134a94a0 | ||
|
|
14902a060f | ||
|
|
c45c5faad0 | ||
|
|
162616c2cb | ||
|
|
301a972a53 | ||
|
|
290df59e02 | ||
|
|
3e2cde1e53 | ||
|
|
eb9a7b1939 | ||
|
|
7d3341b7a7 | ||
|
|
d78ae80ef1 | ||
|
|
316e7581cf | ||
|
|
36fe1f5a2f | ||
|
|
273a5b3e7e | ||
|
|
f9c30d230a | ||
|
|
bc200bfef6 | ||
|
|
13186767e8 | ||
|
|
d69c825555 | ||
|
|
56b109cc01 | ||
|
|
decfe17aa2 | ||
|
|
b55a82bf99 | ||
|
|
c052260a8b | ||
|
|
ee98ffd134 | ||
|
|
305d70fb6d | ||
|
|
e50b8b4bb3 | ||
|
|
ca0d24fd11 | ||
|
|
fa29a12d00 | ||
|
|
0ecd8d6166 | ||
|
|
c5fec9cbd1 | ||
|
|
11eae40857 | ||
|
|
604ddfbf97 | ||
|
|
1b45d68568 | ||
|
|
2bfa6e55db | ||
|
|
48c475ee9a | ||
|
|
8517327ee6 | ||
|
|
e2e6214159 | ||
|
|
7ac95c23a9 | ||
|
|
048d007430 | ||
|
|
0e50a92431 | ||
|
|
4dd2d42ca4 | ||
|
|
ead9615ef3 | ||
|
|
cd325a3f05 | ||
|
|
6c84a848fc | ||
|
|
b3e108f2bf | ||
|
|
61cbee1c1d | ||
|
|
8edba57dd5 | ||
|
|
55e6ef0ca2 | ||
|
|
25b5b771dd | ||
|
|
9e7349712b | ||
|
|
0e946cf05a | ||
|
|
f69afbb882 | ||
|
|
467a971d31 | ||
|
|
55b714dbda | ||
|
|
34bd5fe4f8 | ||
|
|
76fda08a71 | ||
|
|
651896418c | ||
|
|
e6ef0953ea | ||
|
|
8968202a3e | ||
|
|
8747efdd7b | ||
|
|
3278f1fbe0 | ||
|
|
0794a51b56 | ||
|
|
200b112fe1 | ||
|
|
193953dc91 | ||
|
|
1c4ba1a29d | ||
|
|
9ed25390c0 | ||
|
|
164058b875 | ||
|
|
eb87c41128 | ||
|
|
5d5d1bc3c1 | ||
|
|
836c13abec | ||
|
|
7e96398958 | ||
|
|
1a61984656 | ||
|
|
7e59834be4 | ||
|
|
80b4d68835 | ||
|
|
3629e883a5 | ||
|
|
7f559d2a2c | ||
|
|
27cd9a8a0e | ||
|
|
b2cfe07578 | ||
|
|
e18320acb3 | ||
|
|
2d30ff934e | ||
|
|
cd24ce8f06 | ||
|
|
a00193acde | ||
|
|
cf9f6e154b | ||
|
|
6ac36c80a3 | ||
|
|
b60682cac5 | ||
|
|
fdfe690b79 | ||
|
|
83546ad90c | ||
|
|
098a112819 | ||
|
|
7c2fbea06f | ||
|
|
9994713e55 | ||
|
|
d206317f45 | ||
|
|
2e1b7a05b5 | ||
|
|
8e702a49e1 | ||
|
|
5d537336a9 | ||
|
|
bcafa8b5a4 | ||
|
|
6cc4b7d492 | ||
|
|
af5af92b62 | ||
|
|
5e0236dff5 | ||
|
|
ea1ba797f6 | ||
|
|
a5b92ed62c | ||
|
|
3798bc2723 | ||
|
|
61db2917f8 | ||
|
|
89742ae74a | ||
|
|
4328a81a1c | ||
|
|
219c16872c | ||
|
|
1296e13c73 | ||
|
|
c471531e71 | ||
|
|
e4110f8df3 | ||
|
|
38d70581ac | ||
|
|
3f308a2c9e | ||
|
|
328cf2a59a | ||
|
|
4b251dfa8d | ||
|
|
f0a562f70a | ||
|
|
83b2801a36 | ||
|
|
675ff2d94d | ||
|
|
8264eb33dd | ||
|
|
fe0503e9f8 | ||
|
|
9afccf31c3 | ||
|
|
980ed8d747 | ||
|
|
71ce39abdb | ||
|
|
c2d7f851b1 | ||
|
|
61db261115 | ||
|
|
18f0d454be | ||
|
|
f45f570730 | ||
|
|
a6c1a89e5c | ||
|
|
8ba60b477a | ||
|
|
4538c1bb4b | ||
|
|
f6aa2a5c7d | ||
|
|
3448e7cffc | ||
|
|
2b6853bc70 | ||
|
|
58324a3ff2 | ||
|
|
055510a7ad | ||
|
|
884ca0baea | ||
|
|
da404f4c7b | ||
|
|
e1d393e2a0 | ||
|
|
3088e8a71d | ||
|
|
1b66ab044f | ||
|
|
3d96810b72 | ||
|
|
3b248c465b | ||
|
|
a3cea3012e | ||
|
|
784947e3a5 | ||
|
|
3e41a07907 | ||
|
|
c48984b9ec | ||
|
|
74140f1d42 | ||
|
|
fd01cc02ce | ||
|
|
43452294db | ||
|
|
b610a0dd8b | ||
|
|
1eddb9655e | ||
|
|
dfc2c79921 | ||
|
|
415efc5731 | ||
|
|
f50ccaaf10 | ||
|
|
90662cd66d | ||
|
|
aaeb51a6ca | ||
|
|
9d05365ffb | ||
|
|
59daccdbf2 | ||
|
|
a880871db0 | ||
|
|
215fbad04e | ||
|
|
a77033801d | ||
|
|
b1765a1e3c | ||
|
|
284b3cc9f2 | ||
|
|
3b60f46002 | ||
|
|
5ebc888d42 | ||
|
|
b8d6d98883 | ||
|
|
759c0e058f | ||
|
|
d3b9577237 | ||
|
|
0c318e1f21 | ||
|
|
ec84ad0c79 | ||
|
|
5256ff6b7a | ||
|
|
e00027bb69 | ||
|
|
b5033f8872 | ||
|
|
574a8332aa | ||
|
|
7b393b7873 | ||
|
|
4aa0998924 | ||
|
|
6de1a4e9e0 | ||
|
|
d078c29ab6 | ||
|
|
d4780d6220 | ||
|
|
8b2a366c95 | ||
|
|
eb60df1a9a | ||
|
|
48d57498aa | ||
|
|
8b2d6fa141 | ||
|
|
19aeb86204 | ||
|
|
d1d15d2425 | ||
|
|
fc6ac2e35c | ||
|
|
5404036ee4 | ||
|
|
1131ccbf91 | ||
|
|
2e00ea05a0 | ||
|
|
0e1e89fdec | ||
|
|
81ac31ae25 | ||
|
|
d78848c29f | ||
|
|
c272dcb788 | ||
|
|
2b41bd25cc | ||
|
|
aa33fe1b42 |
66 changed files with 12253 additions and 1272 deletions
3
.babelrc
Normal file
3
.babelrc
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"presets": ["@babel/env"]
|
||||
}
|
||||
34
.github/ISSUE_TEMPLATE/bug_report.md
vendored
34
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
|
@ -1,35 +1,39 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG] "
|
||||
about: Create a report to help us improve (After v3.1.1, preferably open this report from inside the Dribbblish settings `About > Report Bugs` as it adds additional info automatically)
|
||||
title: ""
|
||||
labels: ["bug"]
|
||||
assignees: ""
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
<!-- If applicable, add screenshots to help explain your problem. -->
|
||||
|
||||
**Desktop Setup**
|
||||
- Operating System: [e.g. Windows 10, MacOS 10.14]
|
||||
- Spotify version [e.g. 1.1.71.560] from `Menu > 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]
|
||||
```
|
||||
|
|
|
|||
10
.github/ISSUE_TEMPLATE/feature_request.md
vendored
10
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
|
@ -1,20 +1,20 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[Feature] "
|
||||
title: ""
|
||||
labels: ["enhancement"]
|
||||
assignees: ""
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
<!-- A clear and concise description of what you want to happen. -->
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
||||
|
|
|
|||
26
.github/workflows/post-release.yaml
vendored
26
.github/workflows/post-release.yaml
vendored
|
|
@ -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
|
||||
|
|
|
|||
3
.github/workflows/prettier.yml
vendored
3
.github/workflows/prettier.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
4
.github/workflows/release.yaml
vendored
4
.github/workflows/release.yaml
vendored
|
|
@ -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
21
.github/workflows/stale.yml
vendored
Normal 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
|
||||
17
.github/workflows/webpack-test.yml
vendored
17
.github/workflows/webpack-test.yml
vendored
|
|
@ -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/**
|
||||
|
|
|
|||
13
CHANGELOG.md
13
CHANGELOG.md
|
|
@ -1,2 +1,13 @@
|
|||
Added:
|
||||
- Ability to hide premium features in addition to ads
|
||||
- Ability to show current artist's genres
|
||||
|
||||
Fixed:
|
||||
- Album info on playbar setting being ignored (#102)
|
||||
- Custom search input not being focussed after clicking
|
||||
- When changing songs, the color shifts to blue for a second (#179)
|
||||
|
||||
Improved:
|
||||
- Add `embedWidgetGenerator` modals to custom modal styles. (Things like the [spicetify-marketplace](https://github.com/CharlieS1103/spicetify-marketplace) options)
|
||||
- Changed custom app tab styles to fit in with the theme
|
||||
- Changed notification styles to fit in with other elements of the theme
|
||||
- The visualizer icon when playing a song from a playlist is now colored
|
||||
32
README.md
32
README.md
|
|
@ -1,29 +1,32 @@
|
|||
# Dribbblish Dynamic
|
||||
A theme for [Spicetify](https://github.com/khanhas/spicetify-cli)
|
||||
|
||||
<a href="https://github.com/JulienMaille/dribbblish-dynamic-theme/releases/latest"><img src="https://img.shields.io/github/release/JulienMaille/dribbblish-dynamic-theme/all.svg"></a>
|
||||
<a href="https://github.com/JulienMaille/dribbblish-dynamic-theme/releases"><img src="https://img.shields.io/github/downloads/JulienMaille/dribbblish-dynamic-theme/total.svg"></a>
|
||||
|
||||
### Preview
|
||||
|
||||
<img src="showcase-images/preview.gif" alt="img" width="500px">
|
||||
<img src="https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/main/showcase-images/preview.gif" alt="img" width="500px">
|
||||
|
||||
## Features
|
||||
### Resizable sidebar
|
||||
|
||||
<img src="showcase-images/resize-sidebar.png" alt="img" width="500px">
|
||||
<img src="https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/main/showcase-images/resize-sidebar.png" alt="img" width="500px">
|
||||
|
||||
### Customizable sidebar
|
||||
Rearrange icons positions, stick icons to header or hide unnecessary to save space.
|
||||
Turn on "Sidebar config" mode in Profile menu and hover on icon to show control buttons.
|
||||
After you finish customizing, turn off Config mode in Profile menu to save.
|
||||
|
||||
<img src="showcase-images/customize-sidebar.png" alt="img" width="500px">
|
||||
<img src="https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/main/showcase-images/customize-sidebar.png" alt="img" width="500px">
|
||||
|
||||
### Playlist Folder image
|
||||
Right click at folder and choose images for your playlist folder. Every image formats supported by Chrome can be used, but do keep image size small and in compressed format.
|
||||
|
||||
<img src="showcase-images/playlist-folders.gif" alt="img" width="500px">
|
||||
<img src="https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/main/showcase-images/playlist-folders.gif" alt="img" width="500px">
|
||||
|
||||
### Left/Right expanded cover
|
||||
In profile menu, toggle option "Right expanded cover" to change expaned current track cover image to left or right side, whereever you prefer.
|
||||
In profile menu, toggle option "Right expanded cover" to change expanded current track cover image to left or right side, wherever you prefer.
|
||||
|
||||
## Install / Update
|
||||
Make sure you are using spicetify >= v2.6.0 and Spotify >= v1.1.67.
|
||||
|
|
@ -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
|
||||
|
|
@ -53,7 +56,7 @@ curl -fsSL https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-the
|
|||
```
|
||||
|
||||
## IMPORTANT!
|
||||
From Spotify > v1.1.62, in sidebar, they use an adaptive render mechanic to actively show and hide items on scroll. It helps reducing number of items to render, hence there is significant performance boost if you have a large playlists collection. But the drawbacks is that item height is hard-coded, it messes up user interaction when we explicity change, in CSS, playlist item height bigger than original value. So you need to add these 2 lines in Patch section in config file:
|
||||
From Spotify > v1.1.62, in sidebar, they use an adaptive render mechanic to actively show and hide items on scroll. It helps reducing number of items to render, hence there is significant performance boost if you have a large playlists collection. But the drawbacks is that item height is hard-coded, it messes up user interaction when we explicitly change, in CSS, playlist item height bigger than original value. So you need to add these 2 lines in Patch section in config file:
|
||||
```ini
|
||||
[Patch]
|
||||
xpui.js_find_8008 = ,(\w+=)32,
|
||||
|
|
@ -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
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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
22
manifest.json.rem
Normal 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
9109
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
25
package.json
25
package.json
|
|
@ -1,27 +1,34 @@
|
|||
{
|
||||
"name": "dribbblish-dynamic-theme",
|
||||
"version": "3.1.0",
|
||||
"version": "4.1.1",
|
||||
"homepage": "https://github.com/JulienMaille/dribbblish-dynamic-theme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/JulienMaille/dribbblish-dynamic-theme/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.17.0",
|
||||
"@babel/preset-env": "^7.16.5",
|
||||
"@babel/register": "^7.16.5",
|
||||
"@material-icons/svg": "^1.0.22",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"copy-webpack-plugin": "^9.0.1",
|
||||
"css-minimizer-webpack-plugin": "^3.1.1",
|
||||
"node-sass": "^6.0.1",
|
||||
"sass": "^1.43.5",
|
||||
"sass-loader": "^12.2.0",
|
||||
"webpack": "^5.58.2",
|
||||
"webpack-cli": "^4.9.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack --mode=development"
|
||||
"build": "webpack"
|
||||
},
|
||||
"dependencies": {
|
||||
"chroma-js": "^2.1.2",
|
||||
"colorthief": "^2.3.2",
|
||||
"jquery": "^3.6.0",
|
||||
"moment": "^2.29.1",
|
||||
"node-vibrant": "3.1.4",
|
||||
"raw-loader": "^4.0.2"
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.defaultsdeep": "^4.6.1",
|
||||
"markdown-it": "^12.3.2",
|
||||
"markdown-it-attrs": "^4.1.0",
|
||||
"moment": "^2.29.2",
|
||||
"node-vibrant": "^3.1.6",
|
||||
"svgson": "^5.2.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
39
patch-dark-mode.ps1
Normal file
39
patch-dark-mode.ps1
Normal 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"
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
src/fonts/GoogleSans-Black.ttf
Normal file
BIN
src/fonts/GoogleSans-Black.ttf
Normal file
Binary file not shown.
BIN
src/fonts/GoogleSans-Bold.ttf
Normal file
BIN
src/fonts/GoogleSans-Bold.ttf
Normal file
Binary file not shown.
BIN
src/fonts/GoogleSans-Light.ttf
Normal file
BIN
src/fonts/GoogleSans-Light.ttf
Normal file
Binary file not shown.
BIN
src/fonts/GoogleSans-Medium.ttf
Normal file
BIN
src/fonts/GoogleSans-Medium.ttf
Normal file
Binary file not shown.
BIN
src/fonts/GoogleSans-Regular.ttf
Normal file
BIN
src/fonts/GoogleSans-Regular.ttf
Normal file
Binary file not shown.
BIN
src/fonts/GoogleSans-Thin.ttf
Normal file
BIN
src/fonts/GoogleSans-Thin.ttf
Normal file
Binary file not shown.
BIN
src/fonts/Roboto-Black.ttf
Normal file
BIN
src/fonts/Roboto-Black.ttf
Normal file
Binary file not shown.
BIN
src/fonts/Roboto-Bold.ttf
Normal file
BIN
src/fonts/Roboto-Bold.ttf
Normal file
Binary file not shown.
BIN
src/fonts/Roboto-Light.ttf
Normal file
BIN
src/fonts/Roboto-Light.ttf
Normal file
Binary file not shown.
BIN
src/fonts/Roboto-Medium.ttf
Normal file
BIN
src/fonts/Roboto-Medium.ttf
Normal file
Binary file not shown.
BIN
src/fonts/Roboto-Regular.ttf
Normal file
BIN
src/fonts/Roboto-Regular.ttf
Normal file
Binary file not shown.
BIN
src/fonts/Roboto-Thin.ttf
Normal file
BIN
src/fonts/Roboto-Thin.ttf
Normal file
Binary file not shown.
131
src/icons/equaliser.svg
Normal file
131
src/icons/equaliser.svg
Normal 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
42
src/icons/loading.svg
Normal 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
3
src/icons/spicetify.svg
Normal 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
3
src/icons/spotify.svg
Normal 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 |
|
|
@ -1,10 +1,15 @@
|
|||
import svgUndo from "../svg/undo.svg";
|
||||
import $ from "jquery";
|
||||
|
||||
import { renderMD, defaults } from "./Util";
|
||||
import { icons } from "./Icons";
|
||||
|
||||
export default class ConfigMenu {
|
||||
/** @typedef {"checkbox" | "select" | "button" | "slider" | "number" | "text" | "textarea" | "time" | "color"} DribbblishConfigType */
|
||||
|
||||
/**
|
||||
* @typedef {Object} DribbblishConfigItem
|
||||
* @property {"checkbox" | "select" | "button" | "slider" | "number" | "text" | "time" | "color"} type
|
||||
* @property {String|DribbblishConfigArea} [area={name: "Main Settings", order: 0}]
|
||||
* @property {DribbblishConfigType} type
|
||||
* @property {String | DribbblishConfigArea} [area={name: "Main Settings", order: 0}]
|
||||
* @property {any} [data={}]
|
||||
* @property {Number} [order=0] order < 0 = Higher up | order > 0 = Lower Down
|
||||
* @property {String} key
|
||||
|
|
@ -12,13 +17,16 @@ export default class ConfigMenu {
|
|||
* @property {String} [description=""]
|
||||
* @property {any} [defaultValue]
|
||||
* @property {Boolean} [hidden=false]
|
||||
* @property {Boolean} [resetButton=true]
|
||||
* @property {Boolean} [insertOnTop=false]
|
||||
* @property {Boolean} [fireInitialChange=true]
|
||||
* @property {Boolean} [save=true]
|
||||
* @property {validate} [validate]
|
||||
* @property {showChildren} [showChildren]
|
||||
* @property {onAppended} [onAppended]
|
||||
* @property {onChange} [onChange]
|
||||
* @property {DribbblishConfigItem[]} [children=[]]
|
||||
* @property {String} [childOf=null] key of parent (set automatically)
|
||||
* @property {String} [parent=null] key of parent (set automatically)
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
@ -28,6 +36,13 @@ export default class ConfigMenu {
|
|||
* @property {Boolean} [toggleable=true]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @callback validate
|
||||
* @this {DribbblishConfigItem}
|
||||
* @param {any} value
|
||||
* @returns {Boolean | String}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @callback showChildren
|
||||
* @this {DribbblishConfigItem}
|
||||
|
|
@ -51,82 +66,38 @@ export default class ConfigMenu {
|
|||
/** @type {Object.<string, DribbblishConfigItem>} */
|
||||
#config;
|
||||
|
||||
/** @type {Spicetify.Menu.Item} */
|
||||
#configButton;
|
||||
|
||||
constructor() {
|
||||
this.#config = {};
|
||||
this.configButton = new Spicetify.Menu.Item("Dribbblish Settings", false, () => this.open());
|
||||
this.configButton.register();
|
||||
this.#configButton = new Spicetify.Menu.Item("Dribbblish Settings", false, () => this.open());
|
||||
this.#configButton.register();
|
||||
|
||||
const container = document.createElement("div");
|
||||
container.id = "dribbblish-config";
|
||||
container.innerHTML = /* html */ `
|
||||
<div class="dribbblish-config-container">
|
||||
<button aria-label="Close" class="dribbblish-config-close main-trackCreditsModal-closeBtn">
|
||||
<svg width="18" height="18" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><path d="M31.098 29.794L16.955 15.65 31.097 1.51 29.683.093 15.54 14.237 1.4.094-.016 1.508 14.126 15.65-.016 29.795l1.414 1.414L15.54 17.065l14.144 14.143" fill="currentColor" fill-rule="evenodd"></path></svg>
|
||||
</button>
|
||||
<button aria-label="Close" class="dribbblish-config-close main-trackCreditsModal-closeBtn">${icons.get("close", { size: 24 })}</button>
|
||||
<h1>Dribbblish Settings</h1>
|
||||
<input type="search" placeholder="Search" class="dribbblish-config-search">
|
||||
<div class="dribbblish-config-areas"></div>
|
||||
</div>
|
||||
<div class="dribbblish-config-backdrop"></div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(container);
|
||||
document.querySelector(".dribbblish-config-close").addEventListener("click", () => this.close());
|
||||
document.querySelector(".dribbblish-config-backdrop").addEventListener("click", () => this.close());
|
||||
$(".dribbblish-config-close").on("click", () => this.close());
|
||||
$(".dribbblish-config-backdrop").on("click", () => this.close());
|
||||
$(".dribbblish-config-search").on("input", (e) => this.#search(e.target.value));
|
||||
}
|
||||
|
||||
open() {
|
||||
document.getElementById("dribbblish-config").setAttribute("active", "");
|
||||
$("#dribbblish-config").attr("active", true);
|
||||
}
|
||||
|
||||
close() {
|
||||
document.getElementById("dribbblish-config").removeAttribute("active");
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {DribbblishConfigItem} options
|
||||
*/
|
||||
addInputHTML(options) {
|
||||
this.registerArea(options.area);
|
||||
const parent = document.querySelector(`.dribbblish-config-area[name="${options.area.name}"] .dribbblish-config-area-items`);
|
||||
|
||||
const elem = document.createElement("div");
|
||||
elem.style.order = options.order;
|
||||
elem.classList.add("dribbblish-config-item");
|
||||
elem.setAttribute("key", options.key);
|
||||
elem.setAttribute("type", options.type);
|
||||
elem.setAttribute("hidden", options.hidden);
|
||||
if (options.childOf) elem.setAttribute("parent", options.childOf);
|
||||
elem.innerHTML = /* html */ `
|
||||
<h2 class="x-settings-title main-type-cello${!options.description ? " no-desc" : ""}" as="h2">
|
||||
${options.name}
|
||||
${["button"].includes(options.type) ? "" : /* html */ `<button aria-label="Reset" class="dribbblish-config-item-reset main-trackCreditsModal-closeBtn">${svgUndo}</button>`}
|
||||
</h2>
|
||||
<label class="main-type-mesto">${options.description.replace(/\n/g, "<br>")}</label>
|
||||
<label class="x-toggle-wrapper x-settings-secondColumn">
|
||||
${options.input}
|
||||
</label>
|
||||
`;
|
||||
|
||||
if (options.insertOnTop && parent.children.length > 0) {
|
||||
parent.insertBefore(elem, parent.children[0]);
|
||||
} else {
|
||||
parent.appendChild(elem);
|
||||
}
|
||||
|
||||
const resetButton = elem.querySelector(".dribbblish-config-item-reset");
|
||||
if (resetButton) {
|
||||
elem.querySelector(".dribbblish-config-item-reset").addEventListener("click", () => {
|
||||
this.reset(options.key);
|
||||
const defaultVal = this.get(options.key);
|
||||
if (options.type == "checkbox") {
|
||||
elem.querySelector("input").checked = defaultVal;
|
||||
} else {
|
||||
elem.querySelector("input, select").value = defaultVal;
|
||||
}
|
||||
options.onChange(defaultVal);
|
||||
});
|
||||
}
|
||||
$("#dribbblish-config").removeAttr("active");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -141,16 +112,20 @@ export default class ConfigMenu {
|
|||
data: {},
|
||||
name: "",
|
||||
description: "",
|
||||
hidden: false,
|
||||
resetButton: true,
|
||||
insertOnTop: false,
|
||||
fireInitialChange: true,
|
||||
save: true,
|
||||
validate: () => true,
|
||||
showChildren: () => true,
|
||||
onAppended: () => {},
|
||||
onChange: () => {},
|
||||
children: [],
|
||||
childOf: null
|
||||
parent: null
|
||||
};
|
||||
// Set Defaults
|
||||
options = { ...defaultOptions, ...options };
|
||||
options = defaults(options, defaultOptions);
|
||||
if (typeof options.area == "string") options.area = { name: options.area, order: 0 };
|
||||
options.description = options.description
|
||||
.split("\n")
|
||||
|
|
@ -159,163 +134,173 @@ export default class ConfigMenu {
|
|||
.join("\n");
|
||||
options._onChange = options.onChange;
|
||||
options.onChange = (val) => {
|
||||
const isValid = validate(val) === true;
|
||||
$(`.dribbblish-config-item[key="${options.key}"]`).attr("changed", options.type != "button" && isValid && val != options.defaultValue ? "" : null);
|
||||
if (!isValid) return;
|
||||
this.set(options.key, val, options.save);
|
||||
|
||||
options._onChange.call(options, val);
|
||||
const show = options.showChildren.call(options, val);
|
||||
options.children.forEach((child) => this.setHidden(child.key, Array.isArray(show) ? !show.includes(child.key) : !show));
|
||||
};
|
||||
options.children = options.children.map((child) => {
|
||||
return { ...child, area: options.area, childOf: options.key };
|
||||
return { ...child, area: options.area, parent: options.key, order: options.order ?? 0 + child.order ?? 0 };
|
||||
});
|
||||
|
||||
this.#config[options.key] = options;
|
||||
|
||||
function validate(val) {
|
||||
const isValid = options.validate.call(options, val);
|
||||
const $elem = $(`.dribbblish-config-item[key="${options.key}"]`);
|
||||
if (isValid === true) {
|
||||
$elem.attr("invalid", null).css("--validation-error", "");
|
||||
} else {
|
||||
const error = isValid === false ? "Invalid" : isValid;
|
||||
$elem.attr("invalid", "").css("--validation-error", `"${error.replace(/"/g, `\\"`)}"`);
|
||||
}
|
||||
return isValid;
|
||||
}
|
||||
|
||||
let elem;
|
||||
let input;
|
||||
if (options.type == "checkbox") {
|
||||
const input = /* html */ `
|
||||
<input id="dribbblish-config-input-${options.key}" class="x-toggle-input" type="checkbox"${this.get(options.key) ? " checked" : ""}>
|
||||
<span class="x-toggle-indicatorWrapper">
|
||||
<span class="x-toggle-indicator"></span>
|
||||
</span>
|
||||
`;
|
||||
this.addInputHTML({ ...options, input });
|
||||
elem = document.createDocumentFragment();
|
||||
|
||||
document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener("change", (e) => {
|
||||
this.set(options.key, e.target.checked);
|
||||
options.onChange(this.get(options.key));
|
||||
input = document.createElement("input");
|
||||
input.classList.add("dribbblish-config-input");
|
||||
input.type = "checkbox";
|
||||
input.classList.add("x-toggle-input");
|
||||
input.checked = this.get(options.key);
|
||||
input.addEventListener("change", (e) => {
|
||||
options.onChange(e.target.checked);
|
||||
});
|
||||
elem.appendChild(input);
|
||||
|
||||
const indicator = document.createElement("span");
|
||||
indicator.classList.add("x-toggle-indicatorWrapper");
|
||||
indicator.innerHTML = /* html */ `<span class="x-toggle-indicator"></span>`;
|
||||
elem.appendChild(indicator);
|
||||
} else if (options.type == "select") {
|
||||
// Validate
|
||||
const val = this.get(options.key);
|
||||
if (!Object.keys(options.data).includes(val)) this.reset(options.key);
|
||||
|
||||
const input = /* html */ `
|
||||
<select class="main-dropDown-dropDown" id="dribbblish-config-input-${options.key}">
|
||||
${Object.entries(options.data)
|
||||
.map(([key, name]) => `<option value="${key}"${this.get(options.key) == key ? " selected" : ""}>${name}</option>`)
|
||||
.join("")}
|
||||
</select>
|
||||
`;
|
||||
this.addInputHTML({ ...options, input });
|
||||
|
||||
document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener("change", (e) => {
|
||||
this.set(options.key, e.target.value);
|
||||
options.onChange(this.get(options.key));
|
||||
input = document.createElement("select");
|
||||
input.classList.add("dribbblish-config-input");
|
||||
input.classList.add("main-dropDown-dropDown");
|
||||
input.innerHTML = Object.entries(options.data)
|
||||
.map(([key, name]) => `<option value="${key}"${this.get(options.key) == key ? " selected" : ""}>${name}</option>`)
|
||||
.join("");
|
||||
input.addEventListener("change", (e) => {
|
||||
options.onChange(e.target.value);
|
||||
});
|
||||
} else if (options.type == "button") {
|
||||
options.fireInitialChange = false;
|
||||
if (typeof options.data != "string") options.data = options.name;
|
||||
options.fireInitialChange = false;
|
||||
options.resetButton = false;
|
||||
options.save = false;
|
||||
|
||||
const input = /* html */ `
|
||||
<button class="main-buttons-button main-button-primary" type="button" id="dribbblish-config-input-${options.key}">
|
||||
<div class="x-settings-buttonContainer">
|
||||
<span>${options.data}</span>
|
||||
</div>
|
||||
</button>
|
||||
`;
|
||||
this.addInputHTML({ ...options, input });
|
||||
|
||||
document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener("click", (e) => {
|
||||
input = document.createElement("button");
|
||||
input.classList.add("dribbblish-config-input");
|
||||
input.type = "button";
|
||||
input.classList.add("main-buttons-button", "main-button-primary");
|
||||
input.innerHTML = /* html */ `<div class="x-settings-buttonContainer"><span>${options.data}</span></div>`;
|
||||
input.addEventListener("click", (e) => {
|
||||
options.onChange(true);
|
||||
});
|
||||
} else if (options.type == "number") {
|
||||
// Validate
|
||||
if (options.defaultValue == null) options.defaultValue = 0;
|
||||
const val = this.get(options.key);
|
||||
if (options.data.min != null && val < options.data.min) this.set(options.key, options.data.min);
|
||||
if (options.data.max != null && val > options.data.max) this.set(options.key, options.data.max);
|
||||
const _val = this.get(options.key);
|
||||
if (options.data.min != null && _val < options.data.min) this.set(options.key, options.data.min, options.save);
|
||||
if (options.data.max != null && _val > options.data.max) this.set(options.key, options.data.max, options.save);
|
||||
|
||||
const input = /* html */ `
|
||||
<input
|
||||
type="number"
|
||||
id="dribbblish-config-input-${options.key}"
|
||||
${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?
|
||||
document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener("keypress", (e) => {
|
||||
if (["+", "-", "e"].includes(e.key)) e.preventDefault();
|
||||
input = document.createElement("input");
|
||||
input.classList.add("dribbblish-config-input");
|
||||
input.type = "number";
|
||||
input.value = this.get(options.key);
|
||||
input.step = options.data.step ?? 1;
|
||||
if (options.data.min != null) input.min = options.data.min;
|
||||
if (options.data.max != null) input.max = options.data.max;
|
||||
input.addEventListener("keypress", (e) => {
|
||||
// Prevent inputting + and e.
|
||||
if (["+", "e"].includes(e.key)) e.preventDefault();
|
||||
});
|
||||
|
||||
document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener("input", (e) => {
|
||||
input.addEventListener("input", (e) => {
|
||||
if (options.data.min != null && e.target.value < options.data.min) e.target.value = options.data.min;
|
||||
if (options.data.max != null && e.target.value > options.data.max) e.target.value = options.data.max;
|
||||
|
||||
this.set(options.key, Number(e.target.value));
|
||||
options.onChange(this.get(options.key));
|
||||
options.onChange(Number(e.target.value));
|
||||
});
|
||||
} else if (options.type == "text") {
|
||||
if (options.defaultValue == null) options.defaultValue = "";
|
||||
|
||||
const input = /* html */ `
|
||||
<input type="text" id="dribbblish-config-input-${options.key}" value="${this.get(options.key)}">
|
||||
`;
|
||||
this.addInputHTML({ ...options, input });
|
||||
input = document.createElement("input");
|
||||
input.classList.add("dribbblish-config-input");
|
||||
input.type = "text";
|
||||
input.value = this.get(options.key);
|
||||
input.addEventListener("input", (e) => {
|
||||
options.onChange(e.target.value);
|
||||
});
|
||||
} else if (options.type == "textarea") {
|
||||
if (options.defaultValue == null) options.defaultValue = "";
|
||||
|
||||
document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener("input", (e) => {
|
||||
// TODO: maybe add an validation function via `data.validate`
|
||||
this.set(options.key, e.target.value);
|
||||
options.onChange(this.get(options.key));
|
||||
input = document.createElement("textarea");
|
||||
input.classList.add("dribbblish-config-input");
|
||||
input.value = this.get(options.key);
|
||||
input.addEventListener("input", (e) => {
|
||||
options.onChange(e.target.value);
|
||||
});
|
||||
} else if (options.type == "slider") {
|
||||
// Validate
|
||||
if (options.defaultValue == null) options.defaultValue = 0;
|
||||
const val = this.get(options.key);
|
||||
if (options.data.min != null && val < options.data.min) this.set(options.key, options.data.min);
|
||||
if (options.data.max != null && val > options.data.max) this.set(options.key, options.data.max);
|
||||
if (options.data.min != null && val < options.data.min) this.set(options.key, options.data.min, options.save);
|
||||
if (options.data.max != null && val > options.data.max) this.set(options.key, options.data.max, options.save);
|
||||
|
||||
const input = /* html */ `
|
||||
<input
|
||||
type="range"
|
||||
id="dribbblish-config-input-${options.key}"
|
||||
name="${options.name}"
|
||||
min="${options.data?.min ?? "0"}"
|
||||
max="${options.data?.max ?? "100"}"
|
||||
step="${options.data?.step ?? "1"}"
|
||||
value="${this.get(options.key)}"
|
||||
tooltip="${this.get(options.key)}${options.data?.suffix ?? ""}"
|
||||
>
|
||||
`;
|
||||
this.addInputHTML({ ...options, input });
|
||||
|
||||
document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener("input", (e) => {
|
||||
document.getElementById(`dribbblish-config-input-${options.key}`).setAttribute("tooltip", `${e.target.value}${options.data?.suffix ?? ""}`);
|
||||
document.getElementById(`dribbblish-config-input-${options.key}`).setAttribute("value", e.target.value);
|
||||
this.set(options.key, Number(e.target.value));
|
||||
options.onChange(this.get(options.key));
|
||||
input = document.createElement("input");
|
||||
input.classList.add("dribbblish-config-input");
|
||||
input.type = "range";
|
||||
input.step = options.data.step ?? 1;
|
||||
input.min = options.data.min ?? 0;
|
||||
input.max = options.data.max ?? 100;
|
||||
input.value = this.get(options.key);
|
||||
input.setAttribute("tooltip", `${this.get(options.key)}${options.data.suffix ?? ""}`);
|
||||
input.addEventListener("input", (e) => {
|
||||
options.onChange(Number(e.target.value));
|
||||
$(`#dribbblish-config-input-${options.key}`).attr("tooltip", `${e.target.value}${options.data?.suffix ?? ""}`);
|
||||
});
|
||||
} else if (options.type == "time") {
|
||||
// Validate
|
||||
if (options.defaultValue == null) options.defaultValue = "00:00";
|
||||
const input = /* html */ `
|
||||
<input type="time" id="dribbblish-config-input-${options.key}" name="${options.name}" value="${this.get(options.key)}">
|
||||
`;
|
||||
this.addInputHTML({ ...options, input });
|
||||
|
||||
document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener("input", (e) => {
|
||||
document.getElementById(`dribbblish-config-input-${options.key}`).setAttribute("value", e.target.value);
|
||||
this.set(options.key, e.target.value);
|
||||
options.onChange(this.get(options.key));
|
||||
input = document.createElement("input");
|
||||
input.classList.add("dribbblish-config-input");
|
||||
input.type = "time";
|
||||
input.value = this.get(options.key);
|
||||
input.addEventListener("input", (e) => {
|
||||
options.onChange(e.target.value);
|
||||
});
|
||||
} else if (options.type == "color") {
|
||||
// Validate
|
||||
if (options.defaultValue == null) options.defaultValue = "#000000";
|
||||
const input = /* html */ `
|
||||
<input type="color" id="dribbblish-config-input-${options.key}" name="${options.name}" value="${this.get(options.key)}">
|
||||
`;
|
||||
this.addInputHTML({ ...options, input });
|
||||
|
||||
document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener("input", (e) => {
|
||||
this.set(options.key, e.target.value);
|
||||
options.onChange(this.get(options.key));
|
||||
input = document.createElement("input");
|
||||
input.classList.add("dribbblish-config-input");
|
||||
input.type = "color";
|
||||
input.value = this.get(options.key);
|
||||
input.addEventListener("input", (e) => {
|
||||
options.onChange(e.target.value);
|
||||
});
|
||||
} else {
|
||||
throw new Error(`Config Type "${options.type}" invalid`);
|
||||
}
|
||||
|
||||
this.#addInputHTML({ ...options, input: elem ?? input });
|
||||
options.input = input;
|
||||
|
||||
// Re-write internal config since some values may have changed
|
||||
this.#config[options.key] = options;
|
||||
|
||||
validate(this.get(options.key));
|
||||
$(`.dribbblish-config-item[key="${options.key}"]`).attr("changed", options.save && this.get(options.key) != options.defaultValue ? "" : null);
|
||||
|
||||
options.children.forEach((child) => this.register(child));
|
||||
|
||||
options.onAppended.call(options);
|
||||
|
|
@ -326,36 +311,17 @@ export default class ConfigMenu {
|
|||
* @param {DribbblishConfigArea} area
|
||||
*/
|
||||
registerArea(area) {
|
||||
if (area.toggleable == null) area.toggleable = true;
|
||||
/** @type {DribbblishConfigArea} */
|
||||
const defaultOptions = {
|
||||
toggleable: true,
|
||||
order: 0
|
||||
};
|
||||
area = { ...defaultOptions, ...area };
|
||||
|
||||
if (!document.querySelector(`.dribbblish-config-area[name="${area.name}"]`)) {
|
||||
const areaElem = document.createElement("div");
|
||||
areaElem.classList.add("dribbblish-config-area");
|
||||
areaElem.style.order = area.order;
|
||||
const uncollapsedAreas = JSON.parse(localStorage.getItem("dribbblish:config-areas:uncollapsed") ?? "[]");
|
||||
if (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`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -366,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;
|
||||
}
|
||||
|
|
@ -378,11 +344,12 @@ export default class ConfigMenu {
|
|||
*
|
||||
* @param {String} key
|
||||
* @param {any} val
|
||||
* @param {Boolean} [save=true] if setting should be stored in localStorage
|
||||
*/
|
||||
set(key, val) {
|
||||
set(key, val, save = true) {
|
||||
val = { type: this.#config[key].type, value: val ?? this.#config[key].defaultValue };
|
||||
this.#config[key].storageCache = JSON.stringify(val);
|
||||
localStorage.setItem(`dribbblish:config:${key}`, JSON.stringify(val));
|
||||
if (save) localStorage.setItem(`dribbblish:config:${key}`, JSON.stringify(val));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -392,19 +359,203 @@ 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);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {String} key
|
||||
* @param {Boolean} hidden
|
||||
* @private
|
||||
*/
|
||||
setHidden(key, hidden) {
|
||||
setHidden(key, hidden, search = false) {
|
||||
this.#config[key].hidden = hidden;
|
||||
document.querySelector(`.dribbblish-config-item[key="${key}"]`).setAttribute("hidden", hidden);
|
||||
const $elem = $(`.dribbblish-config-item[key="${key}"]`);
|
||||
$elem.attr(search ? "hidden-override" : "hidden", hidden ? "" : null);
|
||||
|
||||
// If element has children or a parent
|
||||
if ($elem.attr("parent") != null || $elem.attr("children") != null) {
|
||||
// Get parent of element block
|
||||
const $parent = $elem.attr("parent") != null ? $(`[children~="${key}"]`) : $elem;
|
||||
const $nextChildren = $parent.nextAll(`[parent="${$parent.attr("key")}"]`);
|
||||
|
||||
// Make parent connect on bottom when children are visible
|
||||
$parent.attr("connect-bottom", $nextChildren.filter(":not([hidden]):not([hidden-override])").length > 0 ? "" : null);
|
||||
|
||||
// Reset all children's bottom connection
|
||||
$nextChildren.each(function () {
|
||||
$(this).attr("connect-bottom", null);
|
||||
});
|
||||
// Add bottom connection to all but the last visible child
|
||||
$nextChildren
|
||||
.filter(":not([hidden]):not([hidden-override])")
|
||||
.slice(0, -1)
|
||||
.each(function () {
|
||||
$(this).attr("connect-bottom", "");
|
||||
});
|
||||
|
||||
//* NOTE: All children automatically have a top connection
|
||||
}
|
||||
}
|
||||
|
||||
setDisabled(key, disabled) {
|
||||
this.#config[key].input.disabled = disabled;
|
||||
}
|
||||
|
||||
getOptions(key) {
|
||||
return this.#config[key];
|
||||
}
|
||||
|
||||
export() {
|
||||
const obj = {
|
||||
EXPORTED_WITH: process.env.DRIBBBLISH_VERSION
|
||||
};
|
||||
Object.entries(this.#config).forEach(([key, options]) => {
|
||||
if (options.save) obj[key] = this.get(key);
|
||||
});
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String} text
|
||||
*/
|
||||
#search(text) {
|
||||
text = text.trim().toLowerCase();
|
||||
$(".dribbblish-config-area-header svg").css("display", text != "" ? "none" : "");
|
||||
$(".dribbblish-config-area").attr("search", text != "" ? "" : null);
|
||||
|
||||
const cmp = (s) => s.trim().toLowerCase().includes(text.trim().toLowerCase());
|
||||
/**
|
||||
* @param {DribbblishConfigItem} options
|
||||
*/
|
||||
function cmpOpts(options) {
|
||||
let matches = cmp(options.name) || cmp($(`.dribbblish-config-item[key="${options.key}"] .dribbblish-config-item-header label`).text());
|
||||
if (!matches && options.type == "select") matches = Object.values(options.data).some(cmp);
|
||||
return matches;
|
||||
}
|
||||
|
||||
// Check every item
|
||||
for (const [key, options] of Object.entries(this.#config)) {
|
||||
this.setHidden(key, false, true);
|
||||
if (text == "") continue;
|
||||
|
||||
const show = cmpOpts(options);
|
||||
this.setHidden(key, !show, true);
|
||||
}
|
||||
|
||||
// Check every item's children and show it when any child is visible
|
||||
for (const [key, options] of Object.entries(this.#config)) {
|
||||
if (options.children.length != 0 && $(`.dribbblish-config-item[parent="${options.key}"]`).filter(":not([hidden]):not([hidden-override])").length != 0) this.setHidden(key, false, true);
|
||||
}
|
||||
|
||||
// Hide areas without visible children
|
||||
for (const area of $(".dribbblish-config-area").toArray()) {
|
||||
const $area = $(area);
|
||||
const itemsVisible = $area.children(".dribbblish-config-area-items").children(":not([hidden]):not([hidden-override])").length;
|
||||
$area.css("display", text != "" && itemsVisible == 0 ? "none" : "");
|
||||
}
|
||||
|
||||
$(`.dribbblish-config-item`).filter(":not([hidden]):not([hidden-override])").length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {DribbblishConfigItem} options
|
||||
*/
|
||||
#addInputHTML(options) {
|
||||
if (!document.querySelector(`.dribbblish-config-area[name="${options.area.name}"]`)) this.registerArea(options.area);
|
||||
|
||||
const parent = document.querySelector(`.dribbblish-config-area[name="${options.area.name}"] .dribbblish-config-area-items`);
|
||||
|
||||
const elem = document.createElement("div");
|
||||
if (options.order != 0) elem.style.order = options.order;
|
||||
elem.classList.add("dribbblish-config-item");
|
||||
elem.setAttribute("key", options.key);
|
||||
elem.setAttribute("type", options.type);
|
||||
if (options.hidden) elem.setAttribute("hidden", true);
|
||||
if (options.parent) elem.setAttribute("parent", options.parent);
|
||||
if (options.children.length > 0) elem.setAttribute("children", options.children.map((c) => c.key).join(" "));
|
||||
elem.innerHTML = /* html */ `
|
||||
${
|
||||
options.name != null && options.description != null
|
||||
? /* html */ `
|
||||
<div class="dribbblish-config-item-header">
|
||||
<h2 class="x-settings-title main-type-cello" as="h2" empty="${options.name == null}">${options.name}</h2>
|
||||
<label class="main-type-mesto" empty="${options.description == null}" markdown>${renderMD(options.description)}</label>
|
||||
</div>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
<div class="dribbblish-config-item-input">
|
||||
<label class="x-toggle-wrapper x-settings-secondColumn"></label>
|
||||
</div>
|
||||
`;
|
||||
|
||||
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
80
src/js/Dribbblish.js
Normal 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
98
src/js/Folders.js
Normal 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
124
src/js/Icons.js
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
import { defaults } from "./Util";
|
||||
import { parseSync as parseSVG, stringify as stringifySVG } from "svgson";
|
||||
|
||||
export default class Icons {
|
||||
/** @typedef {"custom" | "material:baseline" | "material:outline" | "material:round" | "material:sharp" | "material:twotone"} IconStyle */
|
||||
|
||||
/**
|
||||
* @typedef {Object} IconOptions
|
||||
* @property {IconStyle} [style="round"]
|
||||
* @property {String} [className=""]
|
||||
* @property {Number} [size=16]
|
||||
* @property {Number} [scale=1]
|
||||
* @property {String} [fill="currentColor"]
|
||||
* @property {Boolean} [base64=false]
|
||||
*/
|
||||
|
||||
/** @type {Object.<String, Object.<IconStyle, String>>} */
|
||||
#icons;
|
||||
|
||||
constructor(icons) {
|
||||
this.#icons = icons ?? process.env.DRIBBBLISH_ICONS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String} name
|
||||
* @param {IconStyle} style
|
||||
* @returns {String}
|
||||
*/
|
||||
getRawSVG(name, style) {
|
||||
if (style == null) style = this.#getDefaultStyle(name);
|
||||
if (!this.#icons.hasOwnProperty(name)) throw new Error(`Icon "${name}" does not exist`);
|
||||
|
||||
if (!this.#icons[name].hasOwnProperty(style)) {
|
||||
const styles = Object.keys(this.#icons[name])
|
||||
.map((s) => `"${s}"`)
|
||||
.join(", ");
|
||||
throw new Error(`Icon "${name}" does not have style "${style}". It is available in styles [${styles}].`);
|
||||
}
|
||||
|
||||
return this.#icons[name][style];
|
||||
}
|
||||
|
||||
getAvailableStyles(name) {
|
||||
if (!this.#icons.hasOwnProperty(name)) throw new Error(`Icon "${name}" does not exist`);
|
||||
return Object.keys(this.#icons[name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String} name
|
||||
* @returns
|
||||
*/
|
||||
#getDefaultStyle(name) {
|
||||
const styles = this.getAvailableStyles(name);
|
||||
for (const s of ["custom", "material:round"]) {
|
||||
if (styles.includes(s)) return s;
|
||||
}
|
||||
|
||||
return styles[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String} name icon name lowercase with dashes like `ac-unit`
|
||||
* @param {IconOptions} options
|
||||
* @see https://fonts.google.com/icons?selected=Material+Icons
|
||||
* @returns
|
||||
*/
|
||||
get(name, options) {
|
||||
/** @type {IconOptions} */
|
||||
const defaultOptions = {
|
||||
style: this.#getDefaultStyle(name),
|
||||
className: "",
|
||||
size: 16,
|
||||
scale: 1,
|
||||
fill: "currentColor",
|
||||
base64: false
|
||||
};
|
||||
options = defaults(options, defaultOptions);
|
||||
|
||||
const svg = parseSVG(this.getRawSVG(name, options.style));
|
||||
|
||||
// Add general / required attributes
|
||||
svg.attributes["icon-type"] = "dribbblish";
|
||||
svg.attributes["icon-name"] = name;
|
||||
svg.attributes["icon-style"] = options.style;
|
||||
svg.attributes.fill = options.fill;
|
||||
svg.attributes.width = options.size;
|
||||
svg.attributes.height = options.size;
|
||||
|
||||
// Add className
|
||||
if (options.className != "") svg.attributes.class = options.className;
|
||||
|
||||
// Add Styles
|
||||
const styles = {};
|
||||
if (options.scale != 1) {
|
||||
styles["transform"] = `scale(${options.scale})`;
|
||||
styles["transform-origin"] = "center";
|
||||
}
|
||||
const styleStr = Object.entries(styles)
|
||||
.map(([prop, val]) => `${prop}: ${val};`)
|
||||
.join(" ");
|
||||
svg.children = svg.children.map((child) => {
|
||||
if (styleStr != "") child.attributes.style = styleStr;
|
||||
return child;
|
||||
});
|
||||
|
||||
// Add title
|
||||
if (options.title != null) {
|
||||
svg.children.push({
|
||||
name: "title",
|
||||
type: "element",
|
||||
value: "",
|
||||
children: [{ name: "", type: "text", value: options.title, attributes: {}, children: [] }]
|
||||
});
|
||||
}
|
||||
|
||||
if (options.base64) {
|
||||
return `data:image/svg+xml;base64,${Buffer.from(stringifySVG(svg)).toString("base64")}`;
|
||||
} else {
|
||||
return stringifySVG(svg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const icons = new Icons();
|
||||
90
src/js/Info.js
Normal file
90
src/js/Info.js
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
import $ from "jquery";
|
||||
import { icons } from "./Icons";
|
||||
|
||||
import { waitForElement } from "./Util";
|
||||
|
||||
export default class Info {
|
||||
/**
|
||||
* @typedef {Object} DribbblishInfo
|
||||
* @property {String} [text]
|
||||
* @property {String} [tooltip]
|
||||
* @property {String} [icon] svg string or icon name
|
||||
* @property {DribbblishInfoColor} [color]
|
||||
* @property {Number} [order=0] order < 0 = More to the Left | order > 0 = More to the Right
|
||||
* @property {onClick} [onClick]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} DribbblishInfoColor
|
||||
* @property {String} [fg]
|
||||
* @property {String} [bg]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @callback onClick
|
||||
* @returns {void}
|
||||
*/
|
||||
|
||||
/** @type {HTMLDivElement} */
|
||||
#container;
|
||||
|
||||
/** @type {MarkdownIt} */
|
||||
#md;
|
||||
|
||||
/** @type {Boolean} */
|
||||
#ready = false;
|
||||
|
||||
constructor() {
|
||||
waitForElement([".main-topBar-container", ".main-userWidget-box"], ([topBarContainer, userWidget]) => {
|
||||
this.#container = document.createElement("div");
|
||||
this.#container.id = "dribbblish-info-container";
|
||||
topBarContainer.insertBefore(this.#container, userWidget);
|
||||
|
||||
this.#ready = true;
|
||||
});
|
||||
}
|
||||
|
||||
isReady() {
|
||||
return this.#ready;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String} key
|
||||
* @param {DribbblishInfo} info
|
||||
*/
|
||||
set(key, info) {
|
||||
if (!this.#ready) {
|
||||
setTimeout(() => this.set(key, info), 200);
|
||||
return;
|
||||
}
|
||||
|
||||
this.remove(key);
|
||||
if (info == null) return;
|
||||
if (info.text == null && info.icon == null) throw new Error("invalid info");
|
||||
|
||||
const elem = document.createElement("div");
|
||||
elem.classList.add("dribbblish-info-item");
|
||||
elem.addEventListener("click", info.onClick);
|
||||
elem.setAttribute("key", key);
|
||||
if (info.text == null) elem.setAttribute("icon-only", "");
|
||||
if (info.tooltip != null) elem.setAttribute("title", info.tooltip);
|
||||
if (info.onClick != null) elem.setAttribute("clickable", "");
|
||||
if (info.color != null) {
|
||||
const { fg, bg } = info.color;
|
||||
if (fg != null) elem.style.color = fg;
|
||||
if (bg != null) elem.style.backgroundColor = bg;
|
||||
}
|
||||
if (info.order != 0) elem.style.order = info.order;
|
||||
if (!info.icon.startsWith("<svg")) info.icon = icons.get(info.icon, { size: 18 });
|
||||
elem.innerHTML = `${info.text ?? ""}${info.icon ?? ""}`;
|
||||
|
||||
this.#container.appendChild(elem);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String} key
|
||||
*/
|
||||
remove(key) {
|
||||
$(this.#container).find(`.dribbblish-info-item[key="${key}"]`).remove();
|
||||
}
|
||||
}
|
||||
18
src/js/Loader.js
Normal file
18
src/js/Loader.js
Normal 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
68
src/js/Overlay.js
Normal 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
13
src/js/SassUtil.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
export function lightOffset(n, offset) {
|
||||
return `calc(${n} + ${offset} * var(--is_light))`;
|
||||
}
|
||||
|
||||
export function spiceColor(key, alpha = 1, _lightOffset = 0) {
|
||||
if (alpha == 1) {
|
||||
return `var(--spice-${key})`;
|
||||
} else if (_lightOffset == 0) {
|
||||
return `rgba(var(--spice-rgb-${key}), ${alpha})`;
|
||||
} else {
|
||||
return `rgba(var(--spice-rgb-${key}), ${lightOffset(alpha, _lightOffset)})`;
|
||||
}
|
||||
}
|
||||
95
src/js/Util.js
Normal file
95
src/js/Util.js
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import MarkdownIt from "markdown-it";
|
||||
import MarkdownItAttrs from "markdown-it-attrs";
|
||||
import defaultsDeep from "lodash.defaultsdeep";
|
||||
import { default as _debounce } from "lodash.debounce";
|
||||
|
||||
/**
|
||||
* @callback waitForElCb
|
||||
* @param {HTMLElement[]} queries
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {String[]} els
|
||||
* @param {waitForElCb} cb
|
||||
* @param {Number} [tries=100]
|
||||
* @param {Number} [interval=300]
|
||||
*/
|
||||
export function waitForElement(els, cb, tries = 100, interval = 300) {
|
||||
const queries = els.map((el) => document.querySelector(el));
|
||||
if (queries.every((a) => a)) {
|
||||
cb(queries);
|
||||
} else if (tries > 0) {
|
||||
setTimeout(waitForElement, interval, els, cb, --tries);
|
||||
}
|
||||
}
|
||||
|
||||
export function copyToClipboard(text) {
|
||||
var input = document.createElement("textarea");
|
||||
input.style.display = "fixed";
|
||||
input.innerHTML = text;
|
||||
document.body.appendChild(input);
|
||||
input.select();
|
||||
var result = document.execCommand("copy");
|
||||
document.body.removeChild(input);
|
||||
return result;
|
||||
}
|
||||
|
||||
export function capitalizeFirstLetter(string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
|
||||
export function getClosestToNum(arr, num) {
|
||||
return arr.reduce((prev, curr) => (Math.abs(curr - num) < Math.abs(prev - num) ? curr : prev));
|
||||
}
|
||||
|
||||
export function renderMD(src, env) {
|
||||
const md = MarkdownIt("commonmark", {
|
||||
html: true,
|
||||
breaks: true,
|
||||
linkify: true,
|
||||
typographer: true
|
||||
});
|
||||
md.use(MarkdownItAttrs);
|
||||
|
||||
return md.render(src, env);
|
||||
}
|
||||
|
||||
export function htmlToNode(htmlStr) {
|
||||
var div = document.createElement("div");
|
||||
div.innerHTML = htmlStr.trim();
|
||||
return div.firstChild;
|
||||
}
|
||||
|
||||
export function getRandomArbitrary(min, max) {
|
||||
return Math.random() * (max - min) + min;
|
||||
}
|
||||
|
||||
export function getRandomInt(min, max) {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {T[]} arr
|
||||
* @returns {T}
|
||||
*/
|
||||
export function randomFromArray(arr) {
|
||||
return arr[Math.floor(Math.random() * arr.length)];
|
||||
}
|
||||
|
||||
/** @type {_debounce} */
|
||||
export function debounce(fn, wait, opts) {
|
||||
return _debounce(fn, wait, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {...Object} defaults
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function defaults(options, ...defaults) {
|
||||
return defaultsDeep(options, ...defaults);
|
||||
}
|
||||
1467
src/js/main.js
1467
src/js/main.js
File diff suppressed because it is too large
Load diff
|
|
@ -3,7 +3,7 @@ $colors: (
|
|||
subtext: #f0f0f0,
|
||||
sidebar-text: #ffffff,
|
||||
main: #000000,
|
||||
sidebar: #1ed760,
|
||||
sidebar: #121212,
|
||||
player: #000000,
|
||||
card: #000000,
|
||||
shadow: #202020,
|
||||
|
|
@ -17,14 +17,6 @@ $colors: (
|
|||
misc: #bfbfbf
|
||||
);
|
||||
|
||||
// Set initial Vars
|
||||
:root {
|
||||
@each $key, $color in $colors {
|
||||
--spice-#{$key}: #{$color};
|
||||
--spice-#{$key}-rgb: #{red($color)}, #{green($color)}, #{blue($color)};
|
||||
}
|
||||
}
|
||||
|
||||
// Set Transitions
|
||||
@each $key, $color in $colors {
|
||||
@property --spice-#{$key} {
|
||||
|
|
@ -33,7 +25,7 @@ $colors: (
|
|||
inherits: true;
|
||||
}
|
||||
|
||||
@property --spice-#{$key}-rgb {
|
||||
@property --spice-rgb-#{$key} {
|
||||
syntax: "<number>, <number>, <number>";
|
||||
initial-value: #{red($color)}, #{green($color)}, #{blue($color)};
|
||||
inherits: true;
|
||||
|
|
@ -46,18 +38,9 @@ $props-to-transition: ("sidebar", "main", "text", "button");
|
|||
|
||||
@each $key in $props-to-transition {
|
||||
$props: append($props, --spice-#{$key}, comma);
|
||||
$props: append($props, --spice-#{$key}-rgb, comma);
|
||||
$props: append($props, --spice-rgb-#{$key}, comma);
|
||||
}
|
||||
|
||||
transition: all var(--song-transition-speed) linear;
|
||||
transition-property: $props;
|
||||
}
|
||||
|
||||
// Color Function
|
||||
@function spiceColor($key, $alpha: 1) {
|
||||
@if $alpha == 1 {
|
||||
@return var(--spice-#{$key});
|
||||
} @else {
|
||||
@return rgba(var(--spice-#{$key}-rgb), $alpha);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,50 +9,76 @@
|
|||
|
||||
&[active] {
|
||||
display: flex;
|
||||
|
||||
& ~ #main::after {
|
||||
z-index: 4;
|
||||
position: absolute;
|
||||
content: "";
|
||||
inset: 0px;
|
||||
backdrop-filter: blur(3px) brightness(60%);
|
||||
}
|
||||
}
|
||||
|
||||
.dribbblish-config-container {
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
width: clamp(500px, 50%, 650px);
|
||||
background-color: spiceColor("main", 0.9);
|
||||
backdrop-filter: blur(3px);
|
||||
padding: 20px 15px;
|
||||
border-radius: var(--main-corner-radius);
|
||||
box-shadow: 0 0 10px 3px #0000003b;
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
gap: 8px;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@include spiceGlass();
|
||||
|
||||
> h2 {
|
||||
font-size: 32px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.dribbblish-config-close {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
padding: 0px;
|
||||
top: 24px;
|
||||
right: 24px;
|
||||
}
|
||||
|
||||
.dribbblish-config-search {
|
||||
// TODO: improve styles
|
||||
}
|
||||
|
||||
.dribbblish-config-areas {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
gap: 8px;
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
padding: 0px 50px;
|
||||
padding: 0px 26px;
|
||||
|
||||
.dribbblish-config-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
&[collapsed] {
|
||||
&[collapsed]:not([search]) {
|
||||
overflow: hidden;
|
||||
min-height: 38px; //for some reason height alone isn't enough
|
||||
height: 38px;
|
||||
|
||||
> h2 svg {
|
||||
transform: rotate(270deg);
|
||||
.dribbblish-config-area-header {
|
||||
svg {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
&:hover svg {
|
||||
transform: rotate(0deg) scale(1.1);
|
||||
}
|
||||
|
||||
&:active svg {
|
||||
transform: rotate(0deg) scale(0.9);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -63,6 +89,7 @@
|
|||
.dribbblish-config-area-header {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
width: fit-content;
|
||||
height: 38px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
|
|
@ -74,65 +101,146 @@
|
|||
padding: 0px;
|
||||
height: 100%;
|
||||
stroke-width: 2px;
|
||||
transform: rotate(90deg);
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
&:hover svg {
|
||||
transform: rotate(180deg) scale(1.1);
|
||||
}
|
||||
|
||||
&:active svg {
|
||||
transform: rotate(180deg) scale(0.9);
|
||||
}
|
||||
}
|
||||
|
||||
.dribbblish-config-area-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
|
||||
.dribbblish-config-item {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: min-content;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
grid-template-rows: auto auto;
|
||||
gap: 5px 10px;
|
||||
grid-template-areas: "header input" "description input";
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 16px;
|
||||
user-select: text;
|
||||
|
||||
&[parent] {
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
&[hidden="true"] {
|
||||
&[hidden],
|
||||
&[hidden-override] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
> {
|
||||
.x-settings-title {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
grid-area: header;
|
||||
margin: 0px;
|
||||
height: min-content;
|
||||
position: relative;
|
||||
&::before {
|
||||
z-index: -1;
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0px;
|
||||
border-radius: var(--main-corner-radius);
|
||||
background-color: spiceColor("subtext", 0.03, 0.04);
|
||||
}
|
||||
|
||||
&[parent]::before {
|
||||
top: -8px;
|
||||
border-top-left-radius: 0px;
|
||||
border-top-right-radius: 0px;
|
||||
}
|
||||
|
||||
&[connect-bottom]::before {
|
||||
border-bottom-left-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
}
|
||||
|
||||
&[invalid] {
|
||||
&::before {
|
||||
border: 2px solid rgba(red, 0.8);
|
||||
}
|
||||
|
||||
.dribbblish-config-item-input::before {
|
||||
content: var(--validation-error);
|
||||
margin-right: 8px;
|
||||
color: rgba(red, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
&[changed] {
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
width: 5px;
|
||||
background-color: spiceColor("text");
|
||||
border-top-left-radius: var(--main-corner-radius);
|
||||
border-bottom-left-radius: var(--main-corner-radius);
|
||||
}
|
||||
|
||||
&.no-desc {
|
||||
bottom: -10px;
|
||||
&[parent]::after {
|
||||
top: -4px;
|
||||
border-top-left-radius: 0px;
|
||||
}
|
||||
|
||||
&[connect-bottom]::after {
|
||||
bottom: -4px;
|
||||
border-bottom-left-radius: 0px;
|
||||
}
|
||||
|
||||
.dribbblish-config-item-reset {
|
||||
display: block !important;
|
||||
}
|
||||
}
|
||||
|
||||
.dribbblish-config-item-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 5px;
|
||||
|
||||
> {
|
||||
& [empty="true"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dribbblish-config-item-reset {
|
||||
padding: 0px;
|
||||
color: spiceColor("text");
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
.x-settings-title {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
align-items: center;
|
||||
grid-area: header;
|
||||
margin: 0px;
|
||||
height: min-content;
|
||||
position: relative;
|
||||
bottom: 0px;
|
||||
|
||||
.dribbblish-config-item-reset {
|
||||
display: none;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
padding: 0px;
|
||||
color: spiceColor("text");
|
||||
}
|
||||
}
|
||||
|
||||
.main-type-mesto {
|
||||
grid-area: description;
|
||||
height: min-content;
|
||||
color: spiceColor("subtext");
|
||||
line-height: calc(1em + 6px); // To have line gaps
|
||||
}
|
||||
|
||||
.x-settings-secondColumn {
|
||||
grid-area: input;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.main-type-mesto {
|
||||
grid-area: description;
|
||||
height: min-content;
|
||||
color: spiceColor("subtext");
|
||||
}
|
||||
|
||||
.x-settings-secondColumn {
|
||||
grid-area: input;
|
||||
}
|
||||
.dribbblish-config-item-input {
|
||||
display: flex;
|
||||
min-width: fit-content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -144,6 +252,5 @@
|
|||
position: absolute;
|
||||
content: "";
|
||||
inset: 0px;
|
||||
backdrop-filter: blur(3px) brightness(60%);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
41
src/styles/ConnectDeviceList.scss
Normal file
41
src/styles/ConnectDeviceList.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
32
src/styles/CustomAppTabBar.scss
Normal file
32
src/styles/CustomAppTabBar.scss
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
.main-topBar-topbarContent {
|
||||
height: 100%;
|
||||
|
||||
& > nav[class*="-tabBar"][class*="-tabBar-nav"] {
|
||||
height: 100%;
|
||||
|
||||
& > ul {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
height: 100%;
|
||||
|
||||
li[class*="-tabBar-headerItem"] {
|
||||
height: 100%;
|
||||
|
||||
& > a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0px;
|
||||
height: 100%;
|
||||
padding: 0px 8px;
|
||||
border-radius: var(--sidebar-icons-border-radius);
|
||||
|
||||
& > span {
|
||||
line-height: 13px;
|
||||
@include spiceFont("glue", 14px, "Medium");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
57
src/styles/Fonts.scss
Normal file
57
src/styles/Fonts.scss
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
// GoogleSans: https://www.cufonfonts.com/font/google-sans
|
||||
// Roboto: https://fonts.google.com/specimen/Roboto
|
||||
$font-names: (
|
||||
"glue": "GoogleSans",
|
||||
"info": "Roboto"
|
||||
);
|
||||
$font-weights: (
|
||||
"Thin": 100,
|
||||
"Light": 300,
|
||||
"Regular": 400,
|
||||
"Medium": 500,
|
||||
"Bold": 700,
|
||||
"Black": 900
|
||||
);
|
||||
|
||||
// add @font-face rules
|
||||
@each $prefix, $font in $font-names {
|
||||
@each $style, $weight in $font-weights {
|
||||
@font-face {
|
||||
font-family: $font;
|
||||
font-weight: $weight;
|
||||
font-style: normal;
|
||||
src: url(font64("#{$font}-#{$style}.ttf")) format("truetype");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// spiceFont mixin
|
||||
@mixin spiceFont($type: "glue", $size: null, $weight: null) {
|
||||
@if map-has-key($font-names, $type) {
|
||||
font-family: var(--#{$type}-font-family);
|
||||
} @else {
|
||||
@error "$type is invalid";
|
||||
}
|
||||
|
||||
@if $size != null {
|
||||
font-size: $size;
|
||||
}
|
||||
|
||||
@if $weight != null {
|
||||
@if type-of($weight) == "number" and unit($weight) == "" {
|
||||
font-weight: $weight;
|
||||
} @else if map-has-key($font-weights, $weight) {
|
||||
font-weight: #{map-get($font-weights, $weight)};
|
||||
} @else {
|
||||
@error "$weight is invalid";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set font variables
|
||||
:root {
|
||||
--glue-font-family: #{map-get($font-names, "glue")}, #{map-get($font-names, "info")}, spotify-circular, spotify-circular-cyrillic, spotify-circular-arabic, spotify-circular-hebrew, Helvetica Neue, helvetica, arial, Hiragino Kaku Gothic Pro, Meiryo, MS Gothic, sans-serif;
|
||||
--info-font-family: #{map-get($font-names, "info")}, spotify-circular, spotify-circular-cyrillic, spotify-circular-arabic, spotify-circular-hebrew, Helvetica Neue, helvetica, arial, Hiragino Kaku Gothic Pro, Meiryo, MS Gothic, sans-serif;
|
||||
font-family: var(--glue-font-family);
|
||||
letter-spacing: normal;
|
||||
}
|
||||
7
src/styles/Icons.scss
Normal file
7
src/styles/Icons.scss
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
svg[icon-type="dribbblish"] {
|
||||
overflow: visible;
|
||||
|
||||
// &[icon-style="custom"] {}
|
||||
|
||||
// &[icon-style^="material:"] {}
|
||||
}
|
||||
32
src/styles/Info.scss
Normal file
32
src/styles/Info.scss
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#dribbblish-info-container {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
height: 100%;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
& > div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
height: 100%;
|
||||
padding: 0px 8px;
|
||||
color: spiceColor("sidebar-text");
|
||||
background-color: spiceColor("sidebar");
|
||||
border-radius: var(--sidebar-icons-border-radius);
|
||||
line-height: 13px;
|
||||
@include spiceFont("glue", 14px, "Medium");
|
||||
|
||||
&[clickable] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&[icon-only] {
|
||||
padding: 0px;
|
||||
aspect-ratio: 1/1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +1,109 @@
|
|||
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;
|
||||
|
||||
// &:hover,
|
||||
// &:active {
|
||||
// background-color: spiceColor("selected-row", 0.6) !important;
|
||||
// }
|
||||
|
||||
// span {
|
||||
// color: $color !important;
|
||||
// }
|
||||
// }
|
||||
|
||||
// ? the `:not(...)` is to fix #137
|
||||
&-tertiary:not(.main-entityHeader-titleButton) {
|
||||
$color: spiceColor("subtext");
|
||||
color: $color;
|
||||
background-color: spiceColor("selected-row", 0.2, 0.05) !important;
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
background-color: spiceColor("selected-row", 0.6) !important;
|
||||
}
|
||||
|
||||
span {
|
||||
color: $color !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
// Checkbox
|
||||
.x-toggle-indicatorWrapper {
|
||||
background-color: spiceColor("subtext", 0.1);
|
||||
|
||||
input:not(:disabled):hover ~ & {
|
||||
background-color: spiceColor("subtext", 0.15) !important;
|
||||
}
|
||||
|
||||
input:checked ~ & {
|
||||
background-color: spiceColor("selected-row", 0.4) !important;
|
||||
}
|
||||
|
||||
input:not(:disabled):hover:checked ~ & {
|
||||
background-color: spiceColor("selected-row", 0.6) !important;
|
||||
}
|
||||
}
|
||||
|
||||
textarea,
|
||||
select,
|
||||
input {
|
||||
background-color: spiceColor("selected-row", 0.4) !important;
|
||||
border-radius: 4px !important;
|
||||
padding: 6px 10px 6px 48px;
|
||||
color: spiceColor("text") !important;
|
||||
color: spiceColor("subtext") !important;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
padding: 6px 10px;
|
||||
}
|
||||
|
||||
select > option {
|
||||
background: spiceColor("main") !important;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 6px 10px 6px 48px;
|
||||
|
||||
&[type="checkbox"] {
|
||||
opacity: 0 !important;
|
||||
}
|
||||
|
||||
&[type="range"] {
|
||||
|
|
@ -42,30 +125,28 @@ textarea {
|
|||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
z-index: 9999;
|
||||
content: attr(tooltip);
|
||||
position: absolute;
|
||||
min-width: 50px;
|
||||
top: -10px;
|
||||
left: 50%;
|
||||
transform: translateX(calc(-50%));
|
||||
padding: 0 5px;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
color: spiceColor("sidebar-text");
|
||||
background-color: spiceColor("button");
|
||||
transition: opacity 0.25s ease;
|
||||
opacity: 0;
|
||||
}
|
||||
&[tooltip] {
|
||||
&::after {
|
||||
z-index: 9999;
|
||||
content: attr(tooltip);
|
||||
position: absolute;
|
||||
min-width: 50px;
|
||||
top: -22px;
|
||||
left: 50%;
|
||||
transform: translateX(calc(-50%));
|
||||
padding: 0 5px;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
color: spiceColor("sidebar-text");
|
||||
background-color: spiceColor("button");
|
||||
transition: opacity 0.25s ease;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&:hover::after,
|
||||
&:active::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
&:hover::after,
|
||||
&:active::after {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&::-webkit-slider-runnable-track {
|
||||
|
|
@ -78,37 +159,21 @@ textarea {
|
|||
|
||||
&[type="number"],
|
||||
&[type="text"],
|
||||
&[type="search"],
|
||||
&[type="time"] {
|
||||
height: 32px;
|
||||
border: none;
|
||||
border-radius: 4px !important;
|
||||
padding: 0px 10px;
|
||||
color: spiceColor("subtext") !important;
|
||||
}
|
||||
|
||||
&[type="time"] {
|
||||
&::-webkit-calendar-picker-indicator {
|
||||
filter: invert(calc(1 - var(--is_light)));
|
||||
filter: invert(var(--is_dark));
|
||||
}
|
||||
}
|
||||
|
||||
&[type="color"] {
|
||||
position: relative;
|
||||
padding: 0px;
|
||||
border: none;
|
||||
|
||||
&::before {
|
||||
z-index: -1;
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: -5px;
|
||||
border-radius: 4px;
|
||||
background-color: 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
1
src/styles/Lyrics.scss
Normal file
|
|
@ -0,0 +1 @@
|
|||
// TODO: improve default lyrics styles when the classes are mapped `see: https://github.com/JulienMaille/dribbblish-dynamic-theme/issues/144`
|
||||
21
src/styles/Markdown.scss
Normal file
21
src/styles/Markdown.scss
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
*[markdown] {
|
||||
ol,
|
||||
ul {
|
||||
list-style: inside;
|
||||
|
||||
> li::marker {
|
||||
content: "• ";
|
||||
font-weight: 900;
|
||||
}
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: spiceColor("subtext", 0.1);
|
||||
padding: 0px 5px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.muted {
|
||||
color: spiceColor("subtext", 0.5, 0.2);
|
||||
}
|
||||
}
|
||||
67
src/styles/Modals.scss
Normal file
67
src/styles/Modals.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
42
src/styles/Overlay.scss
Normal 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
16
src/styles/Util.scss
Normal 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();
|
||||
}
|
||||
|
|
@ -1,13 +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;
|
||||
|
|
@ -17,66 +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));
|
||||
|
||||
@font-face {
|
||||
font-family: "Google Sans Display";
|
||||
src: url("glue-resources/fonts/GoogleSansDisplayRegular.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Google Sans Display";
|
||||
src: url("glue-resources/fonts/GoogleSansDisplayMedium.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Roboto";
|
||||
src: url("glue-resources/fonts/Roboto.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Roboto";
|
||||
src: url("glue-resources/fonts/RobotoMedium.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
body {
|
||||
--glue-font-family: "Google Sans Display", "Roboto", 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: "Roboto", spotify-circular, spotify-circular-cyrillic, spotify-circular-arabic, spotify-circular-hebrew, Helvetica Neue, helvetica, arial, Hiragino Kaku Gothic Pro, Meiryo, MS Gothic, sans-serif;
|
||||
font-family: var(--glue-font-family);
|
||||
letter-spacing: normal;
|
||||
}
|
||||
|
||||
.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,
|
||||
|
|
@ -84,7 +69,7 @@ body {
|
|||
.main-type-ballad,
|
||||
.main-type-balladBold,
|
||||
.main-type-canon {
|
||||
font-family: var(--info-font-family);
|
||||
@include spiceFont("info");
|
||||
letter-spacing: normal;
|
||||
}
|
||||
|
||||
|
|
@ -93,7 +78,7 @@ body {
|
|||
}
|
||||
|
||||
.lyrics-lyricsContainer-LyricsLine {
|
||||
font-family: var(--glue-font-family);
|
||||
@include spiceFont("glue");
|
||||
letter-spacing: -0.03em !important;
|
||||
}
|
||||
|
||||
|
|
@ -106,6 +91,24 @@ body {
|
|||
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,
|
||||
|
|
@ -120,15 +123,6 @@ body {
|
|||
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;
|
||||
|
|
@ -137,18 +131,31 @@ body {
|
|||
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,
|
||||
|
|
@ -163,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;
|
||||
|
|
@ -306,17 +318,8 @@ span.artist-artistVerifiedBadge-badge svg > path:last-of-type {
|
|||
|
||||
.main-userWidget-box {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.main-userWidget-box.update-avail {
|
||||
padding-right: 10px;
|
||||
background-color: spiceColor("subtext", 0.1) !important;
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&[data-context-menu-open="true"] {
|
||||
background-color: spiceColor("subtext", 0.15) !important;
|
||||
}
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.main-avatar-avatar.main-avatar-withBadge:after {
|
||||
|
|
@ -331,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;
|
||||
|
|
@ -346,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 {
|
||||
|
|
@ -408,8 +428,9 @@ html {
|
|||
|
||||
.main-rootlist-statusIcons {
|
||||
z-index: 1;
|
||||
inset: 0;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
left: calc(var(--indentation) * var(--left-sidebar-item-indentation-width));
|
||||
margin-left: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -420,6 +441,10 @@ html {
|
|||
display: none;
|
||||
}
|
||||
|
||||
html.sidebar-hide-text & {
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
.main-playButton-button {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
|
@ -508,29 +533,17 @@ html.sidebar-hide-text .GlueDropTarget span {
|
|||
display: none;
|
||||
}
|
||||
|
||||
.x-toggle-indicatorWrapper {
|
||||
background-color: transparent;
|
||||
backdrop-filter: invert(0.25);
|
||||
}
|
||||
.main-topBar-historyButtons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
input:checked ~ .x-toggle-indicatorWrapper {
|
||||
background-color: spiceColor("button", 0.4);
|
||||
}
|
||||
|
||||
input:hover:checked ~ .x-toggle-indicatorWrapper {
|
||||
background-color: spiceColor("button", 0.5) !important;
|
||||
}
|
||||
|
||||
input:hover:not([disabled]):not(:active) ~ .x-toggle-indicatorWrapper {
|
||||
background-color: transparent;
|
||||
backdrop-filter: invert(0.4);
|
||||
}
|
||||
|
||||
/** */
|
||||
.main-topBar-historyButtons .main-topBar-button {
|
||||
background-color: unset;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
.main-topBar-button {
|
||||
margin: 0px;
|
||||
background-color: unset;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.main-topBar-historyButtons svg {
|
||||
|
|
@ -546,13 +559,9 @@ input:hover:not([disabled]):not(:active) ~ .x-toggle-indicatorWrapper {
|
|||
|
||||
.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 {
|
||||
|
|
@ -567,6 +576,7 @@ input:hover:not([disabled]):not(:active) ~ .x-toggle-indicatorWrapper {
|
|||
|
||||
.progress-bar {
|
||||
--progress-bar-height: 2px;
|
||||
--is-active-fg-color: #{spiceColor("button-active")};
|
||||
--fg-color: #{spiceColor("button")};
|
||||
--bg-color: #{spiceColor("text", 0.2)};
|
||||
}
|
||||
|
|
@ -582,12 +592,18 @@ input:hover:not([disabled]):not(:active) ~ .x-toggle-indicatorWrapper {
|
|||
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 {
|
||||
|
|
@ -597,15 +613,11 @@ input:hover:not([disabled]):not(:active) ~ .x-toggle-indicatorWrapper {
|
|||
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 {
|
||||
|
|
@ -654,20 +666,23 @@ input:hover:not([disabled]):not(:active) ~ .x-toggle-indicatorWrapper {
|
|||
color: spiceColor("subtext");
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
&:active {
|
||||
color: spiceColor("text");
|
||||
}
|
||||
}
|
||||
|
||||
#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;
|
||||
}
|
||||
|
|
@ -698,7 +713,7 @@ input:hover:not([disabled]):not(:active) ~ .x-toggle-indicatorWrapper {
|
|||
padding: 0 12px;
|
||||
}
|
||||
|
||||
img.playlist-picture {
|
||||
.playlist-picture {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
flex: 0 0 32px;
|
||||
|
|
@ -706,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 {
|
||||
|
|
@ -797,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 */
|
||||
|
|
@ -820,18 +842,6 @@ li.GlueDropTarget {
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
#dribbblish-update {
|
||||
color: spiceColor("button-active");
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
letter-spacing: normal;
|
||||
line-height: 16px;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/** Rearrange player bar */
|
||||
.main-nowPlayingBar-left {
|
||||
order: 1;
|
||||
|
|
@ -869,7 +879,6 @@ li.GlueDropTarget {
|
|||
.main-nowPlayingWidget-nowPlaying {
|
||||
justify-content: center;
|
||||
|
||||
// Workaround for #73 (https://github.com/JulienMaille/dribbblish-dynamic-theme/issues/73)
|
||||
& button {
|
||||
color: spiceColor("subtext");
|
||||
|
||||
|
|
@ -934,9 +943,9 @@ li.GlueDropTarget {
|
|||
|
||||
.main-collectionLinkButton-collectionLinkText,
|
||||
.main-createPlaylistButton-text,
|
||||
.main-navBar-navBarLink > span {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
.main-navBar-navBarLink > span,
|
||||
.main-rootlist-rootlistItemLink > span {
|
||||
@include spiceFont("info", 14px, "Regular");
|
||||
letter-spacing: normal;
|
||||
line-height: 20px;
|
||||
text-transform: none;
|
||||
|
|
@ -1041,7 +1050,9 @@ span.main-userWidget-displayName,
|
|||
}
|
||||
|
||||
.main-rootlist-wrapper > div:nth-child(2) > li img,
|
||||
.main-navBar-navBarLink > .icon {
|
||||
.main-navBar-navBarLink > svg,
|
||||
.main-navBar-navBarLink > .icon,
|
||||
.main-rootlist-rootlistItemLink > svg {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
|
@ -1061,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;
|
||||
|
|
@ -1118,17 +1129,13 @@ 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: calc(var(--main-gap) + 10px);
|
||||
right: calc(var(--main-gap) * 2);
|
||||
left: unset;
|
||||
}
|
||||
|
||||
html.right-expanded-cover.buddyfeed-visible .main-coverSlotExpanded-container {
|
||||
right: calc(var(--main-gap) + var(--buddy-feed-width) + 10px);
|
||||
right: calc(var(--main-gap) + var(--buddy-feed-width));
|
||||
left: unset;
|
||||
}
|
||||
|
||||
|
|
@ -1180,22 +1187,6 @@ html.right-expanded-cover.buddyfeed-visible .main-coverSlotExpanded-container {
|
|||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.main-dropDown-dropDown,
|
||||
.x-sortBox-sortDropdown {
|
||||
background-color: spiceColor("selected-row", 0.4) !important;
|
||||
color: spiceColor("subtext");
|
||||
}
|
||||
|
||||
.main-dropDown-dropDown:hover,
|
||||
.x-sortBox-sortDropdown:hover {
|
||||
background-color: spiceColor("selected-row", 0.6) !important;
|
||||
}
|
||||
|
||||
.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;
|
||||
|
|
@ -1241,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,
|
||||
|
|
@ -1281,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;
|
||||
|
|
@ -1294,18 +1285,68 @@ 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 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"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// workaround to remove gradient near album info #103
|
||||
.main-trackInfo-container > div > div:before,
|
||||
.main-trackInfo-container > div > div:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// workaround to re-center track-info
|
||||
#main:not([player-controls="spotify"]) .main-trackInfo-container > div > div > div {
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<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: 656 B |
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
110
webpack.config.babel.js
Normal 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;
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
const webpack = require("webpack");
|
||||
const CopyPlugin = require("copy-webpack-plugin");
|
||||
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
|
||||
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
|
||||
const path = require("path");
|
||||
|
||||
/** @type {import('webpack').Configuration} */
|
||||
module.exports = {
|
||||
entry: [path.resolve(__dirname, "./src/js/main.js"), path.resolve(__dirname, "./src/styles/main.scss"), path.resolve(__dirname, "./src/styles/Colors.scss")],
|
||||
output: {
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
filename: "dribbblish-dynamic.js"
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /main\.js$/,
|
||||
exclude: /node_modules/,
|
||||
use: []
|
||||
},
|
||||
{
|
||||
test: /main\.scss$/,
|
||||
exclude: /node_modules/,
|
||||
type: "asset/resource",
|
||||
generator: {
|
||||
filename: "user.css"
|
||||
},
|
||||
use: [
|
||||
{
|
||||
loader: "sass-loader",
|
||||
options: {
|
||||
sourceMap: true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /Colors\.scss$/,
|
||||
exclude: /node_modules/,
|
||||
type: "asset/resource",
|
||||
generator: {
|
||||
filename: "color.ini"
|
||||
},
|
||||
use: [path.resolve(__dirname, "./src/loaders/color-loader.js")]
|
||||
},
|
||||
{
|
||||
test: /\.svg/,
|
||||
exclude: /node_modules/,
|
||||
use: ["raw-loader"]
|
||||
}
|
||||
]
|
||||
},
|
||||
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" }]
|
||||
})
|
||||
],
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [`...`, new CssMinimizerPlugin()]
|
||||
}
|
||||
};
|
||||
Loading…
Reference in a new issue