Merge branch 'dev'
This commit is contained in:
commit
7b897720a9
19 changed files with 714 additions and 114 deletions
16
.github/workflows/reportBrokenPlugins.yml
vendored
16
.github/workflows/reportBrokenPlugins.yml
vendored
|
|
@ -28,12 +28,8 @@ jobs:
|
|||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
if: ${{ github.event_name == 'schedule' }}
|
||||
with:
|
||||
ref: dev
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
ref: ${{ github.event_name == 'schedule' && 'dev' || github.ref }}
|
||||
|
||||
- uses: pnpm/action-setup@v3 # Install pnpm using packageManager key in package.json
|
||||
|
||||
|
|
@ -45,13 +41,7 @@ jobs:
|
|||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pnpm install --frozen-lockfile
|
||||
|
||||
- name: Install Google Chrome
|
||||
id: setup-chrome
|
||||
uses: browser-actions/setup-chrome@82b9ce628cc5595478a9ebadc480958a36457dc2
|
||||
with:
|
||||
chrome-version: stable
|
||||
pnpm install --frozen-lockfile --prefer-offline
|
||||
|
||||
- name: Build Equicord Reporter Version
|
||||
run: pnpm buildReporter
|
||||
|
|
@ -60,7 +50,7 @@ jobs:
|
|||
timeout-minutes: 10
|
||||
run: |
|
||||
export PATH="$PWD/node_modules/.bin:$PATH"
|
||||
export CHROMIUM_BIN=${{ steps.setup-chrome.outputs.chrome-path }}
|
||||
export CHROMIUM_BIN=/usr/bin/google-chrome
|
||||
|
||||
esbuild scripts/generateReport.ts > dist/report.mjs
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@
|
|||
"@ffmpeg/ffmpeg": "^0.12.10",
|
||||
"@ffmpeg/util": "^0.12.1",
|
||||
"@intrnl/xxhash64": "^0.1.2",
|
||||
"@song-spotlight/api": "^2.0.0",
|
||||
"@song-spotlight/api": "^2.0.1",
|
||||
"@streamparser/json": "^0.0.22",
|
||||
"@types/less": "^3.0.8",
|
||||
"@types/stylus": "^0.48.42",
|
||||
|
|
|
|||
164
pnpm-lock.yaml
generated
164
pnpm-lock.yaml
generated
|
|
@ -26,8 +26,8 @@ importers:
|
|||
specifier: ^0.1.2
|
||||
version: 0.1.2
|
||||
'@song-spotlight/api':
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0(typescript@5.9.3)
|
||||
specifier: ^2.0.1
|
||||
version: 2.0.1(typescript@5.9.3)
|
||||
'@streamparser/json':
|
||||
specifier: ^0.0.22
|
||||
version: 0.0.22
|
||||
|
|
@ -721,8 +721,8 @@ packages:
|
|||
'@rtsao/scc@1.1.0':
|
||||
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
|
||||
|
||||
'@song-spotlight/api@2.0.0':
|
||||
resolution: {integrity: sha512-a3gyrSojd3BzwloxZHyDXA7HhjmCNQZOYATh7AZJyCXIqilBMwFPgQ2FVkUpzf6MzZhN9emeN6OGZOJb/rkECw==}
|
||||
'@song-spotlight/api@2.0.1':
|
||||
resolution: {integrity: sha512-nxBJch8cGp10NYeJSp3DCPS/LYmlpcs9VCHrHzgCHFRICTKWhlpiUZgrACP9TmQ8RQsE4B9e23BcQDaqWqPCGg==}
|
||||
peerDependencies:
|
||||
typescript: ^5
|
||||
|
||||
|
|
@ -941,6 +941,10 @@ packages:
|
|||
resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
array-includes@3.1.9:
|
||||
resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
array-union@2.1.0:
|
||||
resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
|
||||
engines: {node: '>=8'}
|
||||
|
|
@ -957,6 +961,10 @@ packages:
|
|||
resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
array.prototype.flat@1.3.3:
|
||||
resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
array.prototype.flatmap@1.3.3:
|
||||
resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
|
@ -1258,6 +1266,10 @@ packages:
|
|||
resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-abstract@1.24.1:
|
||||
resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-define-property@1.0.1:
|
||||
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
|
@ -1322,8 +1334,8 @@ packages:
|
|||
eslint-import-resolver-node@0.3.9:
|
||||
resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==}
|
||||
|
||||
eslint-module-utils@2.12.0:
|
||||
resolution: {integrity: sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==}
|
||||
eslint-module-utils@2.12.1:
|
||||
resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==}
|
||||
engines: {node: '>=4'}
|
||||
peerDependencies:
|
||||
'@typescript-eslint/parser': '*'
|
||||
|
|
@ -1757,6 +1769,10 @@ packages:
|
|||
resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
is-negative-zero@2.0.3:
|
||||
resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
is-number-object@1.1.1:
|
||||
resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
|
@ -1947,6 +1963,9 @@ packages:
|
|||
minimatch@3.1.2:
|
||||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
||||
|
||||
minimatch@3.1.5:
|
||||
resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==}
|
||||
|
||||
minimatch@9.0.5:
|
||||
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
|
|
@ -2217,6 +2236,11 @@ packages:
|
|||
resolve-pkg-maps@1.0.0:
|
||||
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
|
||||
|
||||
resolve@1.22.11:
|
||||
resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
hasBin: true
|
||||
|
||||
resolve@1.22.8:
|
||||
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
|
||||
hasBin: true
|
||||
|
|
@ -2340,6 +2364,10 @@ packages:
|
|||
standalone-electron-types@34.2.0:
|
||||
resolution: {integrity: sha512-+BIrNe0TdZBBBRS3G/F7cPbuBipSZylSjSrJu9+sjw84+vz36a5oZ7l9NeH7aXgMWmPzPZKsTm+K0nOIvhENJQ==}
|
||||
|
||||
stop-iteration-iterator@1.1.0:
|
||||
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
streamx@2.22.0:
|
||||
resolution: {integrity: sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==}
|
||||
|
||||
|
|
@ -2588,6 +2616,10 @@ packages:
|
|||
resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
which-typed-array@1.1.20:
|
||||
resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
which@1.3.1:
|
||||
resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}
|
||||
hasBin: true
|
||||
|
|
@ -3006,7 +3038,7 @@ snapshots:
|
|||
|
||||
'@rtsao/scc@1.1.0': {}
|
||||
|
||||
'@song-spotlight/api@2.0.0(typescript@5.9.3)':
|
||||
'@song-spotlight/api@2.0.1(typescript@5.9.3)':
|
||||
dependencies:
|
||||
typescript: 5.9.3
|
||||
optionalDependencies:
|
||||
|
|
@ -3271,6 +3303,17 @@ snapshots:
|
|||
get-intrinsic: 1.3.0
|
||||
is-string: 1.1.1
|
||||
|
||||
array-includes@3.1.9:
|
||||
dependencies:
|
||||
call-bind: 1.0.8
|
||||
call-bound: 1.0.4
|
||||
define-properties: 1.2.1
|
||||
es-abstract: 1.24.1
|
||||
es-object-atoms: 1.1.1
|
||||
get-intrinsic: 1.3.0
|
||||
is-string: 1.1.1
|
||||
math-intrinsics: 1.1.0
|
||||
|
||||
array-union@2.1.0: {}
|
||||
|
||||
array.prototype.findlast@1.2.5:
|
||||
|
|
@ -3287,7 +3330,7 @@ snapshots:
|
|||
call-bind: 1.0.8
|
||||
call-bound: 1.0.4
|
||||
define-properties: 1.2.1
|
||||
es-abstract: 1.23.9
|
||||
es-abstract: 1.24.1
|
||||
es-errors: 1.3.0
|
||||
es-object-atoms: 1.1.1
|
||||
es-shim-unscopables: 1.1.0
|
||||
|
|
@ -3299,6 +3342,13 @@ snapshots:
|
|||
es-abstract: 1.23.9
|
||||
es-shim-unscopables: 1.1.0
|
||||
|
||||
array.prototype.flat@1.3.3:
|
||||
dependencies:
|
||||
call-bind: 1.0.8
|
||||
define-properties: 1.2.1
|
||||
es-abstract: 1.24.1
|
||||
es-shim-unscopables: 1.1.0
|
||||
|
||||
array.prototype.flatmap@1.3.3:
|
||||
dependencies:
|
||||
call-bind: 1.0.8
|
||||
|
|
@ -3644,6 +3694,63 @@ snapshots:
|
|||
unbox-primitive: 1.1.0
|
||||
which-typed-array: 1.1.19
|
||||
|
||||
es-abstract@1.24.1:
|
||||
dependencies:
|
||||
array-buffer-byte-length: 1.0.2
|
||||
arraybuffer.prototype.slice: 1.0.4
|
||||
available-typed-arrays: 1.0.7
|
||||
call-bind: 1.0.8
|
||||
call-bound: 1.0.4
|
||||
data-view-buffer: 1.0.2
|
||||
data-view-byte-length: 1.0.2
|
||||
data-view-byte-offset: 1.0.1
|
||||
es-define-property: 1.0.1
|
||||
es-errors: 1.3.0
|
||||
es-object-atoms: 1.1.1
|
||||
es-set-tostringtag: 2.1.0
|
||||
es-to-primitive: 1.3.0
|
||||
function.prototype.name: 1.1.8
|
||||
get-intrinsic: 1.3.0
|
||||
get-proto: 1.0.1
|
||||
get-symbol-description: 1.1.0
|
||||
globalthis: 1.0.4
|
||||
gopd: 1.2.0
|
||||
has-property-descriptors: 1.0.2
|
||||
has-proto: 1.2.0
|
||||
has-symbols: 1.1.0
|
||||
hasown: 2.0.2
|
||||
internal-slot: 1.1.0
|
||||
is-array-buffer: 3.0.5
|
||||
is-callable: 1.2.7
|
||||
is-data-view: 1.0.2
|
||||
is-negative-zero: 2.0.3
|
||||
is-regex: 1.2.1
|
||||
is-set: 2.0.3
|
||||
is-shared-array-buffer: 1.0.4
|
||||
is-string: 1.1.1
|
||||
is-typed-array: 1.1.15
|
||||
is-weakref: 1.1.1
|
||||
math-intrinsics: 1.1.0
|
||||
object-inspect: 1.13.4
|
||||
object-keys: 1.1.1
|
||||
object.assign: 4.1.7
|
||||
own-keys: 1.0.1
|
||||
regexp.prototype.flags: 1.5.4
|
||||
safe-array-concat: 1.1.3
|
||||
safe-push-apply: 1.0.0
|
||||
safe-regex-test: 1.1.0
|
||||
set-proto: 1.0.0
|
||||
stop-iteration-iterator: 1.1.0
|
||||
string.prototype.trim: 1.2.10
|
||||
string.prototype.trimend: 1.0.9
|
||||
string.prototype.trimstart: 1.0.8
|
||||
typed-array-buffer: 1.0.3
|
||||
typed-array-byte-length: 1.0.3
|
||||
typed-array-byte-offset: 1.0.4
|
||||
typed-array-length: 1.0.7
|
||||
unbox-primitive: 1.1.0
|
||||
which-typed-array: 1.1.20
|
||||
|
||||
es-define-property@1.0.1: {}
|
||||
|
||||
es-errors@1.3.0: {}
|
||||
|
|
@ -3767,11 +3874,11 @@ snapshots:
|
|||
dependencies:
|
||||
debug: 3.2.7
|
||||
is-core-module: 2.16.1
|
||||
resolve: 1.22.8
|
||||
resolve: 1.22.11
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.12.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1):
|
||||
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.47.0(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
|
|
@ -3784,19 +3891,19 @@ snapshots:
|
|||
eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1):
|
||||
dependencies:
|
||||
'@rtsao/scc': 1.1.0
|
||||
array-includes: 3.1.8
|
||||
array-includes: 3.1.9
|
||||
array.prototype.findlastindex: 1.2.6
|
||||
array.prototype.flat: 1.3.2
|
||||
array.prototype.flat: 1.3.3
|
||||
array.prototype.flatmap: 1.3.3
|
||||
debug: 3.2.7
|
||||
doctrine: 2.1.0
|
||||
eslint: 9.39.1
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1)
|
||||
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.47.0(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1)
|
||||
hasown: 2.0.2
|
||||
is-core-module: 2.16.1
|
||||
is-glob: 4.0.3
|
||||
minimatch: 3.1.2
|
||||
minimatch: 3.1.5
|
||||
object.fromentries: 2.0.8
|
||||
object.groupby: 1.0.3
|
||||
object.values: 1.2.1
|
||||
|
|
@ -4276,6 +4383,8 @@ snapshots:
|
|||
|
||||
is-map@2.0.3: {}
|
||||
|
||||
is-negative-zero@2.0.3: {}
|
||||
|
||||
is-number-object@1.1.1:
|
||||
dependencies:
|
||||
call-bound: 1.0.4
|
||||
|
|
@ -4450,6 +4559,10 @@ snapshots:
|
|||
dependencies:
|
||||
brace-expansion: 1.1.12
|
||||
|
||||
minimatch@3.1.5:
|
||||
dependencies:
|
||||
brace-expansion: 1.1.12
|
||||
|
||||
minimatch@9.0.5:
|
||||
dependencies:
|
||||
brace-expansion: 2.0.2
|
||||
|
|
@ -4524,7 +4637,7 @@ snapshots:
|
|||
dependencies:
|
||||
call-bind: 1.0.8
|
||||
define-properties: 1.2.1
|
||||
es-abstract: 1.23.9
|
||||
es-abstract: 1.24.1
|
||||
|
||||
object.values@1.2.1:
|
||||
dependencies:
|
||||
|
|
@ -4734,6 +4847,12 @@ snapshots:
|
|||
|
||||
resolve-pkg-maps@1.0.0: {}
|
||||
|
||||
resolve@1.22.11:
|
||||
dependencies:
|
||||
is-core-module: 2.16.1
|
||||
path-parse: 1.0.7
|
||||
supports-preserve-symlinks-flag: 1.0.0
|
||||
|
||||
resolve@1.22.8:
|
||||
dependencies:
|
||||
is-core-module: 2.16.1
|
||||
|
|
@ -4875,6 +4994,11 @@ snapshots:
|
|||
dependencies:
|
||||
'@types/node': 22.13.13
|
||||
|
||||
stop-iteration-iterator@1.1.0:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
internal-slot: 1.1.0
|
||||
|
||||
streamx@2.22.0:
|
||||
dependencies:
|
||||
fast-fifo: 1.3.2
|
||||
|
|
@ -5239,6 +5363,16 @@ snapshots:
|
|||
gopd: 1.2.0
|
||||
has-tostringtag: 1.0.2
|
||||
|
||||
which-typed-array@1.1.20:
|
||||
dependencies:
|
||||
available-typed-arrays: 1.0.7
|
||||
call-bind: 1.0.8
|
||||
call-bound: 1.0.4
|
||||
for-each: 0.3.5
|
||||
get-proto: 1.0.1
|
||||
gopd: 1.2.0
|
||||
has-tostringtag: 1.0.2
|
||||
|
||||
which@1.3.1:
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
|
|
|
|||
|
|
@ -749,3 +749,23 @@ export const QrCodeIcon = (props?: any) => {
|
|||
</Icon>
|
||||
);
|
||||
};
|
||||
|
||||
export const ComponentsIcon = (props?: any) => {
|
||||
return (
|
||||
<Icon
|
||||
{...props}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill={props.fill || "currentColor"}
|
||||
d="M3 15.5V6a3 3 0 0 1 3-3h12a3 3 0 0 1 3 3v.5a.5.5 0 0 1-.5.5H17a4 4 0 0 0-4 4v4.5a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5ZM12.5 18H2a1 1 0 1 0 0 2h10.48c.33 0 .57-.3.54-.63A4.08 4.08 0 0 1 13 19v-.5a.5.5 0 0 0-.5-.5Z"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
fill={props.fill || "currentColor"}
|
||||
d="M15 11c0-1.1.9-2 2-2h4a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-8Zm2 1a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2h-2a1 1 0 0 1-1-1Z"
|
||||
/>
|
||||
</Icon>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
187
src/equicordplugins/clickableRoles/index.tsx
Normal file
187
src/equicordplugins/clickableRoles/index.tsx
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2026 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./style.css";
|
||||
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { classNameFactory } from "@utils/css";
|
||||
import { openUserProfile } from "@utils/discord";
|
||||
import { isTruthy } from "@utils/guards";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import definePlugin from "@utils/types";
|
||||
import type { Role } from "@vencord/discord-types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { Constants, GuildRoleStore, IconUtils, Popout, RestAPI, ScrollerThin, useEffect, useRef, UserStore, useState, useStateFromStores } from "@webpack/common";
|
||||
|
||||
const logger = new Logger("ClickableRoles");
|
||||
|
||||
const cl = classNameFactory("vc-clickableroles-");
|
||||
|
||||
const GuildActions = findByPropsLazy("requestMembersById", "banUser");
|
||||
|
||||
const MAX_VISIBLE_MEMBERS = 20;
|
||||
const CACHE_TTL = 60_000;
|
||||
|
||||
const memberCache = new Map<string, { ids: string[]; timestamp: number; }>();
|
||||
|
||||
function getCachedMemberIds(guildId: string, roleId: string) {
|
||||
const key = `${guildId}-${roleId}`;
|
||||
const cached = memberCache.get(key);
|
||||
if (cached && Date.now() - cached.timestamp < CACHE_TTL) return cached.ids;
|
||||
return null;
|
||||
}
|
||||
|
||||
function setCachedMemberIds(guildId: string, roleId: string, ids: string[]) {
|
||||
memberCache.set(`${guildId}-${roleId}`, { ids, timestamp: Date.now() });
|
||||
}
|
||||
|
||||
function RoleMembersList({ roleId, guildId, closePopout, setPopoutRef }: { roleId: string; guildId: string; closePopout(): void; setPopoutRef(ref: HTMLDivElement | null): void; }) {
|
||||
const [memberIds, setMemberIds] = useState<string[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const role = useStateFromStores([GuildRoleStore], () => GuildRoleStore.getRole(guildId, roleId));
|
||||
|
||||
const [totalCount, setTotalCount] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
const cached = getCachedMemberIds(guildId, roleId);
|
||||
if (cached) {
|
||||
setTotalCount(cached.length);
|
||||
const visible = cached.slice(0, MAX_VISIBLE_MEMBERS);
|
||||
setMemberIds(visible);
|
||||
if (visible.length) GuildActions.requestMembersById(guildId, visible, false);
|
||||
setLoading(false);
|
||||
} else {
|
||||
RestAPI.get({
|
||||
url: Constants.Endpoints.GUILD_ROLE_MEMBER_IDS(guildId, roleId),
|
||||
}).then(res => {
|
||||
if (cancelled) return;
|
||||
const ids = res.body as string[];
|
||||
setCachedMemberIds(guildId, roleId, ids);
|
||||
setTotalCount(ids.length);
|
||||
const visible = ids.slice(0, MAX_VISIBLE_MEMBERS);
|
||||
setMemberIds(visible);
|
||||
if (visible.length) GuildActions.requestMembersById(guildId, visible, false);
|
||||
setLoading(false);
|
||||
}).catch(e => {
|
||||
if (cancelled) return;
|
||||
logger.error("Failed to fetch role members", e);
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
|
||||
return () => { cancelled = true; };
|
||||
}, [guildId, roleId]);
|
||||
|
||||
const users = useStateFromStores(
|
||||
[UserStore],
|
||||
() => memberIds.map(id => UserStore.getUser(id)).filter(isTruthy),
|
||||
[memberIds]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={cl("popout")} ref={setPopoutRef}>
|
||||
<div className={cl("header")}>
|
||||
<span className={cl("color")} style={{ backgroundColor: role?.colorString ?? "var(--background-mod-strong)" }} />
|
||||
<span className={cl("name")}>{role?.name ?? "Unknown Role"}</span>
|
||||
<span className={cl("count")}>{totalCount}</span>
|
||||
</div>
|
||||
<ScrollerThin className={cl("list")} fade>
|
||||
{loading ? (
|
||||
<div className={cl("empty")}>Loading members...</div>
|
||||
) : users.length === 0 ? (
|
||||
<div className={cl("empty")}>No members found.</div>
|
||||
) : users.map(user => (
|
||||
<div
|
||||
key={user.id}
|
||||
className={cl("user")}
|
||||
onClick={() => {
|
||||
closePopout();
|
||||
openUserProfile(user.id);
|
||||
}}
|
||||
>
|
||||
<img src={IconUtils.getUserAvatarURL(user)} alt="" className={cl("avatar")} />
|
||||
<span>{user.globalName ?? user.username}</span>
|
||||
</div>
|
||||
))}
|
||||
{totalCount > MAX_VISIBLE_MEMBERS && (
|
||||
<div className={cl("overflow")}>and {totalCount - MAX_VISIBLE_MEMBERS} more</div>
|
||||
)}
|
||||
</ScrollerThin>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ClickableRole({ roleId, guildId, children }: { roleId: string; guildId: string; children: React.ReactNode; }) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
return (
|
||||
<Popout
|
||||
targetElementRef={ref}
|
||||
position="right"
|
||||
align="center"
|
||||
autoInvert
|
||||
nudgeAlignIntoViewport
|
||||
renderPopout={({ closePopout, setPopoutRef }) => (
|
||||
<RoleMembersList roleId={roleId} guildId={guildId} closePopout={closePopout} setPopoutRef={setPopoutRef} />
|
||||
)}
|
||||
>
|
||||
{popoutProps => (
|
||||
<div ref={ref} {...popoutProps} className={cl("trigger")}>
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
</Popout>
|
||||
);
|
||||
}
|
||||
|
||||
const WrappedClickableRole = ErrorBoundary.wrap(ClickableRole, { noop: true });
|
||||
|
||||
export default definePlugin({
|
||||
name: "ClickableRoles",
|
||||
description: "Click on roles in user profiles and the member list to see which members have them.",
|
||||
authors: [Devs.prism],
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: "#{intl::T+3Adg::raw}",
|
||||
replacement: {
|
||||
match: /(colors:\i\}\);)return(.*?enableTooltip:!1\}\):null,\i\]\}\))/,
|
||||
replace: "$1return $self.wrapRolePill(arguments[0],()=>$2)",
|
||||
},
|
||||
},
|
||||
{
|
||||
find: 'tutorialId:"whos-online"',
|
||||
replacement: {
|
||||
match: /\((function\(\i\)\{let\{id:.*?#{intl::CHANNEL_MEMBERS_A11Y_LABEL}.*?\}\))\}\);/,
|
||||
replace: "($self.wrapRoleGroup($1}));",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
wrapRoleGroup(originalFn: (props: { id: string; guildId: string; }) => React.ReactNode) {
|
||||
return (props: { id: string; guildId: string; }) => {
|
||||
const result = originalFn(props);
|
||||
if (!GuildRoleStore.getRole(props.guildId, props.id)) return result;
|
||||
return (
|
||||
<WrappedClickableRole roleId={props.id} guildId={props.guildId}>
|
||||
{result}
|
||||
</WrappedClickableRole>
|
||||
);
|
||||
};
|
||||
},
|
||||
|
||||
wrapRolePill(props: { role: Role; guildId: string; }, renderOriginal: () => React.ReactNode) {
|
||||
return (
|
||||
<WrappedClickableRole roleId={props.role.id} guildId={props.guildId}>
|
||||
{renderOriginal()}
|
||||
</WrappedClickableRole>
|
||||
);
|
||||
},
|
||||
});
|
||||
80
src/equicordplugins/clickableRoles/style.css
Normal file
80
src/equicordplugins/clickableRoles/style.css
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
.vc-clickableroles-trigger {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.vc-clickableroles-popout {
|
||||
background-color: var(--background-surface-highest);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
width: 250px;
|
||||
max-height: var(--reference-position-layer-max-height, 350px);
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.vc-clickableroles-list {
|
||||
max-height: 260px;
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.vc-clickableroles-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.vc-clickableroles-color {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.vc-clickableroles-name {
|
||||
font-weight: 600;
|
||||
color: var(--interactive-text-default);
|
||||
}
|
||||
|
||||
.vc-clickableroles-count {
|
||||
color: var(--text-muted);
|
||||
margin-left: auto;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.vc-clickableroles-empty {
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.vc-clickableroles-user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 4px 6px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
color: var(--interactive-text-default);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.vc-clickableroles-user:hover {
|
||||
background-color: var(--interactive-background-hover);
|
||||
}
|
||||
|
||||
.vc-clickableroles-avatar {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.vc-clickableroles-overflow {
|
||||
padding: 6px;
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import "./styles.css";
|
||||
|
||||
import { ComponentsIcon } from "@components/Icons";
|
||||
import SettingsPlugin from "@plugins/_core/settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { removeFromArray } from "@utils/misc";
|
||||
|
|
@ -14,30 +15,6 @@ import { SettingsRouter } from "@webpack/common";
|
|||
|
||||
import ComponentsTab from "./components/ComponentsTab";
|
||||
|
||||
function ComponentsIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M3 15.5V6a3 3 0 0 1 3-3h12a3 3 0 0 1 3 3v.5a.5.5 0 0 1-.5.5H17a4 4 0 0 0-4 4v4.5a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5ZM12.5 18H2a1 1 0 1 0 0 2h10.48c.33 0 .57-.3.54-.63A4.08 4.08 0 0 1 13 19v-.5a.5.5 0 0 0-.5-.5Z"
|
||||
/>
|
||||
<path
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
d="M15 11c0-1.1.9-2 2-2h4a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-8Zm2 1a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2h-2a1 1 0 0 1-1-1Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default definePlugin({
|
||||
name: "Components",
|
||||
description: "Adds a new tab to settings to browse Discord components.",
|
||||
|
|
|
|||
|
|
@ -4,8 +4,13 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./style.css";
|
||||
|
||||
import { definePluginSettings, migratePluginSetting, migratePluginSettings } from "@api/Settings";
|
||||
import { Divider } from "@components/Divider";
|
||||
import { HeadingSecondary } from "@components/Heading";
|
||||
import { Notice } from "@components/Notice";
|
||||
import { classNameFactory } from "@utils/css";
|
||||
import { Devs, EquicordDevs } from "@utils/index";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
||||
|
|
@ -24,6 +29,8 @@ for (const [oldKey, newKey] of migrationsAnammox) {
|
|||
migratePluginSetting("Declutter", newKey, oldKey);
|
||||
}
|
||||
|
||||
const cl = classNameFactory("vc-declutter-");
|
||||
|
||||
export const settings = definePluginSettings({
|
||||
userProfileHeader: {
|
||||
type: OptionType.COMPONENT,
|
||||
|
|
@ -59,11 +66,13 @@ export const settings = definePluginSettings({
|
|||
default: true,
|
||||
restartNeeded: true
|
||||
},
|
||||
removeUsernameStyles: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Remove username colors and effects.",
|
||||
default: true,
|
||||
restartNeeded: true
|
||||
accessibilityNotice: {
|
||||
type: OptionType.COMPONENT,
|
||||
component: () => (
|
||||
<Notice.Info className={cl("accessibility-notice")}>
|
||||
Discord already has a built-in username style option in Accessibility settings.
|
||||
</Notice.Info>
|
||||
)
|
||||
},
|
||||
friendsListHeader: {
|
||||
type: OptionType.COMPONENT,
|
||||
|
|
@ -72,7 +81,7 @@ export const settings = definePluginSettings({
|
|||
removeShopAboveDM: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Remove shops above DMs list.",
|
||||
default: true,
|
||||
default: false,
|
||||
restartNeeded: true,
|
||||
},
|
||||
removeQuestsAboveDM: {
|
||||
|
|
@ -125,17 +134,18 @@ export const settings = definePluginSettings({
|
|||
|
||||
function SectionSeparator(title: string) {
|
||||
return (
|
||||
<>
|
||||
<hr style={{ width: "100%" }} />
|
||||
<HeadingSecondary>{title}</HeadingSecondary>
|
||||
<hr style={{ width: "100%" }} />
|
||||
</>
|
||||
<div className={cl("section-separator")}>
|
||||
<Divider />
|
||||
<HeadingSecondary className={cl("section-title")}>
|
||||
{title}
|
||||
</HeadingSecondary>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default definePlugin({
|
||||
name: "Declutter",
|
||||
description: "Declutter discord ui by removing unwanted elements such as profile customizations, shops, quests and more.",
|
||||
description: "Cleans up Discord by removing non-essential UI elements like profile effects, shop tabs, boosts, and more.",
|
||||
authors: [EquicordDevs.Leon135, Devs.prism, Devs.Kyuuhachi],
|
||||
settings,
|
||||
patches: [
|
||||
|
|
@ -176,20 +186,13 @@ export default definePlugin({
|
|||
predicate: () => settings.store.removeClanTag,
|
||||
},
|
||||
{
|
||||
// Username styles and allways show username
|
||||
// Always show username
|
||||
find: ".NITRO_PRIVACY_PERK_BETA_COACHMARK));",
|
||||
replacement: [
|
||||
{
|
||||
match: /displayNameStyles:(\i),/,
|
||||
replace: "displayNameStyles:void 0,",
|
||||
predicate: () => settings.store.removeUsernameStyles
|
||||
},
|
||||
{
|
||||
match: /hoverText:(\i),forceHover:\i,children:/g,
|
||||
replace: "hoverText:$1,forceHover:!0,children:",
|
||||
predicate: () => settings.store.alwaysShowUsername
|
||||
},
|
||||
],
|
||||
replacement: {
|
||||
match: /hoverText:(\i),forceHover:\i,children:/g,
|
||||
replace: "hoverText:$1,forceHover:!0,children:"
|
||||
},
|
||||
predicate: () => settings.store.alwaysShowUsername
|
||||
},
|
||||
{
|
||||
// Button tooltips in user area
|
||||
|
|
|
|||
19
src/equicordplugins/declutter/style.css
Normal file
19
src/equicordplugins/declutter/style.css
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
.vc-declutter-accessibility-notice {
|
||||
margin-top: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.vc-declutter-section-separator {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.vc-declutter-section-title {
|
||||
margin-top: 14px;
|
||||
margin-bottom: 0;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.02em;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
|
@ -129,4 +129,4 @@ export default definePlugin({
|
|||
"user-context": UserMenuPatch,
|
||||
"message": MessageMenuPatch
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,15 +6,14 @@
|
|||
|
||||
import "./style.css";
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
import { useAuthorizationStore } from "./lib/stores/AuthorizationStore";
|
||||
import { useSongStore } from "./lib/stores/SongStore";
|
||||
import { Native } from "./service";
|
||||
import Settings from "./ui/settings";
|
||||
import settings from "./settings";
|
||||
import ProfileSongs from "./ui/songs/ProfileSongs";
|
||||
import WidgetSongs from "./ui/songs/WidgetSongs";
|
||||
|
||||
|
|
@ -22,12 +21,7 @@ export default definePlugin({
|
|||
name: "SongSpotlight",
|
||||
description: "Show off songs on your profile",
|
||||
authors: [EquicordDevs.nexpid],
|
||||
settings: definePluginSettings({
|
||||
manager: {
|
||||
type: OptionType.COMPONENT,
|
||||
component: () => <Settings />,
|
||||
},
|
||||
}),
|
||||
settings,
|
||||
patches: [
|
||||
// Personal profile popout
|
||||
{
|
||||
|
|
@ -50,7 +44,7 @@ export default definePlugin({
|
|||
find: ".SIDEBAR}),nicknameIcons:",
|
||||
replacement: {
|
||||
match: /{userId:(\i)\.id}\)}\).{0,100}]}\)(?=\]\}\))/,
|
||||
replace: "$&,$self.renderProfileSongs({userId:$1.id})",
|
||||
replace: "$&,$self.renderProfileSongs({userId:$1.id,isSidebar:true})",
|
||||
},
|
||||
},
|
||||
// Full profile modal sections (lazy loaded)
|
||||
|
|
@ -58,14 +52,14 @@ export default definePlugin({
|
|||
find: ".MUTUAL_GUILDS})),",
|
||||
replacement: {
|
||||
match: /(\i).push\({text.{0,50}}\);/,
|
||||
replace: "$&$1.push({text:\"Song Spotlight\",section:\"SONG_SPOTLIGHT\"});",
|
||||
replace: '$&$1.push({text:"Song Spotlight",section:"SONG_SPOTLIGHT"});',
|
||||
},
|
||||
},
|
||||
{
|
||||
find: ".hasUnsavedChanges()&&",
|
||||
replacement: {
|
||||
match: /({user:(\i),.{0,80}return (\i===))/,
|
||||
replace: "$1\"SONG_SPOTLIGHT\"?$self.renderWidgetSongs({user:$2}):$3",
|
||||
replace: '$1"SONG_SPOTLIGHT"?$self.renderWidgetSongs({user:$2}):$3',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
|||
34
src/equicordplugins/songSpotlight.desktop/settings.tsx
Normal file
34
src/equicordplugins/songSpotlight.desktop/settings.tsx
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2026 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { makeRange, OptionType } from "@utils/types";
|
||||
|
||||
import Settings from "./ui/settings";
|
||||
|
||||
export default definePluginSettings({
|
||||
collapseSongList: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Collapses the song list on user profiles to a button which opens a separate menu",
|
||||
default: false,
|
||||
},
|
||||
profileSongsLimit: {
|
||||
type: OptionType.SLIDER,
|
||||
description: "How many songs are shown when initially clicking on a user",
|
||||
default: 6,
|
||||
markers: makeRange(1, 6),
|
||||
},
|
||||
manager: {
|
||||
type: OptionType.COMPONENT,
|
||||
component: () => <Settings />,
|
||||
},
|
||||
}, {
|
||||
profileSongsLimit: {
|
||||
disabled() {
|
||||
return this.store.collapseSongList;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
@ -23,7 +23,10 @@
|
|||
-webkit-line-clamp: 1;
|
||||
line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* components */
|
||||
|
|
@ -137,13 +140,13 @@
|
|||
--list-height: calc(var(--base-height) + 80px);
|
||||
--number-size: 32px;
|
||||
|
||||
padding: 8px;
|
||||
gap: 12px;
|
||||
overflow: visible;
|
||||
}
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
|
||||
.user-profile-sidebar .vc-songspotlight-songs-container {
|
||||
padding: 12px;
|
||||
&.vc-songspotlight-songs-container-sidebar {
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.vc-songspotlight-songs-more {
|
||||
|
|
@ -194,6 +197,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.user-profile-sidebar .vc-songspotlight-song-container {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.user-profile-popout:not(.custom-user-profile-theme) .vc-songspotlight-song-container {
|
||||
background: var(--opacity-16);
|
||||
}
|
||||
|
|
@ -211,7 +218,6 @@
|
|||
grid-template-columns: 1fr auto;
|
||||
grid-template-rows: 1fr auto;
|
||||
gap: 8px;
|
||||
overflow: visible;
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
|
|
@ -220,7 +226,7 @@
|
|||
grid-column: 1;
|
||||
grid-row: 1 / 3;
|
||||
border-radius: inherit;
|
||||
overflow: visible;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.vc-songspotlight-song-cover {
|
||||
|
|
@ -232,7 +238,6 @@
|
|||
flex-shrink: 0;
|
||||
box-shadow: 0 3px 12px 0 rgb(0 0 0 / 16%);
|
||||
transition: 0.18s transform;
|
||||
overflow: visible;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.03);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,14 @@ import { Tooltip } from "@webpack/common";
|
|||
import { ComponentType, HTMLAttributes, JSX } from "react";
|
||||
|
||||
export const OverlayClasses = findCssClassesLazy("overlay", "inner", "outer");
|
||||
export const ContainerClasses = findCssClassesLazy(
|
||||
"breadcrumb",
|
||||
"innerContainer",
|
||||
"icons",
|
||||
"icon",
|
||||
"displayCount",
|
||||
"displayCountText",
|
||||
);
|
||||
export const CardClasses = findCssClassesLazy("card", "headerText", "headerContextMenu");
|
||||
export const WidgetClasses = findCssClassesLazy("tabPanelScroller", "tabListScroller");
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2026 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { BaseText } from "@components/index";
|
||||
import { Native } from "@equicordplugins/songSpotlight.desktop/service";
|
||||
import { CardClasses, ContainerClasses, OverlayClasses, Spinner } from "@equicordplugins/songSpotlight.desktop/ui/common";
|
||||
import { RenderSongInfo } from "@song-spotlight/api/handlers";
|
||||
import { UserData } from "@song-spotlight/api/structs";
|
||||
import { sid } from "@song-spotlight/api/util";
|
||||
import { classes } from "@utils/index";
|
||||
import {
|
||||
SelectedChannelStore,
|
||||
SelectedGuildStore,
|
||||
useEffect,
|
||||
useMemo,
|
||||
UserProfileActions,
|
||||
UserStore,
|
||||
useState,
|
||||
} from "@webpack/common";
|
||||
|
||||
import { ProfileSongsProps } from "./ProfileSongs";
|
||||
|
||||
const shownSongs = 4;
|
||||
|
||||
interface CollapsedProfileSongsProps extends ProfileSongsProps {
|
||||
data?: UserData;
|
||||
}
|
||||
|
||||
export default function CollapsedProfileSongs({ data, userId, isSidebar }: CollapsedProfileSongsProps) {
|
||||
const [renders, setRenders] = useState(new Map<string, RenderSongInfo>());
|
||||
const previews = useMemo(() => data?.slice(0, shownSongs), [data]);
|
||||
|
||||
useEffect(() => {
|
||||
setRenders(new Map());
|
||||
if (!previews) return;
|
||||
|
||||
for (const song of previews) {
|
||||
Native.renderSong(song)
|
||||
.catch(() => null)
|
||||
.then(info => {
|
||||
if (!info) return;
|
||||
setRenders(renders => new Map(renders).set(sid(song), info));
|
||||
});
|
||||
}
|
||||
}, [previews]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={ContainerClasses.breadcrumb}
|
||||
aria-label="Song Spotlight"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => {
|
||||
const user = UserStore.getUser(userId);
|
||||
if (!user) return;
|
||||
|
||||
const guildId = SelectedGuildStore.getGuildId();
|
||||
UserProfileActions.openUserProfileModal({
|
||||
userId,
|
||||
guildId,
|
||||
channelId: SelectedChannelStore.getChannelId(),
|
||||
analyticsLocation: {
|
||||
page: guildId ? "Guild Channel" : "DM Channel",
|
||||
section: "Profile Popout",
|
||||
},
|
||||
tabSection: "SONG_SPOTLIGHT",
|
||||
});
|
||||
}}
|
||||
>
|
||||
<div className={classes(OverlayClasses.overlay, ContainerClasses.innerContainer, CardClasses.card)}>
|
||||
<BaseText size={isSidebar ? "sm" : "xs"} weight="medium">Song Spotlight</BaseText>
|
||||
<div className={ContainerClasses.icons}>
|
||||
{previews && data
|
||||
? previews.map((song, i) => {
|
||||
const render = renders.get(sid(song));
|
||||
const extra = i === shownSongs - 1 && data.length > shownSongs;
|
||||
|
||||
return (
|
||||
<div className={ContainerClasses.icon} key={i}>
|
||||
{render?.thumbnailUrl && (
|
||||
<img
|
||||
src={render.thumbnailUrl}
|
||||
alt={render.label}
|
||||
className={classes(extra && ContainerClasses.displayCount)}
|
||||
/>
|
||||
)}
|
||||
{extra && (
|
||||
<div className={ContainerClasses.displayCountText}>
|
||||
<BaseText size="xs" weight="medium">+{data.length - shownSongs}</BaseText>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})
|
||||
: <Spinner type={Spinner.Type.SPINNING_CIRCLE} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -7,10 +7,12 @@
|
|||
import { BaseText } from "@components/BaseText";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { LinkIcon, PencilIcon } from "@components/Icons";
|
||||
import { Link } from "@components/Link";
|
||||
import { listData } from "@equicordplugins/songSpotlight.desktop/lib/api";
|
||||
import { useAuthorizationStore } from "@equicordplugins/songSpotlight.desktop/lib/stores/AuthorizationStore";
|
||||
import { useSongStore } from "@equicordplugins/songSpotlight.desktop/lib/stores/SongStore";
|
||||
import { cl } from "@equicordplugins/songSpotlight.desktop/lib/utils";
|
||||
import settings from "@equicordplugins/songSpotlight.desktop/settings";
|
||||
import {
|
||||
CardClasses,
|
||||
MoreHorizontalIcon,
|
||||
|
|
@ -20,40 +22,75 @@ import {
|
|||
import { openSettingsModal } from "@equicordplugins/songSpotlight.desktop/ui/settings";
|
||||
import { sid } from "@song-spotlight/api/util";
|
||||
import { copyWithToast } from "@utils/discord";
|
||||
import { classes } from "@utils/misc";
|
||||
import { ContextMenuApi, FluxDispatcher, Menu, React, Tooltip, useEffect, UserStore, useState } from "@webpack/common";
|
||||
import { classes } from "@utils/index";
|
||||
import {
|
||||
ContextMenuApi,
|
||||
FluxDispatcher,
|
||||
Menu,
|
||||
React,
|
||||
Tooltip,
|
||||
useEffect,
|
||||
useMemo,
|
||||
UserStore,
|
||||
useState,
|
||||
} from "@webpack/common";
|
||||
|
||||
import Song from ".";
|
||||
import CollapsedProfileSongs from "./CollapsedProfileSongs";
|
||||
|
||||
interface ProfileSongsProps {
|
||||
export interface ProfileSongsProps {
|
||||
userId: string;
|
||||
isSidebar: boolean;
|
||||
}
|
||||
|
||||
export default function ProfileSongs({ userId }: ProfileSongsProps) {
|
||||
export default function ProfileSongs({ userId, isSidebar }: ProfileSongsProps) {
|
||||
const [failed, setFailed] = useState(false);
|
||||
const { isAuthorized } = useAuthorizationStore();
|
||||
const { users } = useSongStore();
|
||||
const { profileSongsLimit, collapseSongList } = settings.use();
|
||||
|
||||
const data = users[userId]?.data, at = users[userId]?.at;
|
||||
const data = users[userId]?.data;
|
||||
useEffect(() => {
|
||||
if (isAuthorized() && !data) listData(userId).catch(() => setFailed(true));
|
||||
}, [isAuthorized()]);
|
||||
|
||||
const [clamped, setClamped] = useState(true);
|
||||
const clampedData = useMemo(() => data && (clamped ? data.slice(0, profileSongsLimit) : data), [
|
||||
data,
|
||||
clamped,
|
||||
profileSongsLimit,
|
||||
]);
|
||||
|
||||
const owned = UserStore.getCurrentUser().id === userId;
|
||||
|
||||
if (isAuthorized() && !data && !failed) {
|
||||
const pending = isAuthorized() && !clampedData && !data && !failed;
|
||||
if (!pending && !clampedData?.[0]) return null;
|
||||
|
||||
if (collapseSongList) {
|
||||
return (
|
||||
<CollapsedProfileSongs
|
||||
data={data}
|
||||
userId={userId}
|
||||
isSidebar={isSidebar}
|
||||
/>
|
||||
);
|
||||
} else if (pending) {
|
||||
return <Spinner type={Spinner.Type.WANDERING_CUBES} />;
|
||||
} else if (!data?.[0]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes(OverlayClasses.overlay, CardClasses.card, cl("songs-container"))}
|
||||
className={classes(
|
||||
isSidebar && OverlayClasses.overlay,
|
||||
isSidebar && CardClasses.card,
|
||||
cl("songs-container", isSidebar && "songs-container-sidebar"),
|
||||
)}
|
||||
key="song-spotlight-profile-songs"
|
||||
>
|
||||
<Flex justifyContent="space-between">
|
||||
<BaseText size="xs" weight="semibold" className={cl("header")}>Song Spotlight</BaseText>
|
||||
<BaseText size="xs" weight={isSidebar ? "semibold" : "medium"} className={cl("header")}>
|
||||
Song Spotlight
|
||||
</BaseText>
|
||||
<Tooltip text="More">
|
||||
{props => (
|
||||
<MoreHorizontalIcon
|
||||
|
|
@ -89,8 +126,17 @@ export default function ProfileSongs({ userId }: ProfileSongsProps) {
|
|||
</Tooltip>
|
||||
</Flex>
|
||||
<Flex flexDirection="column" gap="6px">
|
||||
{data.map((song, i) => <Song owned={owned} song={song} index={i} key={sid(song)} />)}
|
||||
{clampedData.map((song, i) => <Song owned={owned} song={song} index={i} key={sid(song)} />)}
|
||||
</Flex>
|
||||
{clamped && data.length > profileSongsLimit && (
|
||||
<Link
|
||||
onClick={() => setClamped(false)}
|
||||
>
|
||||
<BaseText size="sm" weight="medium" className={cl("sub")} style={{ textAlign: "center" }}>
|
||||
Show {data.length - profileSongsLimit} more
|
||||
</BaseText>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ function SongInfo({ owned, song, render, big }: SongInfoProps) {
|
|||
icon={LinkIcon}
|
||||
action={() => copyWithToast(render.link)}
|
||||
/>
|
||||
{owned
|
||||
{!owned
|
||||
? (
|
||||
<Menu.MenuItem
|
||||
id="steal-song"
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export default definePlugin({
|
|||
replacement: [
|
||||
{
|
||||
match: /\[\i\.\i\]:\i\}\),children:\[/,
|
||||
replace: "$&$self.renderButtons(arguments[0].user),"
|
||||
replace: "$&$self.renderButtons(arguments[0]?.user),"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export default definePlugin({
|
|||
{
|
||||
find: "#{intl::USER_PROFILE_PRONOUNS}",
|
||||
replacement: {
|
||||
match: /(?<=shouldUnderlineOnHover:null.{0,150})children:(\i)/,
|
||||
match: /(?<=shouldUnderlineOnHover:null.{0,200})children:(\i)/,
|
||||
replace: "children:[...Vencord.Api.NicknameIcons._renderIcons({userId:arguments[0].user?.id}),$1]"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue