mirror of
https://github.com/trafficlunar/jellyfin-spicetify.git
synced 2026-06-13 19:07:06 +00:00
feat: settings modal + player hijack concept
This commit is contained in:
parent
8c98c2624b
commit
138c7e815c
6 changed files with 556 additions and 35 deletions
|
|
@ -12,5 +12,8 @@
|
||||||
"@types/react": "^19.2.14",
|
"@types/react": "^19.2.14",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"spicetify-creator": "^1.0.17"
|
"spicetify-creator": "^1.0.17"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@jellyfin/sdk": "^0.13.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
187
pnpm-lock.yaml
generated
187
pnpm-lock.yaml
generated
|
|
@ -7,6 +7,10 @@ settings:
|
||||||
importers:
|
importers:
|
||||||
|
|
||||||
.:
|
.:
|
||||||
|
dependencies:
|
||||||
|
'@jellyfin/sdk':
|
||||||
|
specifier: ^0.13.0
|
||||||
|
version: 0.13.0(axios@1.13.6)
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@types/react':
|
'@types/react':
|
||||||
specifier: ^19.2.14
|
specifier: ^19.2.14
|
||||||
|
|
@ -33,6 +37,11 @@ packages:
|
||||||
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
'@jellyfin/sdk@0.13.0':
|
||||||
|
resolution: {integrity: sha512-oiBAOXH6s+dKdReSsYgNktBDzbxtg4JVWhEzIxZSxKcWMdSKmBtK41MhXRO7IWAC40DguKUm3nU/Z493qPAlWA==}
|
||||||
|
peerDependencies:
|
||||||
|
axios: ^1.12.0
|
||||||
|
|
||||||
'@parcel/watcher-android-arm64@2.5.6':
|
'@parcel/watcher-android-arm64@2.5.6':
|
||||||
resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==}
|
resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
|
|
@ -149,6 +158,9 @@ packages:
|
||||||
resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}
|
resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
asynckit@0.4.0:
|
||||||
|
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||||
|
|
||||||
at-least-node@1.0.0:
|
at-least-node@1.0.0:
|
||||||
resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==}
|
resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==}
|
||||||
engines: {node: '>= 4.0.0'}
|
engines: {node: '>= 4.0.0'}
|
||||||
|
|
@ -160,6 +172,9 @@ packages:
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
postcss: ^8.1.0
|
postcss: ^8.1.0
|
||||||
|
|
||||||
|
axios@1.13.6:
|
||||||
|
resolution: {integrity: sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==}
|
||||||
|
|
||||||
balanced-match@1.0.2:
|
balanced-match@1.0.2:
|
||||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||||
|
|
||||||
|
|
@ -179,6 +194,10 @@ packages:
|
||||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
call-bind-apply-helpers@1.0.2:
|
||||||
|
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001776:
|
caniuse-lite@1.0.30001776:
|
||||||
resolution: {integrity: sha512-sg01JDPzZ9jGshqKSckOQthXnYwOEP50jeVFhaSFbZcOy05TiuuaffDOfcwtCisJ9kNQuLBFibYywv2Bgm9osw==}
|
resolution: {integrity: sha512-sg01JDPzZ9jGshqKSckOQthXnYwOEP50jeVFhaSFbZcOy05TiuuaffDOfcwtCisJ9kNQuLBFibYywv2Bgm9osw==}
|
||||||
|
|
||||||
|
|
@ -201,6 +220,10 @@ packages:
|
||||||
color-name@1.1.4:
|
color-name@1.1.4:
|
||||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||||
|
|
||||||
|
combined-stream@1.0.8:
|
||||||
|
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
concat-map@0.0.1:
|
concat-map@0.0.1:
|
||||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||||
|
|
||||||
|
|
@ -232,10 +255,18 @@ packages:
|
||||||
supports-color:
|
supports-color:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
delayed-stream@1.0.0:
|
||||||
|
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||||
|
engines: {node: '>=0.4.0'}
|
||||||
|
|
||||||
detect-libc@2.1.2:
|
detect-libc@2.1.2:
|
||||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
dunder-proto@1.0.1:
|
||||||
|
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
eastasianwidth@0.2.0:
|
eastasianwidth@0.2.0:
|
||||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||||
|
|
||||||
|
|
@ -252,6 +283,22 @@ packages:
|
||||||
resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==}
|
resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
es-define-property@1.0.1:
|
||||||
|
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
es-errors@1.3.0:
|
||||||
|
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
es-object-atoms@1.1.1:
|
||||||
|
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
es-set-tostringtag@2.1.0:
|
||||||
|
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
esbuild-android-64@0.14.54:
|
esbuild-android-64@0.14.54:
|
||||||
resolution: {integrity: sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==}
|
resolution: {integrity: sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
@ -412,10 +459,23 @@ packages:
|
||||||
resolution: {integrity: sha512-0rnQWcFwZr7eO0513HahrWafsc3CTFioEB7DRiEYCUM/70QXSY8f3mCST17HXLcPvEhzH/Ty/Bxd72ZZsr/yvw==}
|
resolution: {integrity: sha512-0rnQWcFwZr7eO0513HahrWafsc3CTFioEB7DRiEYCUM/70QXSY8f3mCST17HXLcPvEhzH/Ty/Bxd72ZZsr/yvw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
follow-redirects@1.15.11:
|
||||||
|
resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==}
|
||||||
|
engines: {node: '>=4.0'}
|
||||||
|
peerDependencies:
|
||||||
|
debug: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
debug:
|
||||||
|
optional: true
|
||||||
|
|
||||||
foreground-child@3.3.1:
|
foreground-child@3.3.1:
|
||||||
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
|
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
|
form-data@4.0.5:
|
||||||
|
resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
|
||||||
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
fraction.js@5.3.4:
|
fraction.js@5.3.4:
|
||||||
resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
|
resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
|
||||||
|
|
||||||
|
|
@ -436,6 +496,14 @@ packages:
|
||||||
generic-names@4.0.0:
|
generic-names@4.0.0:
|
||||||
resolution: {integrity: sha512-ySFolZQfw9FoDb3ed9d80Cm9f0+r7qj+HJkWjeD9RBfpxEVTlVhol+gvaQB/78WbwYfbnNh8nWHHBSlg072y6A==}
|
resolution: {integrity: sha512-ySFolZQfw9FoDb3ed9d80Cm9f0+r7qj+HJkWjeD9RBfpxEVTlVhol+gvaQB/78WbwYfbnNh8nWHHBSlg072y6A==}
|
||||||
|
|
||||||
|
get-intrinsic@1.3.0:
|
||||||
|
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
get-proto@1.0.1:
|
||||||
|
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
glob@10.5.0:
|
glob@10.5.0:
|
||||||
resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==}
|
resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==}
|
||||||
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
||||||
|
|
@ -453,6 +521,10 @@ packages:
|
||||||
resolution: {integrity: sha512-gOPiyxcD9dJGCEArAhF4Hd0BAqvAe/JzERP7tYumE4yIkmIedPUVXcJFWbV3/p/ovIIvKjkrTk+f1UVkq7vvbw==}
|
resolution: {integrity: sha512-gOPiyxcD9dJGCEArAhF4Hd0BAqvAe/JzERP7tYumE4yIkmIedPUVXcJFWbV3/p/ovIIvKjkrTk+f1UVkq7vvbw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
gopd@1.2.0:
|
||||||
|
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
graceful-fs@4.2.11:
|
graceful-fs@4.2.11:
|
||||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||||
|
|
||||||
|
|
@ -460,6 +532,14 @@ packages:
|
||||||
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
has-symbols@1.1.0:
|
||||||
|
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
has-tostringtag@1.0.2:
|
||||||
|
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
hasown@2.0.2:
|
hasown@2.0.2:
|
||||||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
@ -565,6 +645,18 @@ packages:
|
||||||
resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==}
|
resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
math-intrinsics@1.1.0:
|
||||||
|
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
mime-db@1.52.0:
|
||||||
|
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
mime-types@2.1.35:
|
||||||
|
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
mime@1.6.0:
|
mime@1.6.0:
|
||||||
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
|
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
|
|
@ -687,6 +779,9 @@ packages:
|
||||||
resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
|
resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
|
||||||
engines: {node: ^10 || ^12 || >=14}
|
engines: {node: ^10 || ^12 || >=14}
|
||||||
|
|
||||||
|
proxy-from-env@1.1.0:
|
||||||
|
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
|
||||||
|
|
||||||
prr@1.0.1:
|
prr@1.0.1:
|
||||||
resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
|
resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
|
||||||
|
|
||||||
|
|
@ -856,6 +951,10 @@ snapshots:
|
||||||
wrap-ansi: 8.1.0
|
wrap-ansi: 8.1.0
|
||||||
wrap-ansi-cjs: wrap-ansi@7.0.0
|
wrap-ansi-cjs: wrap-ansi@7.0.0
|
||||||
|
|
||||||
|
'@jellyfin/sdk@0.13.0(axios@1.13.6)':
|
||||||
|
dependencies:
|
||||||
|
axios: 1.13.6
|
||||||
|
|
||||||
'@parcel/watcher-android-arm64@2.5.6':
|
'@parcel/watcher-android-arm64@2.5.6':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
|
@ -938,6 +1037,8 @@ snapshots:
|
||||||
|
|
||||||
ansi-styles@6.2.3: {}
|
ansi-styles@6.2.3: {}
|
||||||
|
|
||||||
|
asynckit@0.4.0: {}
|
||||||
|
|
||||||
at-least-node@1.0.0: {}
|
at-least-node@1.0.0: {}
|
||||||
|
|
||||||
autoprefixer@10.4.27(postcss@8.5.8):
|
autoprefixer@10.4.27(postcss@8.5.8):
|
||||||
|
|
@ -949,6 +1050,14 @@ snapshots:
|
||||||
postcss: 8.5.8
|
postcss: 8.5.8
|
||||||
postcss-value-parser: 4.2.0
|
postcss-value-parser: 4.2.0
|
||||||
|
|
||||||
|
axios@1.13.6:
|
||||||
|
dependencies:
|
||||||
|
follow-redirects: 1.15.11
|
||||||
|
form-data: 4.0.5
|
||||||
|
proxy-from-env: 1.1.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- debug
|
||||||
|
|
||||||
balanced-match@1.0.2: {}
|
balanced-match@1.0.2: {}
|
||||||
|
|
||||||
baseline-browser-mapping@2.10.0: {}
|
baseline-browser-mapping@2.10.0: {}
|
||||||
|
|
@ -970,6 +1079,11 @@ snapshots:
|
||||||
node-releases: 2.0.27
|
node-releases: 2.0.27
|
||||||
update-browserslist-db: 1.2.3(browserslist@4.28.1)
|
update-browserslist-db: 1.2.3(browserslist@4.28.1)
|
||||||
|
|
||||||
|
call-bind-apply-helpers@1.0.2:
|
||||||
|
dependencies:
|
||||||
|
es-errors: 1.3.0
|
||||||
|
function-bind: 1.1.2
|
||||||
|
|
||||||
caniuse-lite@1.0.30001776: {}
|
caniuse-lite@1.0.30001776: {}
|
||||||
|
|
||||||
chalk@4.1.2:
|
chalk@4.1.2:
|
||||||
|
|
@ -991,6 +1105,10 @@ snapshots:
|
||||||
|
|
||||||
color-name@1.1.4: {}
|
color-name@1.1.4: {}
|
||||||
|
|
||||||
|
combined-stream@1.0.8:
|
||||||
|
dependencies:
|
||||||
|
delayed-stream: 1.0.0
|
||||||
|
|
||||||
concat-map@0.0.1: {}
|
concat-map@0.0.1: {}
|
||||||
|
|
||||||
copy-anything@2.0.6:
|
copy-anything@2.0.6:
|
||||||
|
|
@ -1016,9 +1134,17 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.3
|
ms: 2.1.3
|
||||||
|
|
||||||
|
delayed-stream@1.0.0: {}
|
||||||
|
|
||||||
detect-libc@2.1.2:
|
detect-libc@2.1.2:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
dunder-proto@1.0.1:
|
||||||
|
dependencies:
|
||||||
|
call-bind-apply-helpers: 1.0.2
|
||||||
|
es-errors: 1.3.0
|
||||||
|
gopd: 1.2.0
|
||||||
|
|
||||||
eastasianwidth@0.2.0: {}
|
eastasianwidth@0.2.0: {}
|
||||||
|
|
||||||
electron-to-chromium@1.5.307: {}
|
electron-to-chromium@1.5.307: {}
|
||||||
|
|
@ -1032,6 +1158,21 @@ snapshots:
|
||||||
prr: 1.0.1
|
prr: 1.0.1
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
es-define-property@1.0.1: {}
|
||||||
|
|
||||||
|
es-errors@1.3.0: {}
|
||||||
|
|
||||||
|
es-object-atoms@1.1.1:
|
||||||
|
dependencies:
|
||||||
|
es-errors: 1.3.0
|
||||||
|
|
||||||
|
es-set-tostringtag@2.1.0:
|
||||||
|
dependencies:
|
||||||
|
es-errors: 1.3.0
|
||||||
|
get-intrinsic: 1.3.0
|
||||||
|
has-tostringtag: 1.0.2
|
||||||
|
hasown: 2.0.2
|
||||||
|
|
||||||
esbuild-android-64@0.14.54:
|
esbuild-android-64@0.14.54:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
|
@ -1153,11 +1294,21 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
find-file-up: 0.1.3
|
find-file-up: 0.1.3
|
||||||
|
|
||||||
|
follow-redirects@1.15.11: {}
|
||||||
|
|
||||||
foreground-child@3.3.1:
|
foreground-child@3.3.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
cross-spawn: 7.0.6
|
cross-spawn: 7.0.6
|
||||||
signal-exit: 4.1.0
|
signal-exit: 4.1.0
|
||||||
|
|
||||||
|
form-data@4.0.5:
|
||||||
|
dependencies:
|
||||||
|
asynckit: 0.4.0
|
||||||
|
combined-stream: 1.0.8
|
||||||
|
es-set-tostringtag: 2.1.0
|
||||||
|
hasown: 2.0.2
|
||||||
|
mime-types: 2.1.35
|
||||||
|
|
||||||
fraction.js@5.3.4: {}
|
fraction.js@5.3.4: {}
|
||||||
|
|
||||||
fs-exists-sync@0.1.0: {}
|
fs-exists-sync@0.1.0: {}
|
||||||
|
|
@ -1177,6 +1328,24 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
loader-utils: 3.3.1
|
loader-utils: 3.3.1
|
||||||
|
|
||||||
|
get-intrinsic@1.3.0:
|
||||||
|
dependencies:
|
||||||
|
call-bind-apply-helpers: 1.0.2
|
||||||
|
es-define-property: 1.0.1
|
||||||
|
es-errors: 1.3.0
|
||||||
|
es-object-atoms: 1.1.1
|
||||||
|
function-bind: 1.1.2
|
||||||
|
get-proto: 1.0.1
|
||||||
|
gopd: 1.2.0
|
||||||
|
has-symbols: 1.1.0
|
||||||
|
hasown: 2.0.2
|
||||||
|
math-intrinsics: 1.1.0
|
||||||
|
|
||||||
|
get-proto@1.0.1:
|
||||||
|
dependencies:
|
||||||
|
dunder-proto: 1.0.1
|
||||||
|
es-object-atoms: 1.1.1
|
||||||
|
|
||||||
glob@10.5.0:
|
glob@10.5.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
foreground-child: 3.3.1
|
foreground-child: 3.3.1
|
||||||
|
|
@ -1207,10 +1376,18 @@ snapshots:
|
||||||
is-windows: 0.2.0
|
is-windows: 0.2.0
|
||||||
which: 1.3.1
|
which: 1.3.1
|
||||||
|
|
||||||
|
gopd@1.2.0: {}
|
||||||
|
|
||||||
graceful-fs@4.2.11: {}
|
graceful-fs@4.2.11: {}
|
||||||
|
|
||||||
has-flag@4.0.0: {}
|
has-flag@4.0.0: {}
|
||||||
|
|
||||||
|
has-symbols@1.1.0: {}
|
||||||
|
|
||||||
|
has-tostringtag@1.0.2:
|
||||||
|
dependencies:
|
||||||
|
has-symbols: 1.1.0
|
||||||
|
|
||||||
hasown@2.0.2:
|
hasown@2.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
function-bind: 1.1.2
|
function-bind: 1.1.2
|
||||||
|
|
@ -1314,6 +1491,14 @@ snapshots:
|
||||||
semver: 5.7.2
|
semver: 5.7.2
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
math-intrinsics@1.1.0: {}
|
||||||
|
|
||||||
|
mime-db@1.52.0: {}
|
||||||
|
|
||||||
|
mime-types@2.1.35:
|
||||||
|
dependencies:
|
||||||
|
mime-db: 1.52.0
|
||||||
|
|
||||||
mime@1.6.0:
|
mime@1.6.0:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
|
@ -1421,6 +1606,8 @@ snapshots:
|
||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
source-map-js: 1.2.1
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
|
proxy-from-env@1.1.0: {}
|
||||||
|
|
||||||
prr@1.0.1:
|
prr@1.0.1:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
|
|
||||||
76
src/app.tsx
76
src/app.tsx
|
|
@ -1,8 +1,82 @@
|
||||||
|
// TODO: hijack search result, use that as song URI
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { Api, Jellyfin } from "@jellyfin/sdk";
|
||||||
|
import SettingsModal from "./settings";
|
||||||
|
|
||||||
|
const audio = new Audio("https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3");
|
||||||
|
let hijackActive = false;
|
||||||
|
|
||||||
|
export const jellyfin = new Jellyfin({
|
||||||
|
clientInfo: {
|
||||||
|
name: "Spicetify",
|
||||||
|
version: "1.0.0",
|
||||||
|
},
|
||||||
|
deviceInfo: {
|
||||||
|
name: "Spotify",
|
||||||
|
id: "spotify", // TODO: should be unique?
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export let jellyfinApi: Api | undefined;
|
||||||
|
export const setJellyfinApi = (api: Api) => {
|
||||||
|
jellyfinApi = api;
|
||||||
|
};
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
while (!Spicetify?.showNotification) {
|
while (!Spicetify.showNotification || !Spicetify.Platform.History) {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
new Spicetify.Topbar.Button("Jellyfin", "podcasts", () => {
|
||||||
|
Spicetify.PopupModal.display({
|
||||||
|
title: "Jellyfin",
|
||||||
|
content: React.createElement(SettingsModal) as unknown as Element,
|
||||||
|
isLarge: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Spicetify.Platform.History.listen((location) => {
|
||||||
|
if (location.pathname.startsWith("/search/")) {
|
||||||
|
const segments = location.pathname.split("/");
|
||||||
|
const query = segments[2];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Spicetify.Player.addEventListener("songchange", async (event) => {
|
||||||
|
// if (event?.data.item.uri === "spotify:track:72wehM3q2RVZb4XLmAkyTr") {
|
||||||
|
const oldVolume = Spicetify.Player.getVolume();
|
||||||
|
await audio.play();
|
||||||
|
|
||||||
|
Spicetify.Player.setVolume(0);
|
||||||
|
hijackActive = true;
|
||||||
|
Spicetify.Player.setVolume(oldVolume);
|
||||||
|
});
|
||||||
|
|
||||||
|
Spicetify.Player.addEventListener("onplaypause", async (event) => {
|
||||||
|
if (!hijackActive) return;
|
||||||
|
|
||||||
|
if (event?.data.isPaused) {
|
||||||
|
audio.pause();
|
||||||
|
} else {
|
||||||
|
await audio.play();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const playback = Spicetify.Platform.PlaybackAPI;
|
||||||
|
|
||||||
|
// Change volume of Jellyfin audio instead of Spotify audio
|
||||||
|
playback.setVolume = new Proxy(playback.setVolume, {
|
||||||
|
apply(target, thisArg, args) {
|
||||||
|
if (hijackActive) {
|
||||||
|
audio.volume = args[0];
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
return Reflect.apply(target, thisArg, args);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Show message on start.
|
// Show message on start.
|
||||||
Spicetify.showNotification("Hello!");
|
Spicetify.showNotification("Hello!");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
173
src/settings.tsx
Normal file
173
src/settings.tsx
Normal file
|
|
@ -0,0 +1,173 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { getUserApi } from "@jellyfin/sdk/lib/utils/api/user-api";
|
||||||
|
|
||||||
|
import styles from "./styles.module.css";
|
||||||
|
import { jellyfin, setJellyfinApi } from "./app";
|
||||||
|
|
||||||
|
export default function SettingsModal() {
|
||||||
|
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||||
|
const [url, setUrl] = useState("");
|
||||||
|
const [username, setUsername] = useState("");
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [isUsingQuickConnect, setIsUsingQuickConnect] = useState(false);
|
||||||
|
const [quickConnectCode, setQuickConnectCode] = useState("");
|
||||||
|
|
||||||
|
const [isFocused, setIsFocused] = useState(false);
|
||||||
|
|
||||||
|
const login = async () => {
|
||||||
|
const servers = await jellyfin.discovery.getRecommendedServerCandidates(url);
|
||||||
|
const best = jellyfin.discovery.findBestServer(servers);
|
||||||
|
if (!best) {
|
||||||
|
Spicetify.showNotification("Failed to connect to server!", true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const api = jellyfin.createApi(best.address);
|
||||||
|
const userApi = getUserApi(api);
|
||||||
|
|
||||||
|
const auth =
|
||||||
|
isUsingQuickConnect && quickConnectCode.toString().length === 6
|
||||||
|
? await userApi.authenticateWithQuickConnect({
|
||||||
|
quickConnectDto: { Secret: "111000" },
|
||||||
|
})
|
||||||
|
: await userApi.authenticateUserByName({
|
||||||
|
authenticateUserByName: { Username: username, Pw: password },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!auth.data.AccessToken) {
|
||||||
|
Spicetify.showNotification("Failed to login!", true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setJellyfinApi(api);
|
||||||
|
setIsLoggedIn(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isLoggedIn)
|
||||||
|
return (
|
||||||
|
<div className={styles.modal}>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 512 512">
|
||||||
|
<path fill="#ffb636" d="M378.553 355.648L45.117 500.733c-21.735 8.65-43.335-12.764-34.874-34.572l145.709-338.684" />
|
||||||
|
<path
|
||||||
|
fill="#ffd469"
|
||||||
|
d="m10.243 466.161l11.58-26.916l2.977-4.543c57.597-87.744 116.038-174.952 176.475-260.768l67.765 69.46C217.91 278.496 51.89 450.063 17.115 495.571c-7.57-6.963-11.249-18.128-6.872-29.41"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="#a06c33"
|
||||||
|
d="M304.382 204.434c61.854 61.854 95.685 128.308 75.564 148.43c-20.121 20.121-86.575-13.71-148.43-75.564s-95.685-128.308-75.564-148.43s86.575 13.709 148.43 75.564"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="#f7f9aa"
|
||||||
|
d="M155.601 327.572c0 6.012-4.874 10.885-10.885 10.885s-10.885-4.873-10.885-10.885s4.873-10.885 10.885-10.885s10.885 4.873 10.885 10.885"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="#ffb636"
|
||||||
|
d="M501.986 213.16c0 8.628-6.994 15.622-15.622 15.622s-15.622-6.994-15.622-15.622s6.994-15.622 15.622-15.622s15.622 6.994 15.622 15.622M397.663 421.182c-8.628 0-15.622 6.994-15.622 15.622s6.994 15.622 15.622 15.622s15.622-6.994 15.622-15.622s-6.995-15.622-15.622-15.622"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="#bea4ff"
|
||||||
|
d="M355.949 79.523c-1.34 9.065-7.197 17.072-16.07 21.968c-6.126 3.38-13.33 5.137-20.807 5.137a49 49 0 0 1-7.117-.526c-5.288-.782-10.581.016-14.52 2.189c-1.766.974-4.8 3.105-5.293 6.438c-.492 3.333 1.796 6.251 3.203 7.694c3.058 3.135 7.725 5.381 12.849 6.22c.141.015.281.02.422.041c21.619 3.196 37.061 20.32 34.421 38.173c-1.34 9.066-7.197 17.073-16.071 21.969c-6.126 3.38-13.329 5.137-20.806 5.137a49 49 0 0 1-7.117-.526c-5.287-.783-10.582.015-14.521 2.189c-1.766.974-4.8 3.105-5.293 6.438c-.79 5.349 5.778 12.411 16.47 13.991c5.817.86 9.836 6.273 8.976 12.091c-.782 5.29-5.328 9.092-10.52 9.092q-.779 0-1.571-.116c-21.619-3.196-37.06-20.321-34.421-38.173c1.34-9.066 7.197-17.073 16.071-21.969c8.055-4.444 17.972-6.082 27.924-4.611c5.288.781 10.58-.016 14.52-2.189c1.766-.974 4.8-3.105 5.293-6.438c.777-5.262-5.577-12.171-15.963-13.898c-.17-.017-.341-.031-.512-.056c-9.951-1.472-18.971-5.908-25.395-12.493c-7.077-7.254-10.367-16.614-9.026-25.681c1.34-9.065 7.197-17.072 16.07-21.968c8.055-4.444 17.972-6.082 27.924-4.611c5.286.78 10.581-.016 14.52-2.189c1.766-.974 4.8-3.105 5.293-6.438c.492-3.333-1.796-6.251-3.203-7.694c-3.142-3.22-7.977-5.516-13.267-6.297c-5.817-.86-9.836-6.273-8.976-12.091s6.274-9.832 12.091-8.977c9.951 1.472 18.971 5.908 25.395 12.493c7.078 7.255 10.368 16.615 9.027 25.681"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="#ff6e83"
|
||||||
|
d="M81.731 159.689c0 9.777-7.926 17.703-17.703 17.703s-17.703-7.926-17.703-17.703s7.926-17.703 17.703-17.703s17.703 7.925 17.703 17.703m316.445-20.453c-11.296 0-20.452 9.157-20.452 20.452s9.157 20.452 20.452 20.452s20.452-9.157 20.452-20.452s-9.156-20.452-20.452-20.452M215.529 395.899c-11.296 0-20.452 9.157-20.452 20.452s9.157 20.452 20.452 20.452s20.452-9.157 20.452-20.452s-9.156-20.452-20.452-20.452m271.303-93.646c3.093-5.989.745-13.352-5.244-16.445c-2.388-1.232-5.238-2.868-8.538-4.761c-28.993-16.633-89.319-51.242-160.352 6.109c-5.245 4.234-6.063 11.919-1.829 17.163c4.233 5.245 11.917 6.065 17.163 1.829c58.035-46.856 104.882-19.985 132.871-3.928c3.403 1.952 6.617 3.796 9.483 5.276a12.205 12.205 0 0 0 16.446-5.243"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="#59cafc"
|
||||||
|
d="M434.834 62.776c0 6.012-4.874 10.885-10.885 10.885s-10.885-4.873-10.885-10.885s4.873-10.885 10.885-10.885c6.012-.001 10.885 4.873 10.885 10.885M46.324 11.894c-6.012 0-10.885 4.873-10.885 10.885s4.873 10.885 10.885 10.885S57.21 28.791 57.21 22.779s-4.874-10.885-10.886-10.885m170.681 142.057c1.231-2.414 2.749-5.163 4.356-8.073c8.154-14.771 19.32-34.999 19.992-58.559c.807-28.304-13.934-54.002-43.812-76.38c-5.187-3.885-12.539-2.828-16.421 2.357c-3.884 5.186-2.829 12.538 2.357 16.421c23.75 17.788 35.01 36.411 34.425 56.933c-.51 17.872-9.697 34.516-17.08 47.889c-1.701 3.083-3.309 5.994-4.713 8.747c-2.945 5.771-.654 12.836 5.116 15.781a11.7 11.7 0 0 0 5.323 1.285a11.73 11.73 0 0 0 10.457-6.401"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<p className={styles.logged_in}>You're logged in!</p>
|
||||||
|
|
||||||
|
<select name="" id="">
|
||||||
|
<option value="">Source</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<hr style={{ width: "100%", margin: "1rem 0" }} className={styles.hr} />
|
||||||
|
<button onClick={() => setIsLoggedIn(false)} className={styles.submit}>
|
||||||
|
Log out
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.modal}>
|
||||||
|
{isUsingQuickConnect ? (
|
||||||
|
<div className={styles.input_container}>
|
||||||
|
<label htmlFor="code">Code</label>
|
||||||
|
|
||||||
|
<div className={styles.quick_connect_wrapper}>
|
||||||
|
<input
|
||||||
|
id="quick-connect"
|
||||||
|
type="text"
|
||||||
|
inputMode="numeric"
|
||||||
|
maxLength={6}
|
||||||
|
value={quickConnectCode!}
|
||||||
|
onChange={(e) => setQuickConnectCode(e.target.value.replace(/\D/g, ""))}
|
||||||
|
onFocus={() => setIsFocused(true)}
|
||||||
|
onBlur={() => setIsFocused(false)}
|
||||||
|
// Force caret to always be at the end
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (["ArrowLeft", "ArrowRight", "Home", "End"].includes(e.key)) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
// Same here
|
||||||
|
onSelect={(e) => {
|
||||||
|
const element = e.target as HTMLInputElement;
|
||||||
|
element.setSelectionRange(element.value.length, element.value.length);
|
||||||
|
}}
|
||||||
|
className={styles.quick_connect_input}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{Array.from({ length: 6 }).map((_, i) => (
|
||||||
|
<div key={i} className={`${styles.quick_connect_box} ${isFocused && quickConnectCode.length === i ? styles.quick_connect_box_active : ""}`}>
|
||||||
|
{quickConnectCode[i]}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className={styles.input_container}>
|
||||||
|
<label htmlFor="url">URL</label>
|
||||||
|
<input id="url" type="text" placeholder="Enter Jellyfin URL..." value={url} onChange={(e) => setUrl(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.input_container}>
|
||||||
|
<label htmlFor="username">Username</label>
|
||||||
|
<input id="username" type="text" placeholder="Enter username..." value={username} onChange={(e) => setUsername(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.input_container}>
|
||||||
|
<label htmlFor="password">Password</label>
|
||||||
|
<input id="password" type="password" placeholder="Enter password..." value={password} onChange={(e) => setPassword(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className={styles.separator}>
|
||||||
|
<hr className={styles.hr} />
|
||||||
|
<span>or</span>
|
||||||
|
<hr className={styles.hr} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setIsUsingQuickConnect((prev) => {
|
||||||
|
if (!prev) {
|
||||||
|
document.getElementById("quick-connect")?.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
return !prev;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
className={`${styles.quick_connect} ${styles.button}`}
|
||||||
|
>
|
||||||
|
{isUsingQuickConnect ? "Username/Password" : "Quick Connect"}
|
||||||
|
</button>
|
||||||
|
<button onClick={login} className={styles.button}>
|
||||||
|
Submit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
98
src/styles.module.css
Normal file
98
src/styles.module.css
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
.button {
|
||||||
|
background-color: var(--spice-button);
|
||||||
|
color: var(--spice-text);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
cursor: pointer;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hr {
|
||||||
|
flex-grow: 1;
|
||||||
|
border: none;
|
||||||
|
border-top: 1px solid var(--spice-button-disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logged_in {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input_container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input_container label {
|
||||||
|
color: var(--spice-subtext);
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input_container input,
|
||||||
|
.quick_connect_box {
|
||||||
|
background-color: var(--spice-main-elevated);
|
||||||
|
color: var(--spice-text);
|
||||||
|
border: 1px solid var(--spice-card);
|
||||||
|
padding: 0.5rem 0.6rem;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
transition: 200ms border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input_container input:focus,
|
||||||
|
.quick_connect_box_active {
|
||||||
|
border-color: var(--spice-button);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input_container input::placeholder {
|
||||||
|
color: var(--spice-subtext);
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.separator {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
color: var(--spice-subtext);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick_connect {
|
||||||
|
background-color: var(--spice-main-elevated);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick_connect_wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(6, 1fr);
|
||||||
|
gap: 0.25rem;
|
||||||
|
height: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick_connect_input {
|
||||||
|
background-color: transparent !important;
|
||||||
|
border-color: transparent !important;
|
||||||
|
color: transparent !important;
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick_connect_box {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
54
src/types/spicetify.d.ts
vendored
54
src/types/spicetify.d.ts
vendored
|
|
@ -325,8 +325,8 @@ declare namespace Spicetify {
|
||||||
*/
|
*/
|
||||||
container: HTMLElement;
|
container: HTMLElement;
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
) => void
|
) => void,
|
||||||
): void;
|
): void;
|
||||||
/**
|
/**
|
||||||
* Skip to previous track.
|
* Skip to previous track.
|
||||||
|
|
@ -519,19 +519,8 @@ declare namespace Spicetify {
|
||||||
function put(url: string, body?: Body, headers?: Headers): Promise<Response["body"]>;
|
function put(url: string, body?: Body, headers?: Headers): Promise<Response["body"]>;
|
||||||
function del(url: string, body?: Body, headers?: Headers): Promise<Response["body"]>;
|
function del(url: string, body?: Body, headers?: Headers): Promise<Response["body"]>;
|
||||||
function patch(url: string, body?: Body, headers?: Headers): Promise<Response["body"]>;
|
function patch(url: string, body?: Body, headers?: Headers): Promise<Response["body"]>;
|
||||||
function sub(
|
function sub(url: string, callback: (b: Response["body"]) => void, onError?: (e: Error) => void, body?: Body, headers?: Headers): Promise<Response["body"]>;
|
||||||
url: string,
|
function postSub(url: string, body: Body | null, callback: (b: Response["body"]) => void, onError?: (e: Error) => void): Promise<Response["body"]>;
|
||||||
callback: (b: Response["body"]) => void,
|
|
||||||
onError?: (e: Error) => void,
|
|
||||||
body?: Body,
|
|
||||||
headers?: Headers
|
|
||||||
): Promise<Response["body"]>;
|
|
||||||
function postSub(
|
|
||||||
url: string,
|
|
||||||
body: Body | null,
|
|
||||||
callback: (b: Response["body"]) => void,
|
|
||||||
onError?: (e: Error) => void
|
|
||||||
): Promise<Response["body"]>;
|
|
||||||
function request(method: Method, url: string, body?: Body, headers?: Headers): Promise<Response>;
|
function request(method: Method, url: string, body?: Body, headers?: Headers): Promise<Response>;
|
||||||
function resolve(method: Method, url: string, body?: Body, headers?: Headers): Promise<Response>;
|
function resolve(method: Method, url: string, body?: Body, headers?: Headers): Promise<Response>;
|
||||||
}
|
}
|
||||||
|
|
@ -786,7 +775,18 @@ declare namespace Spicetify {
|
||||||
* Contains vast array of internal APIs.
|
* Contains vast array of internal APIs.
|
||||||
* Please explore in Devtool Console.
|
* Please explore in Devtool Console.
|
||||||
*/
|
*/
|
||||||
const Platform: any;
|
const Platform: {
|
||||||
|
PlaybackAPI: any;
|
||||||
|
History: {
|
||||||
|
push: (path: Location | string) => void;
|
||||||
|
replace: (path: Location | string) => void;
|
||||||
|
goBack: () => void;
|
||||||
|
goForward: () => void;
|
||||||
|
listen: (listener: (location: Location) => void) => () => void;
|
||||||
|
entries: Location[];
|
||||||
|
location: Location;
|
||||||
|
};
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* Queue object contains list of queuing tracks,
|
* Queue object contains list of queuing tracks,
|
||||||
* history of played tracks and current track metadata.
|
* history of played tracks and current track metadata.
|
||||||
|
|
@ -1833,14 +1833,7 @@ declare namespace Spicetify {
|
||||||
* Create a button on the right side of the playbar
|
* Create a button on the right side of the playbar
|
||||||
*/
|
*/
|
||||||
class Button {
|
class Button {
|
||||||
constructor(
|
constructor(label: string, icon: Icon | string, onClick?: (self: Button) => void, disabled?: boolean, active?: boolean, registerOnCreate?: boolean);
|
||||||
label: string,
|
|
||||||
icon: Icon | string,
|
|
||||||
onClick?: (self: Button) => void,
|
|
||||||
disabled?: boolean,
|
|
||||||
active?: boolean,
|
|
||||||
registerOnCreate?: boolean
|
|
||||||
);
|
|
||||||
label: string;
|
label: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
onClick: (self: Button) => void;
|
onClick: (self: Button) => void;
|
||||||
|
|
@ -1856,14 +1849,7 @@ declare namespace Spicetify {
|
||||||
* Create a widget next to track info
|
* Create a widget next to track info
|
||||||
*/
|
*/
|
||||||
class Widget {
|
class Widget {
|
||||||
constructor(
|
constructor(label: string, icon: Icon | string, onClick?: (self: Widget) => void, disabled?: boolean, active?: boolean, registerOnCreate?: boolean);
|
||||||
label: string,
|
|
||||||
icon: Icon | string,
|
|
||||||
onClick?: (self: Widget) => void,
|
|
||||||
disabled?: boolean,
|
|
||||||
active?: boolean,
|
|
||||||
registerOnCreate?: boolean
|
|
||||||
);
|
|
||||||
label: string;
|
label: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
onClick: (self: Widget) => void;
|
onClick: (self: Widget) => void;
|
||||||
|
|
@ -2056,7 +2042,7 @@ declare namespace Spicetify {
|
||||||
* @return Function to handle GraphQL queries
|
* @return Function to handle GraphQL queries
|
||||||
*/
|
*/
|
||||||
function Handler(
|
function Handler(
|
||||||
context: Record<string, any>
|
context: Record<string, any>,
|
||||||
): (query: (typeof Definitions)[Query | string], variables?: Record<string, any>, context?: Record<string, any>) => Promise<any>;
|
): (query: (typeof Definitions)[Query | string], variables?: Record<string, any>, context?: Record<string, any>) => Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2077,7 +2063,7 @@ declare namespace Spicetify {
|
||||||
label?: string,
|
label?: string,
|
||||||
contextUri?: string,
|
contextUri?: string,
|
||||||
sectionIndex?: number,
|
sectionIndex?: number,
|
||||||
dropOriginUri?: string
|
dropOriginUri?: string,
|
||||||
): (event: React.DragEvent, uris?: string[], label?: string, contextUri?: string, sectionIndex?: number) => void;
|
): (event: React.DragEvent, uris?: string[], label?: string, contextUri?: string, sectionIndex?: number) => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue