mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 04:20:12 +02:00
Compare commits
38 Commits
v1.11.0
...
development
| Author | SHA1 | Date | |
|---|---|---|---|
| 324936e0c8 | |||
| 868ec15b16 | |||
| 775c4e68fa | |||
| 34e0c4bd4a | |||
| 323130a877 | |||
| 3b2aab74ac | |||
| bc7ef0624b | |||
| 304ce8b881 | |||
| 01011a49a2 | |||
| d24ca04878 | |||
| 640d38e5a9 | |||
| ac0c074d4b | |||
| 6be5818493 | |||
| 03edd5a639 | |||
| f5eb3f1488 | |||
| 8eab9edb15 | |||
| fcc69980e4 | |||
| 053b78a3fd | |||
| 42ded966e4 | |||
| ea9119431c | |||
| add0345f10 | |||
| e5a8324a79 | |||
| cc4e933c07 | |||
| 382d279dad | |||
| b99899f128 | |||
| f5839bf39c | |||
| 914ed5b8f3 | |||
| ca0a1569f8 | |||
| 9f10fe398a | |||
| 8869278898 | |||
| 16c9e6cc1b | |||
| 2a6e9b6ad3 | |||
| 167b42df2b | |||
| e6a2bc3acf | |||
| ca3c7015c6 | |||
| c7c15d917a | |||
| 6adb29bc38 | |||
| 2c3cd7af24 |
@@ -121,7 +121,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [windows-latest, macos-latest, ubuntu-latest]
|
||||
os: [windows-latest, macos-26, ubuntu-latest]
|
||||
|
||||
steps:
|
||||
- name: Checkout git repo
|
||||
@@ -156,7 +156,7 @@ jobs:
|
||||
on_retry_command: pnpm cache delete
|
||||
|
||||
- name: Build and Publish to R2 (macOS)
|
||||
if: matrix.os == 'macos-latest'
|
||||
if: matrix.os == 'macos-26'
|
||||
uses: nick-invision/retry@v3.0.2
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
|
||||
@@ -115,7 +115,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [windows-latest, macos-latest, ubuntu-latest]
|
||||
os: [windows-latest, macos-26, ubuntu-latest]
|
||||
|
||||
steps:
|
||||
- name: Checkout git repo
|
||||
@@ -156,7 +156,7 @@ jobs:
|
||||
on_retry_command: pnpm cache delete
|
||||
|
||||
- name: Build and Publish releases (macOS)
|
||||
if: matrix.os == 'macos-latest'
|
||||
if: matrix.os == 'macos-26'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
uses: nick-invision/retry@v3.0.2
|
||||
|
||||
@@ -8,7 +8,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest]
|
||||
os: [macos-26]
|
||||
|
||||
steps:
|
||||
- name: Checkout git repo
|
||||
@@ -24,6 +24,7 @@ jobs:
|
||||
|
||||
- name: Build and Publish releases
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=4096
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
uses: nick-invision/retry@v3.0.2
|
||||
with:
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||
os: [macos-26, ubuntu-latest, windows-latest]
|
||||
|
||||
steps:
|
||||
- name: Checkout git repo
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
pnpm run package:linux:pr
|
||||
|
||||
- name: Build for MacOS
|
||||
if: ${{ matrix.os == 'macos-latest' }}
|
||||
if: ${{ matrix.os == 'macos-26' }}
|
||||
uses: nick-invision/retry@v3.0.2
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
@@ -86,7 +86,7 @@ jobs:
|
||||
zip -r dist/linux-binaries.zip dist/*.{AppImage,deb,rpm}
|
||||
|
||||
- name: Zip MacOS Binaries
|
||||
if: ${{ matrix.os == 'macos-latest' }}
|
||||
if: ${{ matrix.os == 'macos-26' }}
|
||||
run: |
|
||||
zip -r dist/macos-binaries.zip dist/*.dmg
|
||||
|
||||
@@ -105,7 +105,7 @@ jobs:
|
||||
path: dist/linux-binaries.zip
|
||||
|
||||
- name: Upload MacOS Binaries
|
||||
if: ${{ matrix.os == 'macos-latest' }}
|
||||
if: ${{ matrix.os == 'macos-26' }}
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: macos-binaries
|
||||
|
||||
@@ -8,7 +8,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [windows-latest, macos-latest, ubuntu-latest]
|
||||
os: [windows-latest, macos-26, ubuntu-latest]
|
||||
|
||||
steps:
|
||||
- name: Checkout git repo
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
on_retry_command: pnpm cache delete
|
||||
|
||||
- name: Build and Publish releases (macOS)
|
||||
if: matrix.os == 'macos-latest'
|
||||
if: matrix.os == 'macos-26'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
uses: nick-invision/retry@v3.0.2
|
||||
|
||||
Binary file not shown.
@@ -40,13 +40,15 @@ mac:
|
||||
arch:
|
||||
- arm64
|
||||
- x64
|
||||
icon: assets/icons/icon.icns
|
||||
icon: media/feishin.icon
|
||||
type: distribution
|
||||
hardenedRuntime: false
|
||||
identity: "-"
|
||||
identity: '-'
|
||||
gatekeeperAssess: false
|
||||
notarize: false
|
||||
|
||||
extendInfo:
|
||||
NSAudioCaptureUsageDescription: "System audio access is required for mpv visualizer capture in Feishin"
|
||||
NSLocalNetworkUsageDescription: 'Local network is necessary for accessing servers hosted on the same system as Feishin'
|
||||
|
||||
dmg:
|
||||
contents: [{ x: 130, y: 220 }, { x: 410, y: 220, type: link, path: /Applications }]
|
||||
@@ -61,7 +63,7 @@ linux:
|
||||
artifactName: ${productName}-${os}-${arch}.${ext}
|
||||
|
||||
toolsets:
|
||||
appimage: "1.0.2"
|
||||
appimage: '1.0.2'
|
||||
|
||||
npmRebuild: false
|
||||
|
||||
|
||||
@@ -40,12 +40,15 @@ mac:
|
||||
arch:
|
||||
- arm64
|
||||
- x64
|
||||
icon: assets/icons/icon.icns
|
||||
icon: media/feishin.icon
|
||||
type: distribution
|
||||
hardenedRuntime: false
|
||||
identity: "-"
|
||||
identity: '-'
|
||||
gatekeeperAssess: false
|
||||
notarize: false
|
||||
extendInfo:
|
||||
NSAudioCaptureUsageDescription: "System audio access is required for mpv visualizer capture in Feishin"
|
||||
NSLocalNetworkUsageDescription: 'Local network is necessary for accessing servers hosted on the same system as Feishin'
|
||||
|
||||
dmg:
|
||||
contents: [{ x: 130, y: 220 }, { x: 410, y: 220, type: link, path: /Applications }]
|
||||
@@ -60,7 +63,7 @@ linux:
|
||||
artifactName: ${productName}-${os}-${arch}.${ext}
|
||||
|
||||
toolsets:
|
||||
appimage: "1.0.2"
|
||||
appimage: '1.0.2'
|
||||
|
||||
npmRebuild: false
|
||||
publish:
|
||||
|
||||
@@ -40,13 +40,14 @@ mac:
|
||||
arch:
|
||||
- arm64
|
||||
- x64
|
||||
icon: assets/icons/icon.icns
|
||||
icon: media/feishin.icon
|
||||
type: distribution
|
||||
hardenedRuntime: false
|
||||
identity: '-'
|
||||
gatekeeperAssess: false
|
||||
notarize: false
|
||||
extendInfo:
|
||||
NSAudioCaptureUsageDescription: 'System audio access is required for mpv visualizer capture in Feishin'
|
||||
NSLocalNetworkUsageDescription: 'Local network is necessary for accessing servers hosted on the same system as Feishin'
|
||||
|
||||
dmg:
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512"><g style="display:inline" transform="translate(-53.452 -43.352)scale(1.11813)"><circle cx="256" cy="240.312" r="21.5" style="opacity:1;fill:#000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.19597;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke;filter:url(#filter249)"/><path d="M220.85 277.951 183.5 315.6l36 36.1 20-19.7s5.856-6.2 16.5-6.2 16.5 6.2 16.5 6.2l20 19.7 36-36.1-37.35-37.649A51.5 51.5 0 0 1 256 291.812a51.5 51.5 0 0 1-35.15-13.86" style="opacity:1;fill:#000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter249)"/><path d="M256 145.4a25.7 25.7 0 0 0-18.229 7.551L66.97 323.47A25.42 25.42 0 0 0 59.5 341.5c0 14.083 11.417 25.5 25.5 25.5a25.42 25.42 0 0 0 18.031-7.469l103.895-103.597a51.5 51.5 0 0 1-2.426-15.621 51.5 51.5 0 0 1 51.5-51.5 51.5 51.5 0 0 1 51.5 51.5 51.5 51.5 0 0 1-2.426 15.62L408.97 359.532A25.42 25.42 0 0 0 427 367c14.083 0 25.5-11.417 25.5-25.5a25.42 25.42 0 0 0-7.469-18.031L274.23 152.95a25.7 25.7 0 0 0-18.229-7.55" style="display:inline;opacity:1;fill:#000;fill-opacity:1;stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round;paint-order:markers fill stroke;filter:url(#filter249)"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,202 @@
|
||||
{
|
||||
"fill-specializations" : [
|
||||
{
|
||||
"value" : {
|
||||
"linear-gradient" : [
|
||||
"display-p3:0.87416,0.87416,0.87416,1.00000",
|
||||
"display-p3:0.99575,0.99575,0.99575,1.00000"
|
||||
],
|
||||
"orientation" : {
|
||||
"start" : {
|
||||
"x" : 0.5,
|
||||
"y" : 1
|
||||
},
|
||||
"stop" : {
|
||||
"x" : 0.5,
|
||||
"y" : 0.3
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"appearance" : "dark",
|
||||
"value" : "system-dark"
|
||||
}
|
||||
],
|
||||
"groups" : [
|
||||
{
|
||||
"blend-mode-specializations" : [
|
||||
{
|
||||
"appearance" : "tinted",
|
||||
"value" : "normal"
|
||||
}
|
||||
],
|
||||
"blur-material-specializations" : [
|
||||
{
|
||||
"value" : 0.7
|
||||
},
|
||||
{
|
||||
"appearance" : "dark",
|
||||
"value" : 0.7
|
||||
},
|
||||
{
|
||||
"appearance" : "tinted",
|
||||
"value" : null
|
||||
}
|
||||
],
|
||||
"hidden" : false,
|
||||
"layers" : [
|
||||
{
|
||||
"blend-mode-specializations" : [
|
||||
{
|
||||
"appearance" : "tinted",
|
||||
"value" : "normal"
|
||||
}
|
||||
],
|
||||
"fill-specializations" : [
|
||||
{
|
||||
"value" : {
|
||||
"solid" : "extended-gray:0.00000,1.00000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"appearance" : "dark",
|
||||
"value" : {
|
||||
"linear-gradient" : [
|
||||
"display-p3:0.78674,0.78674,0.78674,1.00000",
|
||||
"display-p3:0.87416,0.87416,0.87416,1.00000"
|
||||
],
|
||||
"orientation" : {
|
||||
"start" : {
|
||||
"x" : 0.5,
|
||||
"y" : 1
|
||||
},
|
||||
"stop" : {
|
||||
"x" : 0.5,
|
||||
"y" : 0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"appearance" : "tinted",
|
||||
"value" : {
|
||||
"solid" : "gray:1.00000,1.00000"
|
||||
}
|
||||
}
|
||||
],
|
||||
"glass-specializations" : [
|
||||
{
|
||||
"value" : true
|
||||
},
|
||||
{
|
||||
"appearance" : "dark",
|
||||
"value" : true
|
||||
},
|
||||
{
|
||||
"appearance" : "tinted",
|
||||
"value" : true
|
||||
}
|
||||
],
|
||||
"hidden" : false,
|
||||
"image-name" : "feishin.svg",
|
||||
"name" : "feishin",
|
||||
"opacity-specializations" : [
|
||||
{
|
||||
"value" : 1
|
||||
},
|
||||
{
|
||||
"appearance" : "tinted",
|
||||
"value" : 1
|
||||
}
|
||||
],
|
||||
"position" : {
|
||||
"scale" : 0.79,
|
||||
"translation-in-points" : [
|
||||
18,
|
||||
-2
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"lighting-specializations" : [
|
||||
{
|
||||
"value" : "individual"
|
||||
},
|
||||
{
|
||||
"appearance" : "tinted",
|
||||
"value" : "combined"
|
||||
}
|
||||
],
|
||||
"position" : {
|
||||
"scale" : 2.2,
|
||||
"translation-in-points" : [
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
"shadow-specializations" : [
|
||||
{
|
||||
"value" : {
|
||||
"kind" : "neutral",
|
||||
"opacity" : 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"appearance" : "dark",
|
||||
"value" : {
|
||||
"kind" : "layer-color",
|
||||
"opacity" : 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"appearance" : "tinted",
|
||||
"value" : {
|
||||
"kind" : "neutral",
|
||||
"opacity" : 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"specular-specializations" : [
|
||||
{
|
||||
"value" : false
|
||||
},
|
||||
{
|
||||
"appearance" : "dark",
|
||||
"value" : false
|
||||
},
|
||||
{
|
||||
"appearance" : "tinted",
|
||||
"value" : true
|
||||
}
|
||||
],
|
||||
"translucency-specializations" : [
|
||||
{
|
||||
"value" : {
|
||||
"enabled" : true,
|
||||
"value" : 0.29
|
||||
}
|
||||
},
|
||||
{
|
||||
"appearance" : "dark",
|
||||
"value" : {
|
||||
"enabled" : false,
|
||||
"value" : 0.29
|
||||
}
|
||||
},
|
||||
{
|
||||
"appearance" : "tinted",
|
||||
"value" : {
|
||||
"enabled" : true,
|
||||
"value" : 0.5
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"supported-platforms" : {
|
||||
"squares" : [
|
||||
"macOS"
|
||||
]
|
||||
}
|
||||
}
|
||||
+7
-7
@@ -78,13 +78,13 @@
|
||||
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.1.0",
|
||||
"@electron-toolkit/preload": "^3.0.2",
|
||||
"@electron-toolkit/utils": "^4.0.0",
|
||||
"@mantine/colors-generator": "^8.3.18",
|
||||
"@mantine/core": "^8.3.18",
|
||||
"@mantine/dates": "^8.3.18",
|
||||
"@mantine/form": "^8.3.18",
|
||||
"@mantine/hooks": "^8.3.18",
|
||||
"@mantine/modals": "^8.3.18",
|
||||
"@mantine/notifications": "^8.3.18",
|
||||
"@mantine/colors-generator": "^9.1.1",
|
||||
"@mantine/core": "^9.1.1",
|
||||
"@mantine/dates": "^9.1.1",
|
||||
"@mantine/form": "^9.1.1",
|
||||
"@mantine/hooks": "^9.1.1",
|
||||
"@mantine/modals": "^9.1.1",
|
||||
"@mantine/notifications": "^9.1.1",
|
||||
"@radix-ui/react-context-menu": "^2.2.16",
|
||||
"@tanstack/react-query": "^5.96.2",
|
||||
"@tanstack/react-query-devtools": "^5.96.2",
|
||||
|
||||
Generated
+96
-131
@@ -28,26 +28,26 @@ importers:
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0(electron@39.8.6)
|
||||
'@mantine/colors-generator':
|
||||
specifier: ^8.3.18
|
||||
version: 8.3.18(chroma-js@3.1.2)
|
||||
specifier: ^9.1.1
|
||||
version: 9.1.1(chroma-js@3.1.2)
|
||||
'@mantine/core':
|
||||
specifier: ^8.3.18
|
||||
version: 8.3.18(@mantine/hooks@8.3.18(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
specifier: ^9.1.1
|
||||
version: 9.1.1(@mantine/hooks@9.1.1(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@mantine/dates':
|
||||
specifier: ^8.3.18
|
||||
version: 8.3.18(@mantine/core@8.3.18(@mantine/hooks@8.3.18(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@mantine/hooks@8.3.18(react@19.2.4))(dayjs@1.11.20)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
specifier: ^9.1.1
|
||||
version: 9.1.1(@mantine/core@9.1.1(@mantine/hooks@9.1.1(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@mantine/hooks@9.1.1(react@19.2.4))(dayjs@1.11.20)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@mantine/form':
|
||||
specifier: ^8.3.18
|
||||
version: 8.3.18(react@19.2.4)
|
||||
specifier: ^9.1.1
|
||||
version: 9.1.1(react@19.2.4)
|
||||
'@mantine/hooks':
|
||||
specifier: ^8.3.18
|
||||
version: 8.3.18(react@19.2.4)
|
||||
specifier: ^9.1.1
|
||||
version: 9.1.1(react@19.2.4)
|
||||
'@mantine/modals':
|
||||
specifier: ^8.3.18
|
||||
version: 8.3.18(@mantine/core@8.3.18(@mantine/hooks@8.3.18(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@mantine/hooks@8.3.18(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
specifier: ^9.1.1
|
||||
version: 9.1.1(@mantine/core@9.1.1(@mantine/hooks@9.1.1(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@mantine/hooks@9.1.1(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@mantine/notifications':
|
||||
specifier: ^8.3.18
|
||||
version: 8.3.18(@mantine/core@8.3.18(@mantine/hooks@8.3.18(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@mantine/hooks@8.3.18(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
specifier: ^9.1.1
|
||||
version: 9.1.1(@mantine/core@9.1.1(@mantine/hooks@9.1.1(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@mantine/hooks@9.1.1(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-context-menu':
|
||||
specifier: ^2.2.16
|
||||
version: 2.2.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
@@ -1441,57 +1441,57 @@ packages:
|
||||
resolution: {integrity: sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
|
||||
'@mantine/colors-generator@8.3.18':
|
||||
resolution: {integrity: sha512-u7gNAuVD/WPvB49uNszfkn7lQr85OXTI0Ijkbutcymhv0/utqlapQvZlQAYHwdrrWpQgqPNLsDBt3VSheVe9jw==}
|
||||
'@mantine/colors-generator@9.1.1':
|
||||
resolution: {integrity: sha512-mzEkW9oBrtvnq3zHW1jISa5hNWGi2wRgo4/N0W96DKxTqS0E1IMQ13KWJM0I9er8MK9xZo69jlpR7SyztRYFaw==}
|
||||
peerDependencies:
|
||||
chroma-js: '>=2.4.2'
|
||||
|
||||
'@mantine/core@8.3.18':
|
||||
resolution: {integrity: sha512-9tph1lTVogKPjTx02eUxDUOdXacPzK62UuSqb4TdGliI54/Xgxftq0Dfqu6XuhCxn9J5MDJaNiLDvL/1KRkYqA==}
|
||||
'@mantine/core@9.1.1':
|
||||
resolution: {integrity: sha512-vClOZdCeZ4oLYuA/3jAOgKGQ6dXbF6ZkzpYz09Gied9nZpB7HcQeb3dcMh8UPBE4f+EM7KlYWk6dch7GoASeaA==}
|
||||
peerDependencies:
|
||||
'@mantine/hooks': 8.3.18
|
||||
react: ^18.x || ^19.x
|
||||
react-dom: ^18.x || ^19.x
|
||||
'@mantine/hooks': 9.1.1
|
||||
react: ^19.2.0
|
||||
react-dom: ^19.2.0
|
||||
|
||||
'@mantine/dates@8.3.18':
|
||||
resolution: {integrity: sha512-FHx5teJOhupI0gO2o5evtVYQEdqOjayOkLRhEQfB5Nc5DvcysfPfmNILGkc1Nrp9ZQeQWKLT9qr+CkcCXwHOaw==}
|
||||
'@mantine/dates@9.1.1':
|
||||
resolution: {integrity: sha512-P1tr/Hr+EVxppbOVpTLvaZZnM1W/r0TNpqNNMeM81xfyuKYzd7zt2/SQYb6BuudgEQfRJnAee+7bIJLEsrb0uA==}
|
||||
peerDependencies:
|
||||
'@mantine/core': 8.3.18
|
||||
'@mantine/hooks': 8.3.18
|
||||
'@mantine/core': 9.1.1
|
||||
'@mantine/hooks': 9.1.1
|
||||
dayjs: '>=1.0.0'
|
||||
react: ^18.x || ^19.x
|
||||
react-dom: ^18.x || ^19.x
|
||||
react: ^19.2.0
|
||||
react-dom: ^19.2.0
|
||||
|
||||
'@mantine/form@8.3.18':
|
||||
resolution: {integrity: sha512-r5OGLJWTkmIruFjRZRZy9oA7maNYlyt50jB4Pmd2X5360WOmJLd4KH8MFhHZQC7vN+z8/rmBl3t3XGAR2I8xig==}
|
||||
'@mantine/form@9.1.1':
|
||||
resolution: {integrity: sha512-xmebZ3s8GGMrCOPOaOwA+gQkdgNVfT2F9kBtkjAbRoZrMoY+vYFbiPWbIvWFl8pU1jBslYZrj+M0PIawJmFOdQ==}
|
||||
peerDependencies:
|
||||
react: ^18.x || ^19.x
|
||||
react: ^19.2.0
|
||||
|
||||
'@mantine/hooks@8.3.18':
|
||||
resolution: {integrity: sha512-QoWr9+S8gg5050TQ06aTSxtlpGjYOpIllRbjYYXlRvZeTsUqiTbVfvQROLexu4rEaK+yy9Wwriwl9PMRgbLqPw==}
|
||||
'@mantine/hooks@9.1.1':
|
||||
resolution: {integrity: sha512-tTJK73nGFyy1v214TLdvBq0be7QCoc6osfbXVuJgOH3YG85lWk9Mvvor6k+w6hC6HXSqKMqLKePyiGm83xGcMg==}
|
||||
peerDependencies:
|
||||
react: ^18.x || ^19.x
|
||||
react: ^19.2.0
|
||||
|
||||
'@mantine/modals@8.3.18':
|
||||
resolution: {integrity: sha512-JfPDS4549L314SxFPC1x6CbKwzh82OdnIzwgMxPCVNsWLKV2vEHHUH/fzUYj4Wli6IBrsW4cufjMj9BTj3hm3Q==}
|
||||
'@mantine/modals@9.1.1':
|
||||
resolution: {integrity: sha512-SjJ2kIheJaWoKMsSuYQlLFvuJTxCQTOl3gr+wDj/bLmGBgfUykLStRNm9s1H7vFxMIWtN20N8mwtcZV2dGeYBg==}
|
||||
peerDependencies:
|
||||
'@mantine/core': 8.3.18
|
||||
'@mantine/hooks': 8.3.18
|
||||
react: ^18.x || ^19.x
|
||||
react-dom: ^18.x || ^19.x
|
||||
'@mantine/core': 9.1.1
|
||||
'@mantine/hooks': 9.1.1
|
||||
react: ^19.2.0
|
||||
react-dom: ^19.2.0
|
||||
|
||||
'@mantine/notifications@8.3.18':
|
||||
resolution: {integrity: sha512-IpQ0lmwbigTBbZCR6iSYWqIOKEx1tlcd7PcEJ5M5X1qeVSY/N3mmDQt1eJmObvcyDeL5cTJMbSA9UPqhRqo9jw==}
|
||||
'@mantine/notifications@9.1.1':
|
||||
resolution: {integrity: sha512-ZfcEMMDp0BQ+yKmVp8ifPXLKej8pv9TcaRnmy2CZ07USD61E9LH5ClRAP/hxQuCyf/qLb5BPHsI7+f3K8uhj4Q==}
|
||||
peerDependencies:
|
||||
'@mantine/core': 8.3.18
|
||||
'@mantine/hooks': 8.3.18
|
||||
react: ^18.x || ^19.x
|
||||
react-dom: ^18.x || ^19.x
|
||||
'@mantine/core': 9.1.1
|
||||
'@mantine/hooks': 9.1.1
|
||||
react: ^19.2.0
|
||||
react-dom: ^19.2.0
|
||||
|
||||
'@mantine/store@8.3.18':
|
||||
resolution: {integrity: sha512-i+QRTLmZzLldea0egtUVnGALd6UMIu8jd44nrNWBSNIXJU/8B6rMlC6gyX+l4szopZSuOaaNJIXkqRdC1gQsVg==}
|
||||
'@mantine/store@9.1.1':
|
||||
resolution: {integrity: sha512-kbxEU8wVGbobHlmQmk0lu9M+xCILKjuAPcMAshgzPznGLfXeE9zrB0gNT2cbk11Ik8dlV9J6Vsn9cuACyOSpfQ==}
|
||||
peerDependencies:
|
||||
react: ^18.x || ^19.x
|
||||
react: ^19.2.0
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||
@@ -1912,66 +1912,79 @@ packages:
|
||||
resolution: {integrity: sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.60.1':
|
||||
resolution: {integrity: sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.60.1':
|
||||
resolution: {integrity: sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.60.1':
|
||||
resolution: {integrity: sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-loong64-gnu@4.60.1':
|
||||
resolution: {integrity: sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-loong64-musl@4.60.1':
|
||||
resolution: {integrity: sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-gnu@4.60.1':
|
||||
resolution: {integrity: sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-musl@4.60.1':
|
||||
resolution: {integrity: sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.60.1':
|
||||
resolution: {integrity: sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.60.1':
|
||||
resolution: {integrity: sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.60.1':
|
||||
resolution: {integrity: sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.60.1':
|
||||
resolution: {integrity: sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.60.1':
|
||||
resolution: {integrity: sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-openbsd-x64@4.60.1':
|
||||
resolution: {integrity: sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==}
|
||||
@@ -2015,6 +2028,9 @@ packages:
|
||||
resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
'@standard-schema/spec@1.1.0':
|
||||
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
|
||||
|
||||
'@surma/rollup-plugin-off-main-thread@2.2.3':
|
||||
resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==}
|
||||
|
||||
@@ -4710,12 +4726,6 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
react-textarea-autosize@8.5.9:
|
||||
resolution: {integrity: sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
react-transition-group@4.4.5:
|
||||
resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
|
||||
peerDependencies:
|
||||
@@ -5358,6 +5368,10 @@ packages:
|
||||
resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
||||
tagged-tag@1.0.0:
|
||||
resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
tar@7.5.13:
|
||||
resolution: {integrity: sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -5454,9 +5468,9 @@ packages:
|
||||
resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
|
||||
engines: {node: '>=12.20'}
|
||||
|
||||
type-fest@4.41.0:
|
||||
resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==}
|
||||
engines: {node: '>=16'}
|
||||
type-fest@5.6.0:
|
||||
resolution: {integrity: sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
typed-array-buffer@1.0.3:
|
||||
resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
|
||||
@@ -5569,33 +5583,6 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
use-composed-ref@1.4.0:
|
||||
resolution: {integrity: sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
use-isomorphic-layout-effect@1.2.1:
|
||||
resolution: {integrity: sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
use-latest@1.3.0:
|
||||
resolution: {integrity: sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
use-sidecar@1.1.3:
|
||||
resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -7111,60 +7098,60 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@mantine/colors-generator@8.3.18(chroma-js@3.1.2)':
|
||||
'@mantine/colors-generator@9.1.1(chroma-js@3.1.2)':
|
||||
dependencies:
|
||||
chroma-js: 3.1.2
|
||||
|
||||
'@mantine/core@8.3.18(@mantine/hooks@8.3.18(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@mantine/core@9.1.1(@mantine/hooks@9.1.1(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@floating-ui/react': 0.27.19(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@mantine/hooks': 8.3.18(react@19.2.4)
|
||||
'@mantine/hooks': 9.1.1(react@19.2.4)
|
||||
clsx: 2.1.1
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
react-number-format: 5.4.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.4)
|
||||
react-textarea-autosize: 8.5.9(@types/react@19.2.14)(react@19.2.4)
|
||||
type-fest: 4.41.0
|
||||
type-fest: 5.6.0
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
|
||||
'@mantine/dates@8.3.18(@mantine/core@8.3.18(@mantine/hooks@8.3.18(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@mantine/hooks@8.3.18(react@19.2.4))(dayjs@1.11.20)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@mantine/dates@9.1.1(@mantine/core@9.1.1(@mantine/hooks@9.1.1(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@mantine/hooks@9.1.1(react@19.2.4))(dayjs@1.11.20)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@mantine/core': 8.3.18(@mantine/hooks@8.3.18(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@mantine/hooks': 8.3.18(react@19.2.4)
|
||||
'@mantine/core': 9.1.1(@mantine/hooks@9.1.1(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@mantine/hooks': 9.1.1(react@19.2.4)
|
||||
clsx: 2.1.1
|
||||
dayjs: 1.11.20
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
'@mantine/form@8.3.18(react@19.2.4)':
|
||||
'@mantine/form@9.1.1(react@19.2.4)':
|
||||
dependencies:
|
||||
'@standard-schema/spec': 1.1.0
|
||||
fast-deep-equal: 3.1.3
|
||||
klona: 2.0.6
|
||||
react: 19.2.4
|
||||
|
||||
'@mantine/hooks@8.3.18(react@19.2.4)':
|
||||
'@mantine/hooks@9.1.1(react@19.2.4)':
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
|
||||
'@mantine/modals@8.3.18(@mantine/core@8.3.18(@mantine/hooks@8.3.18(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@mantine/hooks@8.3.18(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@mantine/modals@9.1.1(@mantine/core@9.1.1(@mantine/hooks@9.1.1(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@mantine/hooks@9.1.1(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@mantine/core': 8.3.18(@mantine/hooks@8.3.18(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@mantine/hooks': 8.3.18(react@19.2.4)
|
||||
'@mantine/core': 9.1.1(@mantine/hooks@9.1.1(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@mantine/hooks': 9.1.1(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
'@mantine/notifications@8.3.18(@mantine/core@8.3.18(@mantine/hooks@8.3.18(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@mantine/hooks@8.3.18(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@mantine/notifications@9.1.1(@mantine/core@9.1.1(@mantine/hooks@9.1.1(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@mantine/hooks@9.1.1(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@mantine/core': 8.3.18(@mantine/hooks@8.3.18(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@mantine/hooks': 8.3.18(react@19.2.4)
|
||||
'@mantine/store': 8.3.18(react@19.2.4)
|
||||
'@mantine/core': 9.1.1(@mantine/hooks@9.1.1(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@mantine/hooks': 9.1.1(react@19.2.4)
|
||||
'@mantine/store': 9.1.1(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
react-transition-group: 4.4.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
|
||||
'@mantine/store@8.3.18(react@19.2.4)':
|
||||
'@mantine/store@9.1.1(react@19.2.4)':
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
|
||||
@@ -7611,6 +7598,8 @@ snapshots:
|
||||
|
||||
'@sindresorhus/is@4.6.0': {}
|
||||
|
||||
'@standard-schema/spec@1.1.0': {}
|
||||
|
||||
'@surma/rollup-plugin-off-main-thread@2.2.3':
|
||||
dependencies:
|
||||
ejs: 3.1.10
|
||||
@@ -10672,15 +10661,6 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
react-textarea-autosize@8.5.9(@types/react@19.2.14)(react@19.2.4):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
react: 19.2.4
|
||||
use-composed-ref: 1.4.0(@types/react@19.2.14)(react@19.2.4)
|
||||
use-latest: 1.3.0(@types/react@19.2.14)(react@19.2.4)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
|
||||
react-transition-group@4.4.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
@@ -11435,6 +11415,8 @@ snapshots:
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
|
||||
tagged-tag@1.0.0: {}
|
||||
|
||||
tar@7.5.13:
|
||||
dependencies:
|
||||
'@isaacs/fs-minipass': 4.0.1
|
||||
@@ -11544,7 +11526,9 @@ snapshots:
|
||||
|
||||
type-fest@2.19.0: {}
|
||||
|
||||
type-fest@4.41.0: {}
|
||||
type-fest@5.6.0:
|
||||
dependencies:
|
||||
tagged-tag: 1.0.0
|
||||
|
||||
typed-array-buffer@1.0.3:
|
||||
dependencies:
|
||||
@@ -11664,25 +11648,6 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
use-composed-ref@1.4.0(@types/react@19.2.14)(react@19.2.4):
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
use-isomorphic-layout-effect@1.2.1(@types/react@19.2.14)(react@19.2.4):
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
use-latest@1.3.0(@types/react@19.2.14)(react@19.2.4):
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
use-isomorphic-layout-effect: 1.2.1(@types/react@19.2.14)(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
use-sidecar@1.1.3(@types/react@19.2.14)(react@19.2.4):
|
||||
dependencies:
|
||||
detect-node-es: 1.1.0
|
||||
|
||||
@@ -139,7 +139,9 @@
|
||||
"lyricOffset": "demora de la lletra (ms)",
|
||||
"showLyricMatch": "mosta coincidències de lletres",
|
||||
"showLyricProvider": "mostra el proveïdor de la lletra",
|
||||
"lyricGap": "espera entre lletres"
|
||||
"lyricGap": "espera entre lletres",
|
||||
"lyricOpacityNonActive": "Opacitat de la lletra inactiva",
|
||||
"lyricScaleNonActive": "Escala de la lletra inactiva"
|
||||
},
|
||||
"lyrics": "lletres",
|
||||
"visualizer": "visualitzador",
|
||||
@@ -337,7 +339,8 @@
|
||||
"filter_single": "senzill",
|
||||
"filter_multiple": "multi",
|
||||
"rename": "reanomena",
|
||||
"newVersionAvailable": "hi ha una nova versió disponible"
|
||||
"newVersionAvailable": "hi ha una nova versió disponible",
|
||||
"numberOfResults": "{{numberOfResults}} resultats"
|
||||
},
|
||||
"entity": {
|
||||
"album_one": "àlbum",
|
||||
@@ -406,7 +409,8 @@
|
||||
"input_skipDuplicates": "salta't els duplicats",
|
||||
"success": "s'ha afegit $t(entity.trackWithCount, {\"count\": {{message}} }) a $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })",
|
||||
"create": "crea $t(entity.playlist, {\"count\": 1}) {{playlist}}",
|
||||
"searchOrCreate": "cerca $t(entity.playlist, {\"count\": 2}) o escriu per crear-ne una de nova"
|
||||
"searchOrCreate": "cerca $t(entity.playlist, {\"count\": 2}) o escriu per crear-ne una de nova",
|
||||
"noneAdded": "no s'han afegit pistes a la $t(entity.playlist, {\"count\": 1}) '{{playlist}}'"
|
||||
},
|
||||
"createPlaylist": {
|
||||
"input_description": "$t(common.description)",
|
||||
@@ -506,6 +510,9 @@
|
||||
"export": "exporta la lletra",
|
||||
"input_synced": "exporta la lletra sincronitzada",
|
||||
"input_offset": "$t(setting.lyricOffset)"
|
||||
},
|
||||
"editRadioStation": {
|
||||
"success": "Emissora de ràdio actualitzada amb èxit"
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
@@ -522,7 +529,10 @@
|
||||
"goToPage": "anar a la pàgina",
|
||||
"openIn": {
|
||||
"lastfm": "Obrir a Last.fm",
|
||||
"musicbrainz": "Obrir a MusicBrainz"
|
||||
"musicbrainz": "Obrir a MusicBrainz",
|
||||
"listenbrainz": "Obre a ListenBrainz",
|
||||
"qobuz": "Obre a Qobuz",
|
||||
"spotify": "Obre a Spotify"
|
||||
},
|
||||
"deselectAll": "deselecciona-ho tot",
|
||||
"viewPlaylists": "veure $t(entity.playlist, {\"count\": 2})",
|
||||
@@ -597,7 +607,7 @@
|
||||
"artistConfiguration": "configuració de la pàgina de l'artista de l'àlbum",
|
||||
"artistConfiguration_description": "configura quins elements es mostren i el seu ordre de la pàgina de l'artista de l'àlbum",
|
||||
"audioExclusiveMode": "mode d'àudio exclusiu",
|
||||
"audioExclusiveMode_description": "activa el mode d'àudio exclusiu. En aquest mode, el sistema normalment estarà bloquejat i només mpv podrà emetre àudio",
|
||||
"audioExclusiveMode_description": "activa el mode d'àudio exclusiu. En aquest mode, el sistema normalment estarà bloquejat i només mpv podrà emetre àudio. El sistema visualitzador de captura d'àudio no funcionarà mentre això estigui activat",
|
||||
"buttonSize": "mida dels botons de la barra de reproducció",
|
||||
"buttonSize_description": "la mida dels botons de la barra de reproducció",
|
||||
"clearCache": "neteja la memòria del navegador",
|
||||
@@ -910,7 +920,25 @@
|
||||
"primaryShade": "ombra primària",
|
||||
"primaryShade_description": "substitueix el to primari (0–9) utilitzat per a botons, enllaços i altres elements de color primari",
|
||||
"playerItemConfiguration_description": "configurar quins elements es mostren i en quin ordre al reproductor de pantalla completa",
|
||||
"playerItemConfiguration": "configuració d'elements del jugador"
|
||||
"playerItemConfiguration": "configuració d'elements del jugador",
|
||||
"listenbrainz_description": "mostra enllaços a ListenBrainz a les pàgines d'artista/àlbum",
|
||||
"listenbrainz": "mostra enllaços a ListenBrainz",
|
||||
"qobuz_description": "mostra enllaços a Qobuz a les pàgines d'artista/àlbum",
|
||||
"qobuz": "mostra enllaços a Qobuz",
|
||||
"spotify_description": "mostra enllaços a Spotify a les pàgines d'artista/àlbum",
|
||||
"spotify": "mostra enllaços a Spotify",
|
||||
"nativeSpotify_description": "obre amb Spotify en lloc del vostre navegador",
|
||||
"nativeSpotify": "fes servir Spotify",
|
||||
"playerbarWaveformStretch": "extensió de la forma d'ona",
|
||||
"playerbarWaveformStretch_description": "estén la forma d'ona per omplir l'espai disponible",
|
||||
"sidePlayQueueLayout": "disposició de la cua de reproducció lateral",
|
||||
"sidePlayQueueLayout_description": "estableix la disposició de la cua de reproducció lateral adjunta",
|
||||
"sidePlayQueueLayout_optionHorizontal": "horitzontal",
|
||||
"sidePlayQueueLayout_optionVertical": "vertical",
|
||||
"waveformLoadingDelay": "demora de càrrega de la forma d'ona",
|
||||
"waveformLoadingDelay_description": "demora en segons abans de carregar la forma d'ona. incrementeu aquest valor si patiu interrupcions en fer servir el reproductor web.",
|
||||
"preventSuspendOnPlayback_description": "evita que l'aplicació quedi suspesa mentre es reprodueix música",
|
||||
"preventSuspendOnPlayback": "evita la suspensió durant la reproducció"
|
||||
},
|
||||
"table": {
|
||||
"column": {
|
||||
@@ -1360,6 +1388,13 @@
|
||||
}
|
||||
},
|
||||
"pasteGradient": "enganxa degradat",
|
||||
"pasteGradientPlaceholder": "enganxa el degradat JSON aquí..."
|
||||
"pasteGradientPlaceholder": "enganxa el degradat JSON aquí...",
|
||||
"systemAudioConsentAllow": "permetre",
|
||||
"systemAudioConsentBody": "el visualitzador necessita accés a l'àudio del sistema per funcionar",
|
||||
"systemAudioConsentDecline": "denega",
|
||||
"systemAudioConsentTitle": "Voleu permetre accés al sistema d'àudio?",
|
||||
"systemAudioCaptureFailed": "No s'ha pogut iniciar la captura: {{message}}",
|
||||
"systemAudioNoAudioTrack": "No s'ha tornat cap pista d'àudio. Comproveu que la captura d'àudio estigui activada quan se sol·liciti.",
|
||||
"systemAudioExclusiveModeNotSupported": "El visualitzador no està disponible amb el mode d'àudio exclusiu activat. Desactiveu-lo a la configuració d'MPV i torneu-ho a intentar."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
"hotkey_zoomIn": "přiblížení",
|
||||
"scrobble_description": "scrobblovat přehrání na váš multimediální server",
|
||||
"hotkey_browserForward": "vpřed v prohlížeči",
|
||||
"audioExclusiveMode_description": "zapnout režim výhradního výstupu. V tomto režimu bude obvykle v systému schopný přehrávat zvuk pouze přehrávač mpv",
|
||||
"audioExclusiveMode_description": "zapnout režim výhradního výstupu. v tomto režimu bude obvykle v systému schopný přehrávat zvuk pouze přehrávač mpv. záznam systémového zvuku ve vizualizéru nebude fungovat",
|
||||
"discordUpdateInterval": "interval aktualizací {{discord}} rich presence",
|
||||
"themeLight": "motiv (světlý)",
|
||||
"fontType_optionBuiltIn": "vestavěné písmo",
|
||||
@@ -425,7 +425,11 @@
|
||||
"sidePlayQueueLayout_optionHorizontal": "na šířku",
|
||||
"sidePlayQueueLayout_optionVertical": "na výšku",
|
||||
"waveformLoadingDelay": "zpoždění načítání vlnové křivky",
|
||||
"waveformLoadingDelay_description": "zpoždění v sekundách před načtením vlnové křivky. zvyšte, pokud jste během používání webového přehrávače zaznamenali záseky."
|
||||
"waveformLoadingDelay_description": "zpoždění v sekundách před načtením vlnové křivky. zvyšte, pokud jste během používání webového přehrávače zaznamenali záseky.",
|
||||
"playerbarWaveformStretch": "natáhnutí vlnové křivky",
|
||||
"playerbarWaveformStretch_description": "natáhně vlnovou křivku tak, aby vyplnila dostupný prostor",
|
||||
"preventSuspendOnPlayback_description": "zabránit aplikaci v uspání během přehrávání hudby",
|
||||
"preventSuspendOnPlayback": "zabránit uspání při přehrávání"
|
||||
},
|
||||
"action": {
|
||||
"editPlaylist": "upravit $t(entity.playlist, {\"count\": 1})",
|
||||
@@ -825,7 +829,9 @@
|
||||
"lyricGap": "mezera textů",
|
||||
"dynamicImageBlur": "velikost rozostření obrázku",
|
||||
"dynamicIsImage": "povolit obrázek na pozadí",
|
||||
"lyricOffset": "posunutí textů (ms)"
|
||||
"lyricOffset": "posunutí textů (ms)",
|
||||
"lyricOpacityNonActive": "neprůhlednost neaktivních textů",
|
||||
"lyricScaleNonActive": "velikost neaktivních textů"
|
||||
},
|
||||
"upNext": "další",
|
||||
"lyrics": "texty",
|
||||
@@ -1041,7 +1047,8 @@
|
||||
"input_skipDuplicates": "přeskočit duplicity",
|
||||
"input_playlists": "$t(entity.playlist, {\"count\": 2})",
|
||||
"create": "vytvořit $t(entity.playlist, {\"count\": 1}) {{playlist}}",
|
||||
"searchOrCreate": "vyhledejte $t(entity.playlist, {\"count\": 2}) nebo pište pro vytvoření nového"
|
||||
"searchOrCreate": "vyhledejte $t(entity.playlist, {\"count\": 2}) nebo pište pro vytvoření nového",
|
||||
"noneAdded": "do $t(entity.playlist, {\"count\": 1}) '{{playlist}}' nebyly přidány žádné skladby"
|
||||
},
|
||||
"updateServer": {
|
||||
"title": "upravit server",
|
||||
@@ -1381,6 +1388,13 @@
|
||||
}
|
||||
},
|
||||
"pasteGradient": "Vložit přechod",
|
||||
"pasteGradientPlaceholder": "Sem vložte JSON přechodu…"
|
||||
"pasteGradientPlaceholder": "Sem vložte JSON přechodu…",
|
||||
"systemAudioConsentAllow": "Povolit",
|
||||
"systemAudioConsentBody": "Vizualizér potřebuje pro svou činnost přístup k systémovému zvuku",
|
||||
"systemAudioConsentTitle": "Povolit přístup k systémovému zvuku?",
|
||||
"systemAudioCaptureFailed": "Nepodařilo se spustit zachytávání: {{message}}",
|
||||
"systemAudioNoAudioTrack": "Nebyla zachycena žádná zvuková stopa. Ujistěte se, že jste při výzvě povolili zachytávání zvuku.",
|
||||
"systemAudioConsentDecline": "Zamítnout",
|
||||
"systemAudioExclusiveModeNotSupported": "Vizualizér není dostupný při zapnutém režimu výhradního výstupu zvuku. Zakažte Režim výhradního výstupu zvuku v nastavení MPV a zkuste to znovu."
|
||||
}
|
||||
}
|
||||
|
||||
+40
-27
@@ -242,7 +242,7 @@
|
||||
"criticRating": "Kritikerbewertung",
|
||||
"album": "$t(entity.album, {\"count\": 1})",
|
||||
"trackNumber": "Track",
|
||||
"channels": "$t(common.channel_other)",
|
||||
"channels": "$t(common.channel,{\"count\":2})",
|
||||
"owner": "$t(common.owner)",
|
||||
"genre": "$t(entity.genre, {\"count\": 1})",
|
||||
"artist": "$t(entity.artist, {\"count\": 1})",
|
||||
@@ -273,13 +273,13 @@
|
||||
"input_name": "Servername",
|
||||
"success": "Server erfolgreich hinzugefügt",
|
||||
"input_savePassword": "Passwort speichern",
|
||||
"ignoreSsl": "SSL ignorieren $t(common.restartRequired)",
|
||||
"ignoreCors": "CORS ignorieren $t(common.restartRequired)",
|
||||
"ignoreSsl": "SSL ignorieren ($t(common.restartRequired))",
|
||||
"ignoreCors": "CORS ignorieren ($t(common.restartRequired))",
|
||||
"error_savePassword": "Beim Speichern des Passworts ist ein Fehler aufgetreten",
|
||||
"input_preferInstantMix": "Instant-Mix bevorzugen",
|
||||
"input_preferInstantMixDescription": "nur Instant-Mix verwenden, um ähnliche Songs zu erhalten. Nützlich bei Verwendung von Plugins, die in dieses Verhalten eingreifen",
|
||||
"input_preferRemoteUrl": "öffentliche URL bevorzugen",
|
||||
"input_remoteUrl": "Öffentliche URL",
|
||||
"input_remoteUrl": "öffentliche URL",
|
||||
"input_remoteUrlPlaceholder": "Optional: öffentliche URL für externe Funktionen"
|
||||
},
|
||||
"addToPlaylist": {
|
||||
@@ -357,6 +357,9 @@
|
||||
"input_offset": "$t(setting.lyricOffset)",
|
||||
"export": "Songtexte exportieren",
|
||||
"input_synced": "Synchronisierte Songtexte exportieren"
|
||||
},
|
||||
"editRadioStation": {
|
||||
"success": "Radiosender erfolgreich aktualisiert"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
@@ -426,8 +429,8 @@
|
||||
"pagination": "Seitenzahlen",
|
||||
"pagination_itemsPerPage": "Elemente pro Seite",
|
||||
"pagination_infinite": "unendlich",
|
||||
"moveUp": "Nach oben bewegen",
|
||||
"moveDown": "Nach unten bewegen",
|
||||
"moveUp": "nach oben",
|
||||
"moveDown": "nach unten",
|
||||
"pinToLeft": "links anheften",
|
||||
"pinToRight": "rechts anheften",
|
||||
"itemGap": "Item Abstand (px)",
|
||||
@@ -450,13 +453,13 @@
|
||||
"albumArtist": "$t(entity.albumArtist, {\"count\": 1})",
|
||||
"artist": "$t(entity.artist, {\"count\": 1})",
|
||||
"favorite": "$t(common.favorite)",
|
||||
"actions": "$t(common.action_other)",
|
||||
"actions": "$t(common.action,{\"count\":2})",
|
||||
"genre": "$t(entity.genre, {\"count\": 1})",
|
||||
"album": "$t(entity.album, {\"count\": 1})",
|
||||
"size": "$t(common.size)",
|
||||
"bpm": "$t(common.bpm)",
|
||||
"titleCombined": "$t(common.title) (kombiniert)",
|
||||
"channels": "$t(common.channel_other)",
|
||||
"channels": "$t(common.channel,{\"count\":2})",
|
||||
"duration": "$t(common.duration)",
|
||||
"note": "$t(common.note)",
|
||||
"owner": "$t(common.owner)",
|
||||
@@ -493,7 +496,7 @@
|
||||
"rating": "Bewertung",
|
||||
"albumCount": "$t(entity.album, {\"count\": 2})",
|
||||
"artist": "$t(entity.artist, {\"count\": 1})",
|
||||
"channels": "$t(common.channel_other)",
|
||||
"channels": "$t(common.channel,{\"count\":2})",
|
||||
"comment": "Kommentar",
|
||||
"dateAdded": "hinzugefügt am",
|
||||
"playCount": "Abgespielt",
|
||||
@@ -716,7 +719,8 @@
|
||||
},
|
||||
"releasenotes": {
|
||||
"commitsSinceStable": "Commits seit {{stable}}",
|
||||
"noStableReleaseToCompare": "Kein stable Relase zum vergleichen verfügbar"
|
||||
"noStableReleaseToCompare": "Kein stable Relase zum vergleichen verfügbar",
|
||||
"noNewCommits": "keine neuen Beiträge in diesem Bereich"
|
||||
}
|
||||
},
|
||||
"player": {
|
||||
@@ -766,12 +770,12 @@
|
||||
"sleepTimer": "Sleep Timer",
|
||||
"sleepTimer_custom": "Benutzerdefiniert",
|
||||
"sleepTimer_hours": "{{count}} std",
|
||||
"sleepTimer_minutes": "{{count}} min",
|
||||
"sleepTimer_minutes": "{{count}} Min",
|
||||
"trackRadio": "Song Radio",
|
||||
"albumRadio": "Album Radio"
|
||||
},
|
||||
"setting": {
|
||||
"audioDevice_description": "wählen Sie das Audiogerät aus, das für die Wiedergabe verwendet werden soll (nur Webplayer)",
|
||||
"audioDevice_description": "das für die Wiedergabe zu verwendende Audiogerät auswählen",
|
||||
"audioExclusiveMode": "Audio-Exklusivmodus",
|
||||
"audioDevice": "Audiogerät",
|
||||
"accentColor": "Akzentfarbe",
|
||||
@@ -789,7 +793,7 @@
|
||||
"crossfadeDuration": "Dauer der Überblendung",
|
||||
"discordIdleStatus": "rich presence status im Leerlauf",
|
||||
"audioPlayer": "Audio-Player",
|
||||
"discordApplicationId": "{{discord}} Anwendungs ID",
|
||||
"discordApplicationId": "{{discord}} Anwendungs-ID",
|
||||
"customFontPath_description": "Legt den Pfad zur benutzerdefinierten Schriftart fest, welche für die Anwendung verwendet werden soll",
|
||||
"remotePort_description": "Legt den Port des Fernsteuerungsserver fest",
|
||||
"hotkey_skipBackward": "rückwärts springen",
|
||||
@@ -1009,7 +1013,7 @@
|
||||
"transcodeFormat": "Format für Umwandlung",
|
||||
"startMinimized_description": "Startet die Anwendung im Info-Bereich",
|
||||
"startMinimized": "Im Info-Bereich starten",
|
||||
"mediaSession_description": "Aktiviert die Windows Media Session-Integration, zeigt Mediensteuerelemente und Metadaten im Systemlautstärke-Overlay und auf dem Sperrbildschirm an (nur Windows)",
|
||||
"mediaSession_description": "aktiviert die Media Session Integration. Dies ermöglicht die Steuerung und Anzeige der Medien in der Systemlautstärkeoption und auf dem Sperrbildschirm",
|
||||
"mediaSession": "Media Session aktivieren",
|
||||
"artistBackgroundBlur": "Unschärfegrad für Künstlerhintergründe",
|
||||
"artistBackgroundBlur_description": "Legt den Grad der Unschärfe fest, der auf das Hintergrundbild des Künstlers angewendet wird",
|
||||
@@ -1017,11 +1021,11 @@
|
||||
"contextMenu_description": "Legt die Einträge fest, die im Rechtsklick-Menü angezeigt werden sollen. Abgewählte Einträge werden ausgeblendet",
|
||||
"crossfadeStyle": "Art der Überblende",
|
||||
"customCss_description": "Benutzerdefinierter CSS-Inhalt. Hinweis: Inhalte und Remote-URLs sind nicht zulässige Eigenschaften. Unten siehst du eine Vorschau deines Inhalts. Aufgrund von Bereinigung werden womöglich zusätzliche, nicht von dir definierte Felder angezeigt",
|
||||
"customCssNotice": "Warnung: Obwohl eine gewisse Bereinigung erfolgt (url() und content: sind nicht zulässig), kann die Verwendung von benutzerdefiniertem CSS dennoch Risiken mit sich bringen, da dadurch die Benutzeroberfläche verändert wird",
|
||||
"customCssNotice": "Warnung: Obwohl eine gewisse Bereinigung erfolgt (nicht zulässig sind z. B. \"url()\" und \"content:\"), kann ein benutzerdefiniertes CSS Risiken mit sich bringen, da die Benutzeroberfläche dadurch verändert wird",
|
||||
"releaseChannel_optionBeta": "Beta",
|
||||
"releaseChannel_optionLatest": "Stabil",
|
||||
"releaseChannel": "Veröffentlichungskanal",
|
||||
"releaseChannel_description": "Zwischen stabilen und beta Veröffentlichungen für automatische Aktualisierungen wählen",
|
||||
"releaseChannel_description": "zwischen stabilen, Beta- oder Alpha-Versionen (Nightly) für automatische Updates wählen",
|
||||
"discordDisplayType_artistname": "Künstlername(n)",
|
||||
"discordDisplayType_description": "Ändert den aktuellen Titel im Zuhör-Status",
|
||||
"discordDisplayType_songname": "Songtitel",
|
||||
@@ -1121,7 +1125,13 @@
|
||||
"nativeSpotify": "Spotify App benutzen",
|
||||
"qobuz_description": "Zeige Links zu Qobuz auf den Interpreten/Alben Seiten",
|
||||
"spotify_description": "Zeige Links zu Spotify auf den Interpreten/Alben Seiten",
|
||||
"artistReleaseTypeConfiguration": "Interpreten Release Typ Einstellung"
|
||||
"artistReleaseTypeConfiguration": "Interpreten Release Typ Einstellung",
|
||||
"discordStateIcon": "Play Icon anzeigen",
|
||||
"homeFeatureStyle_optionSingle": "Einzeln",
|
||||
"nativeSpotify_description": "in der Spotify App statt im Browser öffnen",
|
||||
"imageResolution_optionFullScreenPlayer": "Wiedergabe im Vollbildmodus",
|
||||
"sidePlayQueueLayout_optionHorizontal": "horizontal",
|
||||
"sidePlayQueueLayout_optionVertical": "vertikal"
|
||||
},
|
||||
"dragDropZone": {
|
||||
"error_oneFileOnly": "Bitte wähle nur 1 Datei",
|
||||
@@ -1211,7 +1221,7 @@
|
||||
"dualVertical": "Dual-Vertikal"
|
||||
}
|
||||
},
|
||||
"minimumFrequency": "Mindestfrequenz",
|
||||
"minimumFrequency": "Minimale Frequenz",
|
||||
"minimumDecibels": "Minimale Dezibel",
|
||||
"visualizerType": "Visualizer Art",
|
||||
"cyclePresets": "Vorlagen durchrotieren",
|
||||
@@ -1246,10 +1256,10 @@
|
||||
"channelLayout": "Kanallayout",
|
||||
"maxFPS": "Max FPS",
|
||||
"opacity": "Deckkraft",
|
||||
"customGradients": "Benutzerdefinierte Gradienten",
|
||||
"customGradients": "Benutzerdefinierter Farbverlauf",
|
||||
"addCustomGradient": "Benutzerdefinierten Gradienten hinzufügen",
|
||||
"gradientName": "Gradientenname",
|
||||
"gradientNamePlaceholder": "Gradientenname",
|
||||
"gradientName": "Name Farbverlauf",
|
||||
"gradientNamePlaceholder": "Name Farbverlauf",
|
||||
"vertical": "Vertikal",
|
||||
"horizontal": "Horizontal",
|
||||
"addColor": "Farbe hinzufügen",
|
||||
@@ -1262,9 +1272,9 @@
|
||||
"builtIn": "Eingebaut",
|
||||
"colors": "Farben",
|
||||
"colorMode": "Farbmodus",
|
||||
"gradient": "Gradienten",
|
||||
"gradientLeft": "Gradienten links",
|
||||
"gradientRight": "Gradienten rechts",
|
||||
"gradient": "Farbverlauf",
|
||||
"gradientLeft": "Farberverlauf links",
|
||||
"gradientRight": "Farbverlauf rechts",
|
||||
"fft": "FFT",
|
||||
"fftSize": "FFT Größe",
|
||||
"smoothing": "Glätten",
|
||||
@@ -1273,17 +1283,20 @@
|
||||
"sensitivity": "Empfindlichkeit",
|
||||
"weightingFilter": "Gewichtungsfilter",
|
||||
"maximumDecibels": "Maximale Dezibel",
|
||||
"linearAmplitude": "Lineare Amplitude",
|
||||
"linearAmplitude": "Linearer Ausschlag",
|
||||
"linearBoost": "Linearer Boost",
|
||||
"radialSpectrum": "Radiales Spektrum",
|
||||
"radial": "Radial",
|
||||
"radialInvert": "Radial invertiert",
|
||||
"radius": "Radius",
|
||||
"miscellaneousSettings": "Verschiedenes Einstellungen",
|
||||
"miscellaneousSettings": "Verschiedene Einstellungen",
|
||||
"ansiBands": "ANSI Bänder",
|
||||
"lowResolution": "Niedrige Auflösung",
|
||||
"showFPS": "FPS anzeigen",
|
||||
"fadePeaks": "Spitzen abblenden",
|
||||
"showPeaks": "Spitzen anzeigen"
|
||||
"showPeaks": "Spitzen anzeigen",
|
||||
"systemAudioConsentAllow": "Erlauben",
|
||||
"systemAudioConsentDecline": "Ablehnen",
|
||||
"frequencyScale": "Frequenzskala"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,6 +347,7 @@
|
||||
"input_skipDuplicates": "skip duplicates",
|
||||
"searchOrCreate": "search $t(entity.playlist, {\"count\": 2}) or type to create a new one",
|
||||
"success": "added $t(entity.trackWithCount, {\"count\": {{message}} }) to $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })",
|
||||
"noneAdded": "no tracks were added to $t(entity.playlist, {\"count\": 1}) '{{playlist}}'",
|
||||
"title": "add to $t(entity.playlist, {\"count\": 1})"
|
||||
},
|
||||
"createPlaylist": {
|
||||
@@ -540,6 +541,8 @@
|
||||
"lyricOffset": "lyrics offset (ms)",
|
||||
"lyricGap": "lyric gap",
|
||||
"lyricSize": "lyric size",
|
||||
"lyricOpacityNonActive": "non-active lyric opacity",
|
||||
"lyricScaleNonActive": "non-active lyric scale",
|
||||
"opacity": "opacity",
|
||||
"showLyricMatch": "show lyric match",
|
||||
"showLyricProvider": "show lyric provider",
|
||||
@@ -761,7 +764,7 @@
|
||||
"artistReleaseTypeConfiguration_description": "configure what release types are shown, and in what order, on the album artist page",
|
||||
"audioDevice_description": "select the audio device to use for playback",
|
||||
"audioDevice": "audio device",
|
||||
"audioExclusiveMode_description": "enable exclusive output mode. In this mode, the system is usually locked out, and only mpv will be able to output audio",
|
||||
"audioExclusiveMode_description": "enable exclusive output mode. in this mode, the system is usually locked out, and only mpv will be able to output audio. visualizer system audio capture will not work while this is enabled",
|
||||
"audioExclusiveMode": "audio exclusive mode",
|
||||
"audioPlayer_description": "select the audio player to use for playback",
|
||||
"audioPlayer": "audio player",
|
||||
@@ -983,6 +986,8 @@
|
||||
"playerbarWaveformBarWidth": "waveform bar width",
|
||||
"playerbarWaveformGap": "waveform gap",
|
||||
"playerbarWaveformRadius": "waveform radius",
|
||||
"playerbarWaveformStretch": "waveform stretch",
|
||||
"playerbarWaveformStretch_description": "stretches the waveform to fill the available space",
|
||||
"preferLocalLyrics_description": "prefer local lyrics over remote lyrics when available",
|
||||
"preferLocalLyrics": "prefer local lyrics",
|
||||
"showLyricsInSidebar_description": "a panel will be added to the attached play queue that displays the lyrics",
|
||||
@@ -1003,6 +1008,8 @@
|
||||
"audioFadeOnStatusChange_description": "enables fade out and fade in when play/pause status changes",
|
||||
"preventSleepOnPlayback_description": "prevent the display from sleeping while music is playing",
|
||||
"preventSleepOnPlayback": "prevent sleep on playback",
|
||||
"preventSuspendOnPlayback_description": "prevent the application from suspending while music is playing",
|
||||
"preventSuspendOnPlayback": "prevent suspend on playback",
|
||||
"remotePassword_description": "sets the password for the remote control server. These credentials are by default transferred insecurely, so you should use a unique password that you do not care about",
|
||||
"remotePassword": "remote control server password",
|
||||
"remotePort_description": "sets the port for the remote control server",
|
||||
@@ -1220,6 +1227,7 @@
|
||||
"systemAudioConsentTitle": "Allow access to system audio?",
|
||||
"systemAudioCaptureFailed": "Could not start capture: {{message}}",
|
||||
"systemAudioNoAudioTrack": "No audio track was returned. Ensure audio capture is enabled when prompted.",
|
||||
"systemAudioExclusiveModeNotSupported": "Visualizer is unavailable while audio exclusive mode is enabled. Disable Audio Exclusive Mode in MPV settings and try again.",
|
||||
"visualizerType": "Visualizer Type",
|
||||
"cyclePresets": "Cycle Presets",
|
||||
"cycleTime": "Cycle Time (seconds)",
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
"replayGainClipping": "recortar {{ReplayGain}}",
|
||||
"hotkey_zoomIn": "ampliar",
|
||||
"scrobble_description": "hace scrobble de las reproducciones en tu servidor de medios",
|
||||
"audioExclusiveMode_description": "activa el modo de audio exclusivo. En este modo, el sistema es normalmente bloqueado, y solo se permitirá mpv en la salida de audio",
|
||||
"audioExclusiveMode_description": "activa el modo de audio exclusivo. En este modo, el sistema es normalmente bloqueado, y solo se permitirá mpv en la salida de audio. El sistema visualizador de captura de audio no funcionará mientras esto esté activado",
|
||||
"discordUpdateInterval": "intervalo de actualización del estado de actividad de {{discord}}",
|
||||
"themeLight": "tema (claro)",
|
||||
"fontType_optionBuiltIn": "fuente incorporada",
|
||||
@@ -425,7 +425,11 @@
|
||||
"sidePlayQueueLayout": "Diseño de la cola de reproducción lateral",
|
||||
"sidePlayQueueLayout_description": "Establece el diseño de la cola de reproducción lateral adjunta",
|
||||
"waveformLoadingDelay": "Retraso de carga de la forma de onda",
|
||||
"waveformLoadingDelay_description": "Retraso en segundos antes de cargar la forma de onda. Incrementa este valor si estás experimentando tartamudeos al usar el reproductor web."
|
||||
"waveformLoadingDelay_description": "Retraso en segundos antes de cargar la forma de onda. Incrementa este valor si estás experimentando tartamudeos al usar el reproductor web.",
|
||||
"playerbarWaveformStretch": "Estiramiento de la forma de onda",
|
||||
"playerbarWaveformStretch_description": "Estira la forma de onda para rellenar el espacio disponible",
|
||||
"preventSuspendOnPlayback": "Evitar la suspensión durante la reproducción",
|
||||
"preventSuspendOnPlayback_description": "Evita que la aplicación se suspenda mientras se reproduce música"
|
||||
},
|
||||
"action": {
|
||||
"editPlaylist": "editar $t(entity.playlist, {\"count\": 1})",
|
||||
@@ -772,7 +776,9 @@
|
||||
"lyricGap": "desfase de letra",
|
||||
"dynamicImageBlur": "tamaño de desenfoque de imagen",
|
||||
"dynamicIsImage": "habilitar imagen de fondo",
|
||||
"lyricOffset": "desplazamiento de letras (ms)"
|
||||
"lyricOffset": "desplazamiento de letras (ms)",
|
||||
"lyricScaleNonActive": "Escala de letra no activa",
|
||||
"lyricOpacityNonActive": "Opacidad de letra no activa"
|
||||
},
|
||||
"lyrics": "letras",
|
||||
"related": "relacionado",
|
||||
@@ -932,7 +938,8 @@
|
||||
"input_skipDuplicates": "saltar duplicados",
|
||||
"input_playlists": "$t(entity.playlist, {\"count\": 2})",
|
||||
"create": "Crear $t(entity.playlist, {\"count\": 1}) {{playlist}}",
|
||||
"searchOrCreate": "Buscar $t(entity.playlist, {\"count\": 2}) o escribir para crear uno nuevo"
|
||||
"searchOrCreate": "Buscar $t(entity.playlist, {\"count\": 2}) o escribir para crear uno nuevo",
|
||||
"noneAdded": "Ninguna pista fue añadida a $t(entity.playlist, {\"count\": 1}) '{{playlist}}'"
|
||||
},
|
||||
"updateServer": {
|
||||
"title": "actualizar servidor",
|
||||
@@ -1001,6 +1008,9 @@
|
||||
"export": "Exportar letras",
|
||||
"input_synced": "Exportar letras sincronizadas",
|
||||
"input_offset": "$t(setting.lyricOffset)"
|
||||
},
|
||||
"editRadioStation": {
|
||||
"success": "Estación de radio actualizada con éxito"
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
@@ -1378,6 +1388,13 @@
|
||||
"lowResolution": "Baja resolución",
|
||||
"splitGradient": "Dividir degradado",
|
||||
"noteLabels": "Etiquetas de notas",
|
||||
"lumiBars": "Barras luminiscentes"
|
||||
"lumiBars": "Barras luminiscentes",
|
||||
"systemAudioConsentAllow": "Permitir",
|
||||
"systemAudioConsentDecline": "Denegar",
|
||||
"systemAudioConsentTitle": "¿Permitir acceso al sistema de audio?",
|
||||
"systemAudioConsentBody": "El visualizador requiere acceso al sistema de audio para funcionar",
|
||||
"systemAudioCaptureFailed": "No se pudo iniciar la captura: {{message}}",
|
||||
"systemAudioNoAudioTrack": "Ninguna pista de audio devuelta. Asegúrate de que la captura de audio está activada cuando se solicite.",
|
||||
"systemAudioExclusiveModeNotSupported": "El visualizador no está disponible mientras el modo de audio exclusivo esté activado. Desactiva el modo de audio exclusivo en la configuración de MPV e inténtalo de nuevo."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +157,8 @@
|
||||
"doNotShowAgain": "ez erakutsi hau berriro",
|
||||
"numberOfResults": "{{numberOfResults}} emaitza",
|
||||
"rename": "berrizendatu",
|
||||
"newVersionAvailable": "bertsio berri bat eskuragarri dago"
|
||||
"newVersionAvailable": "bertsio berri bat eskuragarri dago",
|
||||
"gridRows": "sareta-errenkadak"
|
||||
},
|
||||
"player": {
|
||||
"repeat": "errepikatu",
|
||||
@@ -409,7 +410,8 @@
|
||||
"fromYear": "urtetik aurrera",
|
||||
"explicitStatus": "$t(common.explicitStatus)",
|
||||
"matchAnd": "eta",
|
||||
"matchOr": "edo"
|
||||
"matchOr": "edo",
|
||||
"sortName": "izenaren arabera ordenatu"
|
||||
},
|
||||
"setting": {
|
||||
"hotkey_playbackPause": "pausatu",
|
||||
@@ -643,7 +645,7 @@
|
||||
"passwordStore_description": "zein pasahitz/sekretu biltegi erabili. aldatu hau pasahitzak gordetzeko arazoak badituzu",
|
||||
"playerFilters": "Iragazi ilarako abestiak",
|
||||
"sidePlayQueueStyle_description": "alboko erreprodukzio-ilararen estiloa ezartzen du",
|
||||
"mediaSession_description": "Windows Media Session integrazioa gaitzen du, multimedia kontrolak eta metadatuak sistemaren bolumenaren gainjartzean eta blokeo pantailan bistaratuz (Windows bakarrik)",
|
||||
"mediaSession_description": "Media Session integrazioa gaitzen du, multimedia kontrolak eta metadatuak sistemaren bolumenaren gainjartzean eta blokeo pantailan bistaratuz",
|
||||
"sidePlayQueueStyle": "alboko erreprodukzio-ilarako estiloa",
|
||||
"skipPlaylistPage": "saltatu erreprodukzio-zerrenda orria",
|
||||
"startMinimized_description": "abiarazi aplikazioa sistemaren erretiluan",
|
||||
@@ -714,7 +716,19 @@
|
||||
"preservePitch": "mantendu tonua",
|
||||
"preventSleepOnPlayback": "erreprodukzioan loa saihestu",
|
||||
"replayGainClipping_description": "Saihestu {{ReplayGain}}-k eragindako mozketa irabazpena automatikoki jaitsiz",
|
||||
"replayGainMode_description": "doitu bolumenaren irabazia fitxategiaren metadatuetan gordetako {{ReplayGain}} balioen arabera"
|
||||
"replayGainMode_description": "doitu bolumenaren irabazia fitxategiaren metadatuetan gordetako {{ReplayGain}} balioen arabera",
|
||||
"listenbrainz": "erakutsi ListenBrainz estekak",
|
||||
"sidebarPlaylistSorting": "alboko barrako erreprodukzio-zerrenda ordenatzea",
|
||||
"sidebarPlaylistListFilterRegex_description": "ezkutatu alboko barran adierazpen erregular honekin bat datozen erreprodukzio-zerrendak",
|
||||
"sidebarPlaylistListFilterRegex_placeholder": "adib. ^Eguneroko Nahasketa.*",
|
||||
"sidebarPlaylistListFilterRegex": "erreprodukzio-zerrenda iragazteko adierazpen erregularra",
|
||||
"sidePlayQueueLayout": "alboko erreprodukzio-ilararen diseinua",
|
||||
"sidePlayQueueLayout_description": "erantsitako alboko erreprodukzio-ilararen diseinua ezartzen du",
|
||||
"sidePlayQueueLayout_optionHorizontal": "horizontala",
|
||||
"sidePlayQueueLayout_optionVertical": "bertikala",
|
||||
"skipDuration_description": "erreproduzitzailearen barran saltatzeko botoiak erabiltzean saltatzeko iraupena ezartzen du",
|
||||
"skipPlaylistPage_description": "erreprodukzio-zerrenda batera nabigatzean, joan erreprodukzio-zerrendako abestien zerrendara lehenetsitako orrialdera joan beharrean",
|
||||
"translationApiKey_description": "itzulpenerako api gakoa (zerbitzu globalaren amaiera-puntua soilik)"
|
||||
},
|
||||
"form": {
|
||||
"addServer": {
|
||||
@@ -731,7 +745,9 @@
|
||||
"success": "zerbitzaria behar bezala gehitu da",
|
||||
"input_preferInstantMix": "nahiago izan berehalako nahasketa",
|
||||
"input_preferInstantMixDescription": "erabili berehalako nahasketa soilik antzeko abestiak lortzeko. erabilgarria portaera hau aldatzen duten pluginak badituzu",
|
||||
"input_remoteUrl": "URL publikoa"
|
||||
"input_remoteUrl": "URL publikoa",
|
||||
"input_preferRemoteUrl": "url publikoa nahiago izan",
|
||||
"input_remoteUrlPlaceholder": "aukerakoa: kanpoko funtzioetarako url publikoa"
|
||||
},
|
||||
"addToPlaylist": {
|
||||
"input_playlists": "$t(entity.playlist, {\"count\": 2})",
|
||||
@@ -739,7 +755,8 @@
|
||||
"input_skipDuplicates": "saltatu bikoiztuak",
|
||||
"title": "gehitu $t(entity.playlist, {\"count\": 1})-(a)ri",
|
||||
"create": "sortu $t(entity.playlist, {\"count\": 1}) {{playlist}}",
|
||||
"searchOrCreate": "bilatu $t(entity.playlist, {\"count\": 2}) edo idatzi berri bat sortzeko"
|
||||
"searchOrCreate": "bilatu $t(entity.playlist, {\"count\": 2}) edo idatzi berri bat sortzeko",
|
||||
"noneAdded": "ez da pistarik gehitu honi $t(entity.playlist, {\"count\": 1}) '{{playlist}}'"
|
||||
},
|
||||
"createPlaylist": {
|
||||
"input_description": "$t(common.description)",
|
||||
@@ -760,7 +777,9 @@
|
||||
"success": "partekatzeko esteka arbelera kopiatu da (edo egin klik hemen irekitzeko)",
|
||||
"expireInvalid": "iraungitze-data etorkizunean izan behar da",
|
||||
"allowDownloading": "baimendu deskargatzea",
|
||||
"createFailed": "partekatzea sortzeak huts egin du (partekatzea gaituta al dago?)"
|
||||
"createFailed": "partekatzea sortzeak huts egin du (partekatzea gaituta al dago?)",
|
||||
"copyToClipboard": "Kopiatu arbelean: Ctrl+C, Enter",
|
||||
"successMustClick": "partekatzea behar bezala sortu da. egin klik hemen irekitzeko"
|
||||
},
|
||||
"deletePlaylist": {
|
||||
"success": "$t(entity.playlist, {\"count\": 1}) behar bezala ezabatu da",
|
||||
@@ -988,7 +1007,10 @@
|
||||
"recentReleases": "azken argitalpenak",
|
||||
"viewDiscography": "ikusi diskografia",
|
||||
"groupingTypeAll": "argitalpen mota guztiak",
|
||||
"groupingTypePrimary": "argitalpen mota nagusiak"
|
||||
"groupingTypePrimary": "argitalpen mota nagusiak",
|
||||
"favoriteSongs": "abesti gogokoenak",
|
||||
"topSongsCommunity": "komunitatea",
|
||||
"topSongsPersonal": "pertsonala"
|
||||
},
|
||||
"itemDetail": {
|
||||
"copyPath": "kopiatu bidea arbelean",
|
||||
@@ -1006,6 +1028,13 @@
|
||||
},
|
||||
"radioList": {
|
||||
"title": "irrati-kateak"
|
||||
},
|
||||
"releasenotes": {
|
||||
"noStableReleaseToCompare": "ez dago bertsio egonkorrik alderatzeko"
|
||||
},
|
||||
"windowBar": {
|
||||
"paused": "(Pausatuta) ",
|
||||
"privateMode": "(Modu pribatua)"
|
||||
}
|
||||
},
|
||||
"releaseType": {
|
||||
@@ -1038,7 +1067,19 @@
|
||||
"notContains": "ez dauka",
|
||||
"startsWith": "honekin hasten da",
|
||||
"endsWith": "honekin amaitzen da",
|
||||
"isNot": "ez da"
|
||||
"isNot": "ez da",
|
||||
"afterDate": "(data) ondoren da",
|
||||
"after": "ondoren da",
|
||||
"before": "lehenago da",
|
||||
"beforeDate": "(data) baino lehenagokoa da",
|
||||
"inTheLast": "azkenengoan dago",
|
||||
"inPlaylist": "bertan dago",
|
||||
"inTheRange": "tartean dago",
|
||||
"inTheRangeDate": "(data) tartean dago",
|
||||
"isGreaterThan": "hau baino handiagoa da",
|
||||
"isLessThan": "hau baino gutxiago da",
|
||||
"notInPlaylist": "ez dago barruan",
|
||||
"notInTheLast": "ez dago azkenengoan"
|
||||
},
|
||||
"visualizer": {
|
||||
"general": "Orokorra",
|
||||
|
||||
@@ -246,10 +246,10 @@
|
||||
"loginRateError": "trop de tentatives de connexion, merci de réessayer dans quelques secondes",
|
||||
"openError": "impossible d'ouvrir le fichier",
|
||||
"networkError": "une erreur de réseau est survenue",
|
||||
"badAlbum": "vous voyez cette page parce que ce titre ne fait pas parti d'un album. vous rencontrez probablement cette erreur si vous avez un titre à la racine de votre dossier musique. Jellyfin gère les chansons uniquement si elles sont dans un sous-dossier, qui est lui-même dans un dossier \"Musique(s)\"",
|
||||
"badAlbum": "vous voyez cette page parce que ce titre ne fait pas parti d'un album. vous rencontrez probablement cette erreur si vous avez un titre à la racine de votre dossier musique. Jellyfin gère les pistes uniquement si elles sont dans un sous-dossier, qui est lui-même dans un dossier \"Musique(s)\"",
|
||||
"badValue": "option {{value}} invalide. cette valeur n'existe plus",
|
||||
"notificationDenied": "les autorisations pour les notifications ont été refusées. ce paramètre n'a aucun effet",
|
||||
"multipleServerSaveQueueError": "la file d'attente contient un ou plusieurs morceaux qui ne proviennent pas du serveur actuel. Ceci n'est pas prise en charge",
|
||||
"multipleServerSaveQueueError": "la file d'attente contient un ou plusieurs titres qui ne proviennent pas du serveur actuel. Ceci n'est pas prise en charge",
|
||||
"saveQueueFailed": "échec de l'enregistrement de la file d'attente",
|
||||
"settingsSyncError": "des incohérences ont été détectées entre les paramètres du moteur de rendu et ceux du processus principal. redémarrez l'application pour appliquer les modifications",
|
||||
"noNetwork": "serveur indisponible",
|
||||
@@ -340,7 +340,9 @@
|
||||
"lyricGap": "espacement des lettres",
|
||||
"dynamicIsImage": "activer l'image d'arrière-plan",
|
||||
"dynamicImageBlur": "intensité du flou sur l'image d'arrière-plan",
|
||||
"lyricOffset": "décalage des paroles (ms)"
|
||||
"lyricOffset": "décalage des paroles (ms)",
|
||||
"lyricScaleNonActive": "mise à l'échelle des paroles inactives",
|
||||
"lyricOpacityNonActive": "opacité des paroles inactive"
|
||||
},
|
||||
"upNext": "à suivre",
|
||||
"lyrics": "paroles",
|
||||
@@ -477,7 +479,7 @@
|
||||
"groupingTypePrimary": "types de parution principale",
|
||||
"topSongsCommunity": "communauté",
|
||||
"topSongsPersonal": "personnel",
|
||||
"favoriteSongsFrom": "titres favori de {{title}}"
|
||||
"favoriteSongsFrom": "titres favoris de {{title}}"
|
||||
},
|
||||
"itemDetail": {
|
||||
"copyPath": "copier le chemin dans le presse-papiers",
|
||||
@@ -520,7 +522,7 @@
|
||||
},
|
||||
"setting": {
|
||||
"audioDevice_description": "sélectionnez le périphérique audio à utiliser pour la lecture",
|
||||
"audioExclusiveMode_description": "activer le mode de sortie exclusif. Dans ce mode, le système est généralement verrouillé et seul mpv pourra émettre de l'audio",
|
||||
"audioExclusiveMode_description": "activer le mode de sortie exclusif. Dans ce mode, le système est généralement verrouillé et seul mpv pourra émettre de l'audio. La capture audio du système de visualisation ne fonctionnera pas lorsque cette option est activée",
|
||||
"audioPlayer_description": "sélectionnez le lecteur audio à utiliser pour la lecture",
|
||||
"crossfadeDuration_description": "définit la durée du fondu enchaîné",
|
||||
"audioDevice": "périphérique audio",
|
||||
@@ -914,7 +916,7 @@
|
||||
"ignoreCors": "ignorer cors $t(common.restartRequired)",
|
||||
"error_savePassword": "une erreur s’est produite lors de la tentative de sauvegarde du mot de passe",
|
||||
"input_preferInstantMix": "préférer le mix instantané",
|
||||
"input_preferInstantMixDescription": "utiliser uniquement le mix instantané pour jouer des pistes similaires. utile si vous avez des plugins qui modifient ce comportement",
|
||||
"input_preferInstantMixDescription": "utiliser uniquement le mix instantané pour jouer des titres similaires. utile si vous avez des plugins qui modifient ce comportement",
|
||||
"input_preferRemoteUrl": "préférer une URL publique",
|
||||
"input_remoteUrl": "URL publique",
|
||||
"input_remoteUrlPlaceholder": "optionnel : URL publique pour les fonctionnalités externes"
|
||||
@@ -1002,6 +1004,9 @@
|
||||
"export": "exporter les paroles",
|
||||
"input_synced": "exporter les paroles synchronisées",
|
||||
"input_offset": "$t(setting.lyricOffset)"
|
||||
},
|
||||
"editRadioStation": {
|
||||
"success": "station de radio a été mise à jour avec succès"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
@@ -1379,6 +1384,13 @@
|
||||
},
|
||||
"lumiBars": "Lumi Bars",
|
||||
"outlineBars": "Outline Bars",
|
||||
"splitGradient": "Split Gradient"
|
||||
"splitGradient": "Split Gradient",
|
||||
"systemAudioNoAudioTrack": "Aucune piste audio n'a été renvoyée. Assurez-vous que la capture audio est activée lorsque vous y êtes invité.",
|
||||
"systemAudioConsentAllow": "Permettre",
|
||||
"systemAudioConsentBody": "Le visualiseur nécessite un accès au système audio pour fonctionner",
|
||||
"systemAudioConsentDecline": "Refuser",
|
||||
"systemAudioConsentTitle": "Permettre l'accès au système audio ?",
|
||||
"systemAudioCaptureFailed": "Impossible de démarrer la capture : {{message}}",
|
||||
"systemAudioExclusiveModeNotSupported": "Le visualiseur est indisponible lorsque le mode audio exclusif est activé. Désactivez ce mode dans les paramètres MPV et réessayez."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1037,7 +1037,7 @@
|
||||
},
|
||||
"updateServer": {
|
||||
"title": "サーバーをアップデート",
|
||||
"success": "サーバーがアップデートされました"
|
||||
"success": "サーバーの更新に成功しました"
|
||||
},
|
||||
"queryEditor": {
|
||||
"input_optionMatchAll": "すべて一致",
|
||||
@@ -1102,6 +1102,9 @@
|
||||
},
|
||||
"saveQueue": {
|
||||
"success": "プレイキューをサーバーに保存しました"
|
||||
},
|
||||
"editRadioStation": {
|
||||
"success": "ラジオ局の更新に成功しました"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
@@ -1332,6 +1335,12 @@
|
||||
"d": "D",
|
||||
"z": "Z"
|
||||
}
|
||||
}
|
||||
},
|
||||
"systemAudioConsentAllow": "許可",
|
||||
"systemAudioConsentBody": "ビジュアライザーを機能させるためには、システムオーディオへのアクセスが必要です",
|
||||
"systemAudioConsentDecline": "拒否",
|
||||
"systemAudioConsentTitle": "システムオーディオへのアクセスを許可しますか?",
|
||||
"systemAudioCaptureFailed": "キャプチャを開始できませんでした: {{message}}",
|
||||
"systemAudioNoAudioTrack": "音声トラックが返されませんでした。プロンプトが表示されたら、音声キャプチャが有効になっていることを確認してください。"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@
|
||||
"doNotShowAgain": "niet opnieuw tonen",
|
||||
"externalLinks": "externe links",
|
||||
"faster": "sneller",
|
||||
"preview": "voorvertoning",
|
||||
"preview": "voorbeeld",
|
||||
"private": "privé",
|
||||
"public": "publiekelijk",
|
||||
"recordLabel": "platenlabel",
|
||||
@@ -152,7 +152,7 @@
|
||||
"sort": "sorteer",
|
||||
"trackGain": "trackvolume",
|
||||
"trackPeak": "piekniveau",
|
||||
"clean": "schoon",
|
||||
"clean": "opschonen",
|
||||
"gridRows": "rasterrijen",
|
||||
"tableColumns": "tabelkolommen",
|
||||
"itemsMore": "{{count}} meer",
|
||||
@@ -283,7 +283,9 @@
|
||||
"unsynchronized": "niet gesynchronizeerd",
|
||||
"useImageAspectRatio": "gebruik aspect ratio van de afbeelding",
|
||||
"lyricOffset": "songtekst-vertraging (ms)",
|
||||
"showLyricProvider": "toon songtekstaanbieder"
|
||||
"showLyricProvider": "toon songtekstaanbieder",
|
||||
"lyricOpacityNonActive": "opaciteit van niet-actieve songtekst",
|
||||
"lyricScaleNonActive": "schaal niet-actieve songtekst"
|
||||
},
|
||||
"lyrics": "liedtekst",
|
||||
"related": "gerelateerd",
|
||||
@@ -680,7 +682,7 @@
|
||||
"artistReleaseTypeConfiguration_description": "configureer welke uitgavesoorten worden getoond op de albumartiestpagina en in welke volgorde",
|
||||
"audioDevice_description": "kies het audioapparaat dat wordt gebruikt om af te spelen",
|
||||
"audioDevice": "audioapparaat",
|
||||
"audioExclusiveMode_description": "schakel exclusieve uitvoermodus in. In deze modus wordt het systeem normaliter uitgesloten en zal enkel mpv audio kunnen uitvoeren",
|
||||
"audioExclusiveMode_description": "schakel exclusieve uitvoermodus in. in deze modus wordt het systeem normaliter uitgesloten en zal enkel mpv audio kunnen uitvoeren. geluidsopname in de visualiseerder zal niet werken als dit is ingeschakeld",
|
||||
"audioExclusiveMode": "audio-exclusieve modus",
|
||||
"audioPlayer_description": "kies de audiospeler om te gebruiken bij het afspelen",
|
||||
"audioPlayer": "audiospeler",
|
||||
@@ -985,7 +987,11 @@
|
||||
"sidePlayQueueLayout": "uitlijning afspeelwachtrij aan zijkant",
|
||||
"sidePlayQueueLayout_description": "stel de uitlijning in voor de afspeelwachtrij die aan de zijkant is gekoppeld",
|
||||
"sidePlayQueueLayout_optionHorizontal": "horizontaal",
|
||||
"sidePlayQueueLayout_optionVertical": "verticaal"
|
||||
"sidePlayQueueLayout_optionVertical": "verticaal",
|
||||
"playerbarWaveformStretch": "rekking van golfvorm",
|
||||
"playerbarWaveformStretch_description": "rekt de golfvorm om de beschikbare ruimte op te vullen",
|
||||
"waveformLoadingDelay": "laadvertraging van golfvorm",
|
||||
"waveformLoadingDelay_description": "vertraging in seconden voordat de golfvorm wordt geladen. vergroot deze waarde als je stotteringen ervaart bij gebruik van de webspeler."
|
||||
},
|
||||
"form": {
|
||||
"addServer": {
|
||||
@@ -1094,6 +1100,9 @@
|
||||
},
|
||||
"saveQueue": {
|
||||
"success": "wachtrij opgeslagen op server"
|
||||
},
|
||||
"editRadioStation": {
|
||||
"success": "radiozender succesvol bijgewerkt"
|
||||
}
|
||||
},
|
||||
"player": {
|
||||
@@ -1353,6 +1362,13 @@
|
||||
"d": "D",
|
||||
"z": "Z"
|
||||
}
|
||||
}
|
||||
},
|
||||
"systemAudioConsentAllow": "Toestaan",
|
||||
"systemAudioConsentBody": "De visualiseerder heeft toegang tot het audiosysteem nodig om te werken",
|
||||
"systemAudioConsentDecline": "Weigeren",
|
||||
"systemAudioConsentTitle": "Toegang tot audiosysteem toestaan?",
|
||||
"systemAudioCaptureFailed": "Kon opname niet starten: {{message}}",
|
||||
"systemAudioNoAudioTrack": "Geen geluidsspoor ontvangen. Verifieer dat audio-opname is ingeschakeld als daar om wordt gevraagd.",
|
||||
"systemAudioExclusiveModeNotSupported": "De visualiseerder is niet beschikbaar als audio-exclusieve modus is ingeschakeld. Schakel audio-exclusieve modus in de MPV-instellingen uit en probeer het opnieuw."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,7 +352,8 @@
|
||||
"input_skipDuplicates": "pomiń duplikaty",
|
||||
"input_playlists": "$t(entity.playlist, {\"count\": 2})",
|
||||
"create": "utwórz $t(entity.playlist, {\"count\": 1}) {{playlist}}",
|
||||
"searchOrCreate": "wyszukaj $t(entity.playlist, {\"count\": 2}) lub wpisz, aby utworzyć nową"
|
||||
"searchOrCreate": "wyszukaj $t(entity.playlist, {\"count\": 2}) lub wpisz, aby utworzyć nową",
|
||||
"noneAdded": "nie dodano utworów do $t(entity.playlist, {\"count\": 1}) '{{playlist}}'"
|
||||
},
|
||||
"updateServer": {
|
||||
"title": "uaktualnij serwer",
|
||||
@@ -442,7 +443,9 @@
|
||||
"lyricGap": "odstępy tekstu",
|
||||
"dynamicImageBlur": "rozmiar rozmycia obrazu",
|
||||
"dynamicIsImage": "włącz obraz w tle",
|
||||
"lyricOffset": "opóźnienie tekstów (ms)"
|
||||
"lyricOffset": "opóźnienie tekstów (ms)",
|
||||
"lyricOpacityNonActive": "przezroczystość nieaktywnego tekstu",
|
||||
"lyricScaleNonActive": "skala nieaktywnego tekstu"
|
||||
},
|
||||
"upNext": "następne",
|
||||
"lyrics": "tekst",
|
||||
@@ -702,7 +705,7 @@
|
||||
"hotkey_favoriteCurrentSong": "ulubiona $t(common.currentSong)",
|
||||
"hotkey_zoomIn": "przybliż",
|
||||
"hotkey_browserForward": "przeglądarka w przód",
|
||||
"audioExclusiveMode_description": "włącz wyłączny tryb wyjścia. W tym trybie, system zwykle jest zablokowany i może odtwarzać tylko poprzez mpv",
|
||||
"audioExclusiveMode_description": "włącz wyłączny tryb wyjścia. W tym trybie, system zwykle jest zablokowany i może odtwarzać tylko poprzez mpv. wizualizer i przechwytywanie audio przez sysytem nie będzie działać gdy jest to włączone",
|
||||
"discordUpdateInterval": "{{discord}} interwał aktualizacji rich presence",
|
||||
"fontType_optionBuiltIn": "wbudowana czcionka",
|
||||
"hotkey_playbackPlayPause": "odtwarzaj / wstrzymaj",
|
||||
@@ -1064,7 +1067,11 @@
|
||||
"sidePlayQueueLayout_optionHorizontal": "poziomy",
|
||||
"sidePlayQueueLayout_optionVertical": "pionowy",
|
||||
"waveformLoadingDelay": "opóźnienie załadowania fali",
|
||||
"waveformLoadingDelay_description": "opóźnienie w sekundach przed załadowaniem fali. zwiększ tą wartość jeżeli doświadczasz zawieszania się odtwarzacza przeglądarkowego."
|
||||
"waveformLoadingDelay_description": "opóźnienie w sekundach przed załadowaniem fali. zwiększ tą wartość jeżeli doświadczasz zawieszania się odtwarzacza przeglądarkowego.",
|
||||
"playerbarWaveformStretch": "rozciąganie przebiegu",
|
||||
"playerbarWaveformStretch_description": "rozciąga przebieg aby wypełnić dostępną przestrzeń",
|
||||
"preventSuspendOnPlayback_description": "powstrzymuj wstrzymanie podczas odtwarzania muzyki",
|
||||
"preventSuspendOnPlayback": "powstrzymuje wstrzymanie przy odtwarzaniu"
|
||||
},
|
||||
"table": {
|
||||
"config": {
|
||||
@@ -1381,6 +1388,13 @@
|
||||
},
|
||||
"pasteGradient": "Wklej Gradient",
|
||||
"pasteGradientPlaceholder": "Wklej tutaj JSON gradientu...",
|
||||
"ansiBands": "Paski ANSI"
|
||||
"ansiBands": "Paski ANSI",
|
||||
"systemAudioConsentAllow": "Zezwól",
|
||||
"systemAudioConsentBody": "Wizualizer wymaga dostępu do audio systemu do działania",
|
||||
"systemAudioConsentDecline": "Odmów",
|
||||
"systemAudioConsentTitle": "Przyznać dostęp do audio systemu?",
|
||||
"systemAudioCaptureFailed": "Nie udało się rozpocząć przechwytywania: {{message}}",
|
||||
"systemAudioNoAudioTrack": "Nie została zwrócona żadna ścieżka audio. Sprawdź czy przechwytywanie audio będzie włączone, gdy będzie o to prośba.",
|
||||
"systemAudioExclusiveModeNotSupported": "Wizualizer jest niedostępny w gdy tryb wyłącznie z dźwiękiek jest włączony. Wyłącz tryb wyłącznie audio w ustawieniach MPV i spróbuj ponownie."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
"share": "compartilhar",
|
||||
"close": "fechar",
|
||||
"translation": "tradução",
|
||||
"albumGain": "ganho do album",
|
||||
"albumGain": "ganho do álbum",
|
||||
"trackPeak": "peak da faixa",
|
||||
"albumPeak": "pico do álbum",
|
||||
"trackGain": "ganho da faixa",
|
||||
@@ -121,10 +121,21 @@
|
||||
"removeFromFavorites": "remover de $t(entity.favorite, {\"count\": 2})",
|
||||
"openIn": {
|
||||
"lastfm": "Abrir em Last.fm",
|
||||
"musicbrainz": "Abrir em MusicBrainz"
|
||||
"musicbrainz": "Abrir em MusicBrainz",
|
||||
"listenbrainz": "Abrir no ListenBrainz",
|
||||
"qobuz": "Abrir no Qobuz"
|
||||
},
|
||||
"toggleSmartPlaylistEditor": "alternar editor $t(entity.smartPlaylist)",
|
||||
"moveToNext": "mover para o próximo"
|
||||
"moveToNext": "mover para o próximo",
|
||||
"addOrRemoveFromSelection": "adicionar ou remover da seleção",
|
||||
"goToCurrent": "ir para o item atual",
|
||||
"createRadioStation": "criar $t(entity.radioStation, {\"count\": 1})",
|
||||
"deleteRadioStation": "deletar $t(entity.radioStation, {\"count\": 1})",
|
||||
"selectAll": "selecionar tudo",
|
||||
"holdToMoveToTop": "segure para ir ao topo",
|
||||
"moveItems": "mover itens",
|
||||
"viewMore": "ver mais",
|
||||
"openApplicationDirectory": "abrir a pasta do aplicativo"
|
||||
},
|
||||
"form": {
|
||||
"deletePlaylist": {
|
||||
|
||||
+47
-13
@@ -18,8 +18,11 @@
|
||||
"toggleSmartPlaylistEditor": "вкл./откл. редактор $t(entity.smartPlaylist)",
|
||||
"removeFromFavorites": "удалить из $t(entity.favorite, {\"count\": 2})",
|
||||
"openIn": {
|
||||
"lastfm": "открыть на Last.fm",
|
||||
"musicbrainz": "открыть на MusicBrainz"
|
||||
"lastfm": "Открыть на Last.fm",
|
||||
"musicbrainz": "открыть на MusicBrainz",
|
||||
"listenbrainz": "Открыть на ListenBrainz",
|
||||
"qobuz": "Открыть в Qobuz",
|
||||
"spotify": "Открыть в Spotify"
|
||||
},
|
||||
"moveToNext": "следующий",
|
||||
"addOrRemoveFromSelection": "добавить или удалить из выделения",
|
||||
@@ -167,7 +170,8 @@
|
||||
"explicit": "нецензурная лексика",
|
||||
"externalLinks": "внешние ссылки",
|
||||
"explicitStatus": "признак нецензурного контента",
|
||||
"newVersionAvailable": "доступна новая версия"
|
||||
"newVersionAvailable": "доступна новая версия",
|
||||
"numberOfResults": "{{numberOfResults}} результатов"
|
||||
},
|
||||
"entity": {
|
||||
"album_one": "альбом",
|
||||
@@ -435,7 +439,17 @@
|
||||
"home": "$t(common.home)",
|
||||
"myLibrary": "Моя библиотека",
|
||||
"shared": "Публичные плейлисты $t(entity.playlist, {\"count\": 2})",
|
||||
"collections": "коллекции"
|
||||
"collections": "коллекции",
|
||||
"albumArtists": "$t(entity.albumArtist, {\"count\": 2})",
|
||||
"albums": "$t(entity.album, {\"count\": 2})",
|
||||
"artists": "$t(entity.artist, {\"count\": 2})",
|
||||
"favorites": "$t(entity.favorite, {\"count\": 2})",
|
||||
"folders": "$t(entity.folder, {\"count\": 2})",
|
||||
"genres": "$t(entity.genre, {\"count\": 2})",
|
||||
"radio": "$t(entity.radioStation, {\"count\": 2})",
|
||||
"playlists": "$t(entity.playlist, {\"count\": 2})",
|
||||
"settings": "$t(common.setting, {\"count\": 2})",
|
||||
"tracks": "$t(entity.track, {\"count\": 2})"
|
||||
},
|
||||
"fullscreenPlayer": {
|
||||
"config": {
|
||||
@@ -452,7 +466,9 @@
|
||||
"useImageAspectRatio": "использовать соотношение сторон изображения",
|
||||
"lyricGap": "пробел между словами",
|
||||
"dynamicIsImage": "включить фоновое изображение",
|
||||
"dynamicImageBlur": "сила размытия изображения"
|
||||
"dynamicImageBlur": "сила размытия изображения",
|
||||
"lyricOpacityNonActive": "непрозрачность неактивных слов",
|
||||
"lyricScaleNonActive": "размер неактивных слов"
|
||||
},
|
||||
"upNext": "играет",
|
||||
"lyrics": "слова",
|
||||
@@ -475,7 +491,8 @@
|
||||
"selectMusicFolder": "выбрать папку с музыкой",
|
||||
"noMusicFolder": "папка с музыкой не выбрана",
|
||||
"multipleMusicFolders": "{{count}} выбрано музыкальных папок",
|
||||
"commandPalette": "открыть командную строку"
|
||||
"commandPalette": "открыть командную строку",
|
||||
"settings": "$t(common.setting, {\"count\": 2})"
|
||||
},
|
||||
"manageServers": {
|
||||
"title": "сервера",
|
||||
@@ -510,7 +527,8 @@
|
||||
"goTo": "перейти в",
|
||||
"moveToNext": "$t(action.moveToNext)",
|
||||
"playShuffled": "$t(player.shuffle)",
|
||||
"moveItems": "$t(action.moveItems)"
|
||||
"moveItems": "$t(action.moveItems)",
|
||||
"playSimilarSongs": "$t(player.playSimilarSongs)"
|
||||
},
|
||||
"home": {
|
||||
"mostPlayed": "слушают чаще всего",
|
||||
@@ -518,7 +536,8 @@
|
||||
"title": "$t(common.home)",
|
||||
"explore": "откройте новое",
|
||||
"recentlyPlayed": "игралось недавно",
|
||||
"recentlyReleased": "Новинки"
|
||||
"recentlyReleased": "Новинки",
|
||||
"genres": "$t(entity.genre, {\"count\": 2})"
|
||||
},
|
||||
"albumDetail": {
|
||||
"moreFromArtist": "больше от $t(entity.artist, {\"count\": 1})",
|
||||
@@ -552,10 +571,13 @@
|
||||
},
|
||||
"genreList": {
|
||||
"showAlbums": "показать $t(entity.genre, {\"count\": 1}) $t(entity.album, {\"count\": 2})",
|
||||
"showTracks": "показать $t(entity.genre, {\"count\": 1}) $t(entity.track, {\"count\": 2})"
|
||||
"showTracks": "показать $t(entity.genre, {\"count\": 1}) $t(entity.track, {\"count\": 2})",
|
||||
"title": "$t(entity.genre, {\"count\": 2})"
|
||||
},
|
||||
"trackList": {
|
||||
"artistTracks": "Треки {{artist}}"
|
||||
"artistTracks": "Треки {{artist}}",
|
||||
"genreTracks": "\"{{genre}}\" $t(entity.track, {\"count\": 2})",
|
||||
"title": "$t(entity.track, {\"count\": 2})"
|
||||
},
|
||||
"globalSearch": {
|
||||
"commands": {
|
||||
@@ -616,6 +638,12 @@
|
||||
},
|
||||
"favorites": {
|
||||
"title": "$t(entity.favorite, {\"count\": 2})"
|
||||
},
|
||||
"folderList": {
|
||||
"title": "$t(entity.folder, {\"count\": 2})"
|
||||
},
|
||||
"playlistList": {
|
||||
"title": "$t(entity.playlist, {\"count\": 2})"
|
||||
}
|
||||
},
|
||||
"form": {
|
||||
@@ -656,7 +684,8 @@
|
||||
"input_skipDuplicates": "не добавлять дубликаты",
|
||||
"create": "создать $t(entity.playlist, {\"count\": 1}) {{playlist}}",
|
||||
"searchOrCreate": "для создания нового списка выполните поиск по $t(entity.playlist, {\"count\": 2}) или введите соответствующий текст",
|
||||
"input_playlists": "$t(entity.playlist, {\"count\": 2})"
|
||||
"input_playlists": "$t(entity.playlist, {\"count\": 2})",
|
||||
"noneAdded": "в плейлист $t(entity.playlist, {\"count\": 1}) '{{playlist}}' не было добавлено ни одного трека"
|
||||
},
|
||||
"updateServer": {
|
||||
"title": "обновление сервера",
|
||||
@@ -725,6 +754,9 @@
|
||||
"input_played_optionUnplayed": "только не игранные треки",
|
||||
"input_played_optionPlayed": "только воспроизведённые треки",
|
||||
"input_genre": "$t(entity.genre, {\"count\": 1})"
|
||||
},
|
||||
"editRadioStation": {
|
||||
"success": "радиостанция успешно обновлена"
|
||||
}
|
||||
},
|
||||
"setting": {
|
||||
@@ -1053,7 +1085,8 @@
|
||||
"audioFadeOnStatusChange": "плавное изменение звука",
|
||||
"audioFadeOnStatusChange_description": "включает эффекты затухания и появления звука при изменении статуса (пауза/проигрывание)",
|
||||
"preventSleepOnPlayback_description": "запрещает спящий режим экрана, пока играет музыка",
|
||||
"preventSleepOnPlayback": "не переходить в спящий режим"
|
||||
"preventSleepOnPlayback": "не переходить в спящий режим",
|
||||
"discordLinkType_none": "$t(common.none)"
|
||||
},
|
||||
"releaseType": {
|
||||
"secondary": {
|
||||
@@ -1074,7 +1107,8 @@
|
||||
"other": "другие",
|
||||
"broadcast": "транслировать",
|
||||
"ep": "эп",
|
||||
"single": "сингл"
|
||||
"single": "сингл",
|
||||
"album": "$t(entity.album, {\"count\": 1})"
|
||||
}
|
||||
},
|
||||
"datetime": {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -36,7 +36,10 @@
|
||||
"openApplicationDirectory": "відкрити каталог додатків",
|
||||
"openIn": {
|
||||
"lastfm": "Відкрити в Last.fm",
|
||||
"musicbrainz": "Відкрити в MusicBrainz"
|
||||
"musicbrainz": "Відкрити в MusicBrainz",
|
||||
"listenbrainz": "Відкрити у ListenBrainz",
|
||||
"qobuz": "Відкрити у Qobuz",
|
||||
"spotify": "Відкрити у Spotify"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -164,7 +167,9 @@
|
||||
"explicit": "Експліцитний зміст",
|
||||
"gridRows": "рядки сітки",
|
||||
"tableColumns": "стовпці таблиці",
|
||||
"itemsMore": "{{count}} більше"
|
||||
"itemsMore": "{{count}} більше",
|
||||
"numberOfResults": "{{numberOfResults}} результатів",
|
||||
"newVersionAvailable": "доступна нова версія"
|
||||
},
|
||||
"entity": {
|
||||
"album_one": "альбом",
|
||||
@@ -255,7 +260,9 @@
|
||||
"serverRequired": "потрібен сервер",
|
||||
"sessionExpiredError": "ваша сесія закінчилася",
|
||||
"systemFontError": "сталася помилка під час спроби отримати системні шрифти",
|
||||
"settingsSyncError": "виявлено розбіжності між налаштуваннями в рендерері та основним процесом. перезапустіть програму, щоб застосувати зміни"
|
||||
"settingsSyncError": "виявлено розбіжності між налаштуваннями в рендерері та основним процесом. перезапустіть програму, щоб застосувати зміни",
|
||||
"invalidJson": "недійсний JSON",
|
||||
"playbackPausedDueToError": "відтворення було призупинено через помилку"
|
||||
},
|
||||
"filter": {
|
||||
"album": "$t(entity.album, {\"count\": 1})",
|
||||
@@ -281,7 +288,7 @@
|
||||
"isPublic": "є публічним",
|
||||
"isRated": "є оціненим",
|
||||
"isRecentlyPlayed": "нещодавно відтворено",
|
||||
"lastPlayed": "нещодавно відтворені",
|
||||
"lastPlayed": "останнє відтворене",
|
||||
"mostPlayed": "найбільш відтворювані",
|
||||
"name": "назва",
|
||||
"note": "примітка",
|
||||
@@ -301,7 +308,9 @@
|
||||
"title": "назва",
|
||||
"toYear": "до року",
|
||||
"trackNumber": "трек",
|
||||
"explicitStatus": "$t(common.explicitStatus)"
|
||||
"explicitStatus": "$t(common.explicitStatus)",
|
||||
"matchAnd": "і",
|
||||
"matchOr": "або"
|
||||
},
|
||||
"datetime": {
|
||||
"minuteShort": "хв.",
|
||||
@@ -414,7 +423,9 @@
|
||||
"setExpiration": "встановити термін дії",
|
||||
"success": "посилання для спільного використання скопійовано в буфер обміну (натисніть тут, щоб відкрити)",
|
||||
"expireInvalid": "термін дії повинен бути в майбутньому",
|
||||
"createFailed": "не вдалося створити спільний доступ (чи ввімкнено спільний доступ?)"
|
||||
"createFailed": "не вдалося створити спільний доступ (чи ввімкнено спільний доступ?)",
|
||||
"copyToClipboard": "Скопіювати до буфера обміну: Ctrl+C, Enter",
|
||||
"successMustClick": "посилання успішно створено, натисніть сюди, щоб відкрити"
|
||||
},
|
||||
"shuffleAll": {
|
||||
"title": "відтворити випадково",
|
||||
@@ -435,6 +446,9 @@
|
||||
"enabled": "приватний режим увімкнено, стан відтворення тепер приховано від зовнішніх інтеграцій",
|
||||
"disabled": "приватний режим вимкнено, стан відтворення тепер видно для увімкнених зовнішніх інтеграцій",
|
||||
"title": "приватний режим"
|
||||
},
|
||||
"editRadioStation": {
|
||||
"success": "радіо станція успішно оновлена"
|
||||
}
|
||||
},
|
||||
"player": {
|
||||
@@ -539,6 +553,52 @@
|
||||
"goToAlbum": "перейти до $t(entity.album, {\"count\": 1})",
|
||||
"goToAlbumArtist": "перейти до $t(entity.albumArtist, {\"count\": 1})",
|
||||
"showDetails": "отримати інформацію"
|
||||
},
|
||||
"fullscreenPlayer": {
|
||||
"config": {
|
||||
"dynamicBackground": "динамічний фон",
|
||||
"dynamicImageBlur": "розмір розмиття зображення",
|
||||
"dynamicIsImage": "включити фонове зображення",
|
||||
"followCurrentLyric": "слідкувати за поточним рядком",
|
||||
"lyricAlignment": "вирівнювання тексту",
|
||||
"lyricOffset": "затримка тексту (мс)",
|
||||
"lyricGap": "розмір між рядками",
|
||||
"lyricSize": "розмір тексту",
|
||||
"opacity": "непрозорість",
|
||||
"showLyricMatch": "показувати збіг тексту пісень",
|
||||
"showLyricProvider": "показувати джерело тексту пісень",
|
||||
"synchronized": "синхронізовано",
|
||||
"unsynchronized": "несинхронізовано",
|
||||
"useImageAspectRatio": "використовувати співвідношення сторін зображення"
|
||||
},
|
||||
"lyrics": "текст пісні",
|
||||
"related": "пов'язані",
|
||||
"upNext": "далі",
|
||||
"visualizer": "візуалізатор",
|
||||
"noLyrics": "текст пісні не знайдено"
|
||||
},
|
||||
"genreList": {
|
||||
"showAlbums": "показати $t(entity.genre, {\"count\": 1}) $t(entity.album, {\"count\": 2})",
|
||||
"showTracks": "показати $t(entity.genre, {\"count\": 1}) $t(entity.track, {\"count\": 2})",
|
||||
"title": "$t(entity.genre, {\"count\": 2})"
|
||||
},
|
||||
"folderList": {
|
||||
"title": "$t(entity.folder, {\"count\": 2})"
|
||||
},
|
||||
"globalSearch": {
|
||||
"commands": {
|
||||
"goToPage": "перейти до сторінки",
|
||||
"searchFor": "шукати на {{query}}",
|
||||
"serverCommands": "команди сервера"
|
||||
},
|
||||
"title": "команди"
|
||||
},
|
||||
"home": {
|
||||
"explore": "дослідити з вашої бібліотеки",
|
||||
"genres": "$t(entity.genre, {\"count\": 2})",
|
||||
"mostPlayed": "найбільш відтворені",
|
||||
"newlyAdded": "нещодавно додані релізи",
|
||||
"recentlyPlayed": "нещодавно відтворені"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1015,6 +1015,9 @@
|
||||
"input_played_optionPlayed": "仅已播放的曲目",
|
||||
"input_limit": "有多少首歌?",
|
||||
"input_played": "播放筛选器"
|
||||
},
|
||||
"editRadioStation": {
|
||||
"success": "电台更新成功"
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
|
||||
@@ -233,7 +233,9 @@
|
||||
"showLyricMatch": "顯示匹配的歌詞",
|
||||
"dynamicImageBlur": "圖片模糊大小",
|
||||
"dynamicIsImage": "啟用背景圖片",
|
||||
"lyricOffset": "歌詞偏移時間 (ms)"
|
||||
"lyricOffset": "歌詞偏移時間 (ms)",
|
||||
"lyricOpacityNonActive": "非活躍歌詞的不透明度",
|
||||
"lyricScaleNonActive": "非活躍歌詞的比例"
|
||||
},
|
||||
"lyrics": "歌詞",
|
||||
"related": "相關",
|
||||
@@ -433,7 +435,7 @@
|
||||
"audioDevice": "音訊設備",
|
||||
"audioDevice_description": "選擇用於播放的音訊設備",
|
||||
"audioExclusiveMode": "音訊獨佔模式",
|
||||
"audioExclusiveMode_description": "啟用獨佔輸出模式。在此模式下,系統通常被鎖定,只有 mpv 能夠輸出音訊",
|
||||
"audioExclusiveMode_description": "啟用獨佔輸出模式。在此模式下,系統通常被鎖定,只有 mpv 能夠輸出音訊。視覺化音訊截取在此選項啟用時不會作用",
|
||||
"audioPlayer": "音訊播放器",
|
||||
"crossfadeDuration": "淡入淡出持續時間",
|
||||
"crossfadeDuration_description": "設定淡入淡出持續時間",
|
||||
@@ -792,7 +794,11 @@
|
||||
"qobuz_description": "在藝術家/專輯頁面上顯示 Qobuz 的連結",
|
||||
"qobuz": "顯示 Qobuz 連結",
|
||||
"waveformLoadingDelay": "波形載入延遲",
|
||||
"waveformLoadingDelay_description": "載入波形前的延遲(以秒為單位)。如果您在使用網頁播放器時遇到卡頓,請增加此值。"
|
||||
"waveformLoadingDelay_description": "載入波形前的延遲(以秒為單位)。如果您在使用網頁播放器時遇到卡頓,請增加此值。",
|
||||
"playerbarWaveformStretch": "波形拉伸",
|
||||
"playerbarWaveformStretch_description": "拉伸波形來填補可用空間",
|
||||
"preventSuspendOnPlayback_description": "音樂播放時防止應用程式進入休眠",
|
||||
"preventSuspendOnPlayback": "在播放時防止應用程式暫停"
|
||||
},
|
||||
"table": {
|
||||
"config": {
|
||||
@@ -1042,7 +1048,8 @@
|
||||
"success": "新增 $t(entity.trackWithCount, {\"count\": {{message}} }) 到 $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })",
|
||||
"title": "新增到$t(entity.playlist, {\"count\": 1})",
|
||||
"create": "建立 $t(entity.playlist, {\"count\": 1}) {{playlist}}",
|
||||
"searchOrCreate": "搜尋$t(entity.playlist, {\"count\": 2}) 或輸入內容以建立新項目"
|
||||
"searchOrCreate": "搜尋$t(entity.playlist, {\"count\": 2}) 或輸入內容以建立新項目",
|
||||
"noneAdded": "沒有曲目新增到 $t(entity.playlist, {\"count\": 1}) '{{playlist}}'"
|
||||
},
|
||||
"createPlaylist": {
|
||||
"input_description": "$t(common.description)",
|
||||
@@ -1337,6 +1344,11 @@
|
||||
}
|
||||
},
|
||||
"systemAudioCaptureFailed": "無法開始擷取:{{message}}",
|
||||
"systemAudioNoAudioTrack": "沒有回傳任何音軌。確保在提示時啟用音訊擷取。"
|
||||
"systemAudioNoAudioTrack": "沒有回傳任何音軌。確保在提示時啟用音訊擷取。",
|
||||
"systemAudioConsentAllow": "允許",
|
||||
"systemAudioConsentBody": "此視覺化器需要存取系統音訊才能運作",
|
||||
"systemAudioConsentDecline": "拒絕",
|
||||
"systemAudioConsentTitle": "允許存取系統音訊?",
|
||||
"systemAudioExclusiveModeNotSupported": "啟用音訊獨佔模式時,視覺化不可用。 在MPV設定中停用音訊獨佔模式,然後再試一次。"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import console from 'console';
|
||||
import { app, ipcMain } from 'electron';
|
||||
import { rm } from 'fs/promises';
|
||||
import { access, rm } from 'fs/promises';
|
||||
import uniq from 'lodash/uniq';
|
||||
import MpvAPI from 'node-mpv';
|
||||
import { pid } from 'node:process';
|
||||
import process from 'process';
|
||||
|
||||
import { getMainWindow, sendToastToRenderer } from '../../../index';
|
||||
import { createLog, isWindows } from '../../../utils';
|
||||
import { createLog, isMacOS, isWindows } from '../../../utils';
|
||||
import { store } from '../settings';
|
||||
|
||||
import { PlayerData } from '/@/shared/types/domain-types';
|
||||
@@ -69,6 +69,7 @@ const mpvLog = (
|
||||
};
|
||||
|
||||
const MPV_BINARY_PATH = store.get('mpv_path') as string | undefined;
|
||||
const MACOS_MPV_BINARY_PATHS = ['/opt/homebrew/bin/mpv', '/usr/local/bin/mpv'];
|
||||
|
||||
const prefetchPlaylistParams = [
|
||||
'--prefetch-playlist=no',
|
||||
@@ -86,12 +87,38 @@ const DEFAULT_MPV_PARAMETERS = (extraParameters?: string[]) => {
|
||||
return parameters;
|
||||
};
|
||||
|
||||
const resolveMpvBinaryPath = async (binaryPath?: string) => {
|
||||
if (binaryPath) {
|
||||
return binaryPath;
|
||||
}
|
||||
|
||||
if (MPV_BINARY_PATH) {
|
||||
return MPV_BINARY_PATH;
|
||||
}
|
||||
|
||||
if (!isMacOS()) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
for (const candidate of MACOS_MPV_BINARY_PATHS) {
|
||||
try {
|
||||
await access(candidate);
|
||||
return candidate;
|
||||
} catch {
|
||||
// Try the next common Homebrew location.
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const createMpv = async (data: {
|
||||
binaryPath?: string;
|
||||
extraParameters?: string[];
|
||||
properties?: Record<string, any>;
|
||||
}): Promise<MpvAPI> => {
|
||||
const { binaryPath, extraParameters, properties } = data;
|
||||
const resolvedBinaryPath = await resolveMpvBinaryPath(binaryPath);
|
||||
|
||||
const params = uniq([...DEFAULT_MPV_PARAMETERS(extraParameters), ...(extraParameters || [])]);
|
||||
|
||||
@@ -99,7 +126,7 @@ const createMpv = async (data: {
|
||||
{
|
||||
audio_only: true,
|
||||
auto_restart: false,
|
||||
binary: binaryPath || MPV_BINARY_PATH || undefined,
|
||||
binary: resolvedBinaryPath,
|
||||
socket: socketPath,
|
||||
time_update: 1,
|
||||
},
|
||||
|
||||
@@ -1,2 +1,9 @@
|
||||
import './core';
|
||||
import(`./${process.platform}`);
|
||||
|
||||
if (process.platform === 'linux') {
|
||||
import('./linux');
|
||||
} else if (process.platform === 'darwin') {
|
||||
import('./darwin');
|
||||
} else if (process.platform === 'win32') {
|
||||
import('./win32');
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export {};
|
||||
|
||||
+15
-6
@@ -734,14 +734,21 @@ async function createWindow(first = true): Promise<void> {
|
||||
});
|
||||
|
||||
mainWindow.webContents.session.setDisplayMediaRequestHandler((_request, callback) => {
|
||||
if (!isMacOS()) {
|
||||
callback({ audio: 'loopback' });
|
||||
return;
|
||||
}
|
||||
|
||||
desktopCapturer
|
||||
.getSources({ types: ['screen'] })
|
||||
.getSources({ thumbnailSize: { height: 0, width: 0 }, types: ['screen'] })
|
||||
.then((sources) => {
|
||||
if (sources.length > 0) {
|
||||
callback({ audio: 'loopback', video: sources[0] });
|
||||
} else {
|
||||
const source = sources[0];
|
||||
if (!source) {
|
||||
callback({});
|
||||
return;
|
||||
}
|
||||
|
||||
callback({ audio: 'loopback', video: source });
|
||||
})
|
||||
.catch((err) => {
|
||||
log.warn('desktopCapturer.getSources failed', err);
|
||||
@@ -924,12 +931,14 @@ ipcMain.on(
|
||||
},
|
||||
);
|
||||
|
||||
ipcMain.handle('power-save-blocker-start', () => {
|
||||
ipcMain.handle('power-save-blocker-start', (_event, { full }: { full: boolean }) => {
|
||||
if (powerSaveBlockerId !== null) {
|
||||
return powerSaveBlockerId;
|
||||
}
|
||||
|
||||
powerSaveBlockerId = powerSaveBlocker.start('prevent-display-sleep');
|
||||
powerSaveBlockerId = powerSaveBlocker.start(
|
||||
full ? 'prevent-display-sleep' : 'prevent-app-suspension',
|
||||
);
|
||||
return powerSaveBlockerId;
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { SetActivity } from '@xhayper/discord-rpc';
|
||||
import type { SetActivity } from '@xhayper/discord-rpc';
|
||||
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
const initialize = (clientId: string) => {
|
||||
|
||||
@@ -409,6 +409,8 @@ export const JellyfinController: InternalControllerEndpoint = {
|
||||
return jfNormalize.album(
|
||||
{ ...res.body, Songs: songsRes.body.Items },
|
||||
apiClientProps.server,
|
||||
args.context?.pathReplace,
|
||||
args.context?.pathReplaceWith,
|
||||
);
|
||||
},
|
||||
getAlbumList: async (args) => {
|
||||
@@ -580,7 +582,8 @@ export const JellyfinController: InternalControllerEndpoint = {
|
||||
|
||||
return `${apiClientProps.server?.url}/items/${query.id}/download?apiKey=${apiClientProps.server?.credential}`;
|
||||
},
|
||||
getFolder: async ({ apiClientProps, query }) => {
|
||||
getFolder: async (args) => {
|
||||
const { apiClientProps, query } = args;
|
||||
const userId = apiClientProps.server?.userId;
|
||||
|
||||
if (!userId) throw new Error('No userId found');
|
||||
@@ -742,6 +745,8 @@ export const JellyfinController: InternalControllerEndpoint = {
|
||||
jfNormalize.song(
|
||||
item as unknown as z.infer<typeof jfType._response.song>,
|
||||
apiClientProps.server,
|
||||
args.context?.pathReplace,
|
||||
args.context?.pathReplaceWith,
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -496,7 +496,12 @@ export const NavidromeController: InternalControllerEndpoint = {
|
||||
}
|
||||
|
||||
return res.body.similarSongs.song.map((song) =>
|
||||
ssNormalize.song(song, apiClientProps.server),
|
||||
ssNormalize.song(
|
||||
song,
|
||||
apiClientProps.server,
|
||||
args.context?.pathReplace,
|
||||
args.context?.pathReplaceWith,
|
||||
),
|
||||
);
|
||||
},
|
||||
getArtistList: async (args) => {
|
||||
@@ -566,7 +571,12 @@ export const NavidromeController: InternalControllerEndpoint = {
|
||||
}
|
||||
|
||||
return res.body.similarSongs2.song.map((song) =>
|
||||
ssNormalize.song(song, apiClientProps.server),
|
||||
ssNormalize.song(
|
||||
song,
|
||||
apiClientProps.server,
|
||||
args.context?.pathReplace,
|
||||
args.context?.pathReplaceWith,
|
||||
),
|
||||
);
|
||||
},
|
||||
getDownloadUrl: SubsonicController.getDownloadUrl,
|
||||
@@ -823,7 +833,14 @@ export const NavidromeController: InternalControllerEndpoint = {
|
||||
return (
|
||||
(res.body.similarSongs?.song || [])
|
||||
.filter((song) => song.id !== query.songId)
|
||||
.map((song) => ssNormalize.song(song, apiClientProps.server)) || []
|
||||
.map((song) =>
|
||||
ssNormalize.song(
|
||||
song,
|
||||
apiClientProps.server,
|
||||
args.context?.pathReplace,
|
||||
args.context?.pathReplaceWith,
|
||||
),
|
||||
) || []
|
||||
);
|
||||
},
|
||||
getSongDetail: async (args) => {
|
||||
@@ -1022,6 +1039,7 @@ export const NavidromeController: InternalControllerEndpoint = {
|
||||
|
||||
const res = await NavidromeController.getSongList({
|
||||
apiClientProps,
|
||||
context: args.context,
|
||||
query: {
|
||||
artistIds: [query.artistId],
|
||||
sortBy: SongListSort.PLAY_COUNT,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { initClient, initContract } from '@ts-rest/core';
|
||||
import axios, { AxiosError, AxiosRequestConfig, AxiosResponse, isAxiosError } from 'axios';
|
||||
import omitBy from 'lodash/omitBy';
|
||||
import qs from 'qs';
|
||||
import { z } from 'zod';
|
||||
|
||||
@@ -377,11 +376,39 @@ axiosClient.interceptors.response.use(
|
||||
},
|
||||
);
|
||||
|
||||
const keysToSkipEmptyCheck = new Set([
|
||||
'artist',
|
||||
'comment',
|
||||
'genre',
|
||||
'name',
|
||||
'query',
|
||||
'u',
|
||||
'username',
|
||||
]);
|
||||
|
||||
const parsePath = (fullPath: string) => {
|
||||
const [path, params] = fullPath.split('?');
|
||||
|
||||
const parsedParams = qs.parse(params, { arrayLimit: 99999, parameterLimit: 99999 });
|
||||
const notNilParams = omitBy(parsedParams, (value) => value === 'undefined' || value === 'null');
|
||||
const url = new URLSearchParams(params);
|
||||
const notNilParams: Record<string, string[]> = {};
|
||||
|
||||
for (const [key, value] of url) {
|
||||
if (!keysToSkipEmptyCheck.has(key) && (value === 'undefined' || value === 'null')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let realKey = key;
|
||||
|
||||
if (key.includes('[') && key.includes(']')) {
|
||||
realKey = key.split('[')[0];
|
||||
}
|
||||
|
||||
if (realKey in notNilParams) {
|
||||
notNilParams[realKey].push(value);
|
||||
} else {
|
||||
notNilParams[realKey] = [value];
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
params: notNilParams,
|
||||
|
||||
@@ -2015,8 +2015,12 @@ export const SubsonicController: InternalControllerEndpoint = {
|
||||
},
|
||||
});
|
||||
|
||||
// If the server returns an error for transcodeDecision, fall back to direct stream so that we don't break the player
|
||||
if (transcodeDecision.status !== 200) {
|
||||
throw new Error('Failed to get transcode decision');
|
||||
logFn.error(
|
||||
`Failed to get transcode decision for song ${id}, falling back to direct stream`,
|
||||
);
|
||||
return streamUrl;
|
||||
}
|
||||
|
||||
const td = transcodeDecision.body.transcodeDecision;
|
||||
@@ -2121,6 +2125,7 @@ export const SubsonicController: InternalControllerEndpoint = {
|
||||
|
||||
const res = await SubsonicController.getSongList({
|
||||
apiClientProps,
|
||||
context,
|
||||
query: {
|
||||
artistIds: [query.artistId],
|
||||
sortBy: SongListSort.PLAY_COUNT,
|
||||
|
||||
@@ -22,12 +22,7 @@ import { WebAudio } from '/@/shared/types/types';
|
||||
import '/@/shared/styles/global.css';
|
||||
import { PlayerProvider } from '/@/renderer/features/player/context/player-context';
|
||||
import { AudioPlayers } from '/@/renderer/features/player/components/audio-players';
|
||||
|
||||
const ReleaseNotesModal = lazy(() =>
|
||||
import('./release-notes-modal').then((module) => ({
|
||||
default: module.ReleaseNotesModal,
|
||||
})),
|
||||
);
|
||||
import { ReleaseNotesModal } from '/@/renderer/release-notes-modal';
|
||||
|
||||
const UpdateAvailableDialog = lazy(() =>
|
||||
import('./update-available-dialog').then((module) => ({
|
||||
@@ -82,8 +77,8 @@ const AppShell = memo(function AppShell() {
|
||||
<AppRouter />
|
||||
</PlayerProvider>
|
||||
</WebAudioContext.Provider>
|
||||
<ReleaseNotesModal />
|
||||
<Suspense fallback={null}>
|
||||
<ReleaseNotesModal />
|
||||
<UpdateAvailableDialog />
|
||||
</Suspense>
|
||||
</>
|
||||
|
||||
@@ -169,6 +169,292 @@ export interface ItemCardDerivativeProps extends Omit<ItemCardProps, 'type'> {
|
||||
showRating: boolean;
|
||||
}
|
||||
|
||||
type ItemCardData = NonNullable<ItemCardProps['data']>;
|
||||
|
||||
const ItemCardStandardImageArea = memo(function ItemCardStandardImageArea({
|
||||
controls,
|
||||
data,
|
||||
enableExpansion,
|
||||
enableImageViewport = true,
|
||||
enableNavigation,
|
||||
handleContextMenu,
|
||||
handleImageClick,
|
||||
handleLinkDragStart,
|
||||
imageAsLink,
|
||||
imageFetchPriority,
|
||||
internalState,
|
||||
isRound,
|
||||
itemType,
|
||||
navigationPath,
|
||||
showRating,
|
||||
variant,
|
||||
withControls,
|
||||
}: {
|
||||
controls?: ItemControls;
|
||||
data: ItemCardData;
|
||||
enableExpansion?: boolean;
|
||||
enableImageViewport?: boolean;
|
||||
enableNavigation?: boolean;
|
||||
handleContextMenu: (e: React.MouseEvent<HTMLElement>) => void;
|
||||
handleImageClick: (e: React.MouseEvent<HTMLElement>) => void;
|
||||
handleLinkDragStart: (e: React.DragEvent<HTMLAnchorElement>) => void;
|
||||
imageAsLink?: boolean;
|
||||
imageFetchPriority?: 'auto' | 'high' | 'low';
|
||||
internalState?: ItemListStateActions;
|
||||
isRound?: boolean;
|
||||
itemType: LibraryItem;
|
||||
navigationPath: null | string;
|
||||
showRating: boolean;
|
||||
variant: 'default' | 'poster';
|
||||
withControls?: boolean;
|
||||
}) {
|
||||
const [showControls, setShowControls] = useState(false);
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
if (withControls) {
|
||||
setShowControls(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
if (withControls) {
|
||||
setShowControls(false);
|
||||
}
|
||||
};
|
||||
|
||||
const imageContainerClassName = clsx(styles.imageContainer, {
|
||||
[styles.isRound]: isRound,
|
||||
});
|
||||
|
||||
const isFavorite = 'userFavorite' in data && (data as { userFavorite: boolean }).userFavorite;
|
||||
const userRating =
|
||||
'userRating' in data &&
|
||||
typeof (data as { userRating: null | number }).userRating === 'number'
|
||||
? (data as { userRating: null | number }).userRating
|
||||
: null;
|
||||
const hasRating = showRating && userRating !== null && userRating > 0;
|
||||
|
||||
const imageContainerContent = (
|
||||
<>
|
||||
{itemType === LibraryItem.GENRE &&
|
||||
data &&
|
||||
'name' in data &&
|
||||
typeof (data as Genre).name === 'string' ? (
|
||||
<GenreImagePlaceholder
|
||||
className={clsx(styles.image, styles.genrePlaceholder, {
|
||||
[styles.isRound]: isRound,
|
||||
})}
|
||||
name={(data as Genre).name}
|
||||
/>
|
||||
) : (
|
||||
<ItemImage
|
||||
className={clsx(styles.image, { [styles.isRound]: isRound })}
|
||||
enableDebounce={false}
|
||||
{...(variant === 'poster' ? { enableViewport: enableImageViewport } : {})}
|
||||
explicitStatus={'explicitStatus' in data && data ? data.explicitStatus : null}
|
||||
fetchPriority={imageFetchPriority}
|
||||
id={(data as { imageId?: string })?.imageId}
|
||||
itemType={itemType}
|
||||
src={(data as { imageUrl?: string })?.imageUrl}
|
||||
type="itemCard"
|
||||
/>
|
||||
)}
|
||||
{isFavorite && <div className={styles.favoriteBadge} />}
|
||||
{hasRating && <div className={styles.ratingBadge}>{userRating}</div>}
|
||||
<AnimatePresence>
|
||||
{withControls && showControls && (
|
||||
<ItemCardControls
|
||||
controls={controls}
|
||||
enableExpansion={enableExpansion}
|
||||
{...(variant === 'poster' ? { internalState } : {})}
|
||||
item={data}
|
||||
itemType={itemType}
|
||||
showRating={showRating}
|
||||
type={variant}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</>
|
||||
);
|
||||
|
||||
return enableNavigation && navigationPath && (imageAsLink ?? !internalState) ? (
|
||||
<Link
|
||||
className={imageContainerClassName}
|
||||
draggable={false}
|
||||
onClick={handleImageClick}
|
||||
onContextMenu={handleContextMenu}
|
||||
onDragStart={handleLinkDragStart}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
state={{ item: data }}
|
||||
to={navigationPath}
|
||||
>
|
||||
{imageContainerContent}
|
||||
</Link>
|
||||
) : (
|
||||
<div
|
||||
className={imageContainerClassName}
|
||||
onClick={handleImageClick}
|
||||
onContextMenu={handleContextMenu}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
{imageContainerContent}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
ItemCardStandardImageArea.displayName = 'ItemCardStandardImageArea';
|
||||
|
||||
const CompactItemCardImageArea = memo(function CompactItemCardImageArea({
|
||||
controls,
|
||||
data,
|
||||
enableExpansion,
|
||||
enableNavigation,
|
||||
handleContextMenu,
|
||||
handleImageClick,
|
||||
handleLinkDragStart,
|
||||
imageAsLink,
|
||||
imageFetchPriority,
|
||||
internalState,
|
||||
isRound,
|
||||
itemType,
|
||||
navigationPath,
|
||||
rows,
|
||||
showRating,
|
||||
withControls,
|
||||
}: {
|
||||
controls?: ItemControls;
|
||||
data: ItemCardData;
|
||||
enableExpansion?: boolean;
|
||||
enableNavigation?: boolean;
|
||||
handleContextMenu: (e: React.MouseEvent<HTMLElement>) => void;
|
||||
handleImageClick: (e: React.MouseEvent<HTMLElement>) => void;
|
||||
handleLinkDragStart: (e: React.DragEvent<HTMLAnchorElement>) => void;
|
||||
imageAsLink?: boolean;
|
||||
imageFetchPriority?: 'auto' | 'high' | 'low';
|
||||
internalState?: ItemListStateActions;
|
||||
isRound?: boolean;
|
||||
itemType: LibraryItem;
|
||||
navigationPath: null | string;
|
||||
rows: DataRow[];
|
||||
showRating: boolean;
|
||||
withControls?: boolean;
|
||||
}) {
|
||||
const [showControls, setShowControls] = useState(false);
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
if (withControls) {
|
||||
setShowControls(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
if (withControls) {
|
||||
setShowControls(false);
|
||||
}
|
||||
};
|
||||
|
||||
const imageContainerClassName = clsx(styles.imageContainer, {
|
||||
[styles.isRound]: isRound,
|
||||
});
|
||||
|
||||
const isFavorite = 'userFavorite' in data && (data as { userFavorite: boolean }).userFavorite;
|
||||
const userRating =
|
||||
'userRating' in data &&
|
||||
typeof (data as { userRating: null | number }).userRating === 'number'
|
||||
? (data as { userRating: null | number }).userRating
|
||||
: null;
|
||||
const hasRating = showRating && userRating !== null && userRating > 0;
|
||||
|
||||
const imageContainerContent = (
|
||||
<>
|
||||
{itemType === LibraryItem.GENRE &&
|
||||
data &&
|
||||
'name' in data &&
|
||||
typeof (data as Genre).name === 'string' ? (
|
||||
<GenreImagePlaceholder
|
||||
className={clsx(styles.image, styles.genrePlaceholder, {
|
||||
[styles.isRound]: isRound,
|
||||
})}
|
||||
name={(data as Genre).name}
|
||||
/>
|
||||
) : (
|
||||
<ItemImage
|
||||
className={clsx(styles.image, {
|
||||
[styles.isRound]: isRound,
|
||||
})}
|
||||
enableDebounce={false}
|
||||
explicitStatus={'explicitStatus' in data && data ? data.explicitStatus : null}
|
||||
fetchPriority={imageFetchPriority}
|
||||
id={data?.imageId}
|
||||
itemType={itemType}
|
||||
src={(data as Album | AlbumArtist | Playlist | Song)?.imageUrl}
|
||||
type="itemCard"
|
||||
/>
|
||||
)}
|
||||
{isFavorite && <div className={styles.favoriteBadge} />}
|
||||
{hasRating && <div className={styles.ratingBadge}>{userRating}</div>}
|
||||
<AnimatePresence>
|
||||
{withControls && showControls && data && (
|
||||
<ItemCardControls
|
||||
controls={controls}
|
||||
enableExpansion={enableExpansion}
|
||||
internalState={internalState}
|
||||
item={data}
|
||||
itemType={itemType}
|
||||
showRating={showRating}
|
||||
type="compact"
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<div className={clsx(styles.detailContainer, styles.compact)}>
|
||||
{rows
|
||||
.filter(
|
||||
(row): row is NonNullable<typeof row> => row !== null && row !== undefined,
|
||||
)
|
||||
.map((row, index) => (
|
||||
<ItemCardRow
|
||||
data={data!}
|
||||
index={index}
|
||||
key={row.id}
|
||||
row={row}
|
||||
type="compact"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
return enableNavigation && navigationPath && (imageAsLink ?? !internalState) ? (
|
||||
<Link
|
||||
className={imageContainerClassName}
|
||||
draggable={false}
|
||||
onClick={handleImageClick}
|
||||
onContextMenu={handleContextMenu}
|
||||
onDragStart={handleLinkDragStart}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
state={{ item: data }}
|
||||
to={navigationPath}
|
||||
>
|
||||
{imageContainerContent}
|
||||
</Link>
|
||||
) : (
|
||||
<div
|
||||
className={imageContainerClassName}
|
||||
onClick={handleImageClick}
|
||||
onContextMenu={handleContextMenu}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
{imageContainerContent}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
CompactItemCardImageArea.displayName = 'CompactItemCardImageArea';
|
||||
|
||||
const CompactItemCard = ({
|
||||
controls,
|
||||
data,
|
||||
@@ -185,7 +471,6 @@ const CompactItemCard = ({
|
||||
showRating,
|
||||
withControls,
|
||||
}: ItemCardDerivativeProps) => {
|
||||
const [showControls, setShowControls] = useState(false);
|
||||
const itemRowId =
|
||||
data && internalState && typeof data === 'object' && 'id' in data
|
||||
? internalState.extractRowId(data)
|
||||
@@ -297,18 +582,6 @@ const CompactItemCard = ({
|
||||
if (data) {
|
||||
const navigationPath = getItemNavigationPath(data, itemType);
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
if (withControls) {
|
||||
setShowControls(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
if (withControls) {
|
||||
setShowControls(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleContextMenu = (e: React.MouseEvent<HTMLElement>) => {
|
||||
if (!data || !controls) {
|
||||
return;
|
||||
@@ -338,81 +611,6 @@ const CompactItemCard = ({
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const isFavorite =
|
||||
'userFavorite' in data && (data as { userFavorite: boolean }).userFavorite;
|
||||
const userRating =
|
||||
'userRating' in data &&
|
||||
typeof (data as { userRating: null | number }).userRating === 'number'
|
||||
? (data as { userRating: null | number }).userRating
|
||||
: null;
|
||||
const hasRating = showRating && userRating !== null && userRating > 0;
|
||||
|
||||
const imageContainerClassName = clsx(styles.imageContainer, {
|
||||
[styles.isRound]: isRound,
|
||||
});
|
||||
|
||||
const imageContainerContent = (
|
||||
<>
|
||||
{itemType === LibraryItem.GENRE &&
|
||||
data &&
|
||||
'name' in data &&
|
||||
typeof (data as Genre).name === 'string' ? (
|
||||
<GenreImagePlaceholder
|
||||
className={clsx(styles.image, styles.genrePlaceholder, {
|
||||
[styles.isRound]: isRound,
|
||||
})}
|
||||
name={(data as Genre).name}
|
||||
/>
|
||||
) : (
|
||||
<ItemImage
|
||||
className={clsx(styles.image, {
|
||||
[styles.isRound]: isRound,
|
||||
})}
|
||||
enableDebounce={false}
|
||||
explicitStatus={
|
||||
'explicitStatus' in data && data ? data.explicitStatus : null
|
||||
}
|
||||
fetchPriority={imageFetchPriority}
|
||||
id={data?.imageId}
|
||||
itemType={itemType}
|
||||
src={(data as Album | AlbumArtist | Playlist | Song)?.imageUrl}
|
||||
type="itemCard"
|
||||
/>
|
||||
)}
|
||||
{isFavorite && <div className={styles.favoriteBadge} />}
|
||||
{hasRating && <div className={styles.ratingBadge}>{userRating}</div>}
|
||||
<AnimatePresence>
|
||||
{withControls && showControls && data && (
|
||||
<ItemCardControls
|
||||
controls={controls}
|
||||
enableExpansion={enableExpansion}
|
||||
internalState={internalState}
|
||||
item={data}
|
||||
itemType={itemType}
|
||||
showRating={showRating}
|
||||
type="compact"
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<div className={clsx(styles.detailContainer, styles.compact)}>
|
||||
{rows
|
||||
.filter(
|
||||
(row): row is NonNullable<typeof row> =>
|
||||
row !== null && row !== undefined,
|
||||
)
|
||||
.map((row, index) => (
|
||||
<ItemCardRow
|
||||
data={data!}
|
||||
index={index}
|
||||
key={row.id}
|
||||
row={row}
|
||||
type="compact"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(styles.container, styles.compact, {
|
||||
@@ -421,31 +619,24 @@ const CompactItemCard = ({
|
||||
})}
|
||||
ref={ref}
|
||||
>
|
||||
{enableNavigation && navigationPath && (imageAsLink ?? !internalState) ? (
|
||||
<Link
|
||||
className={imageContainerClassName}
|
||||
draggable={false}
|
||||
onClick={handleImageClick}
|
||||
onContextMenu={handleContextMenu}
|
||||
onDragStart={handleLinkDragStart}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
state={{ item: data }}
|
||||
to={navigationPath}
|
||||
>
|
||||
{imageContainerContent}
|
||||
</Link>
|
||||
) : (
|
||||
<div
|
||||
className={imageContainerClassName}
|
||||
onClick={handleImageClick}
|
||||
onContextMenu={handleContextMenu}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
{imageContainerContent}
|
||||
</div>
|
||||
)}
|
||||
<CompactItemCardImageArea
|
||||
controls={controls}
|
||||
data={data}
|
||||
enableExpansion={enableExpansion}
|
||||
enableNavigation={enableNavigation}
|
||||
handleContextMenu={handleContextMenu}
|
||||
handleImageClick={handleImageClick}
|
||||
handleLinkDragStart={handleLinkDragStart}
|
||||
imageAsLink={imageAsLink}
|
||||
imageFetchPriority={imageFetchPriority}
|
||||
internalState={internalState}
|
||||
isRound={isRound}
|
||||
itemType={itemType}
|
||||
navigationPath={navigationPath}
|
||||
rows={rows}
|
||||
showRating={showRating}
|
||||
withControls={withControls}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -491,7 +682,6 @@ const DefaultItemCard = ({
|
||||
showRating,
|
||||
withControls,
|
||||
}: ItemCardDerivativeProps) => {
|
||||
const [showControls, setShowControls] = useState(false);
|
||||
const itemRowId =
|
||||
data && internalState && typeof data === 'object' && 'id' in data
|
||||
? internalState.extractRowId(data)
|
||||
@@ -538,18 +728,6 @@ const DefaultItemCard = ({
|
||||
if (data) {
|
||||
const navigationPath = getItemNavigationPath(data, itemType);
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
if (withControls) {
|
||||
setShowControls(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
if (withControls) {
|
||||
setShowControls(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleContextMenu = (e: React.MouseEvent<HTMLElement>) => {
|
||||
if (!data || !controls) {
|
||||
return;
|
||||
@@ -579,93 +757,30 @@ const DefaultItemCard = ({
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const imageContainerClassName = clsx(styles.imageContainer, {
|
||||
[styles.isRound]: isRound,
|
||||
});
|
||||
|
||||
const isFavorite =
|
||||
'userFavorite' in data && (data as { userFavorite: boolean }).userFavorite;
|
||||
const userRating =
|
||||
'userRating' in data &&
|
||||
typeof (data as { userRating: null | number }).userRating === 'number'
|
||||
? (data as { userRating: null | number }).userRating
|
||||
: null;
|
||||
const hasRating = showRating && userRating !== null && userRating > 0;
|
||||
|
||||
const imageContainerContent = (
|
||||
<>
|
||||
{itemType === LibraryItem.GENRE &&
|
||||
data &&
|
||||
'name' in data &&
|
||||
typeof (data as Genre).name === 'string' ? (
|
||||
<GenreImagePlaceholder
|
||||
className={clsx(styles.image, styles.genrePlaceholder, {
|
||||
[styles.isRound]: isRound,
|
||||
})}
|
||||
name={(data as Genre).name}
|
||||
/>
|
||||
) : (
|
||||
<ItemImage
|
||||
className={clsx(styles.image, { [styles.isRound]: isRound })}
|
||||
enableDebounce={false}
|
||||
explicitStatus={
|
||||
'explicitStatus' in data && data ? data.explicitStatus : null
|
||||
}
|
||||
fetchPriority={imageFetchPriority}
|
||||
id={data?.imageId}
|
||||
itemType={itemType}
|
||||
src={(data as Album | AlbumArtist | Playlist | Song)?.imageUrl}
|
||||
type="itemCard"
|
||||
/>
|
||||
)}
|
||||
{isFavorite && <div className={styles.favoriteBadge} />}
|
||||
{hasRating && <div className={styles.ratingBadge}>{userRating}</div>}
|
||||
<AnimatePresence>
|
||||
{withControls && showControls && (
|
||||
<ItemCardControls
|
||||
controls={controls}
|
||||
enableExpansion={enableExpansion}
|
||||
item={data}
|
||||
itemType={itemType}
|
||||
showRating={showRating}
|
||||
type="default"
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(styles.container, {
|
||||
[styles.selected]: isSelected,
|
||||
})}
|
||||
>
|
||||
{enableNavigation && navigationPath && (imageAsLink ?? !internalState) ? (
|
||||
<Link
|
||||
className={imageContainerClassName}
|
||||
draggable={false}
|
||||
onClick={handleImageClick}
|
||||
onContextMenu={handleContextMenu}
|
||||
onDragStart={handleLinkDragStart}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
state={{ item: data }}
|
||||
to={navigationPath}
|
||||
>
|
||||
{imageContainerContent}
|
||||
</Link>
|
||||
) : (
|
||||
<div
|
||||
className={imageContainerClassName}
|
||||
onClick={handleImageClick}
|
||||
onContextMenu={handleContextMenu}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
{imageContainerContent}
|
||||
</div>
|
||||
)}
|
||||
<ItemCardStandardImageArea
|
||||
controls={controls}
|
||||
data={data}
|
||||
enableExpansion={enableExpansion}
|
||||
enableNavigation={enableNavigation}
|
||||
handleContextMenu={handleContextMenu}
|
||||
handleImageClick={handleImageClick}
|
||||
handleLinkDragStart={handleLinkDragStart}
|
||||
imageAsLink={imageAsLink}
|
||||
imageFetchPriority={imageFetchPriority}
|
||||
internalState={internalState}
|
||||
isRound={isRound}
|
||||
itemType={itemType}
|
||||
navigationPath={navigationPath}
|
||||
showRating={showRating}
|
||||
variant="default"
|
||||
withControls={withControls}
|
||||
/>
|
||||
<div className={styles.detailContainer}>
|
||||
{rows
|
||||
.filter(
|
||||
@@ -728,7 +843,6 @@ const PosterItemCard = ({
|
||||
showRating,
|
||||
withControls,
|
||||
}: ItemCardDerivativeProps) => {
|
||||
const [showControls, setShowControls] = useState(false);
|
||||
const itemRowId =
|
||||
data && internalState && typeof data === 'object' && 'id' in data
|
||||
? internalState.extractRowId(data)
|
||||
@@ -840,18 +954,6 @@ const PosterItemCard = ({
|
||||
if (data) {
|
||||
const navigationPath = getItemNavigationPath(data, itemType);
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
if (withControls) {
|
||||
setShowControls(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
if (withControls) {
|
||||
setShowControls(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleContextMenu = (e: React.MouseEvent<HTMLElement>) => {
|
||||
if (!data || !controls) {
|
||||
return;
|
||||
@@ -881,63 +983,6 @@ const PosterItemCard = ({
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const imageContainerClassName = clsx(styles.imageContainer, {
|
||||
[styles.isRound]: isRound,
|
||||
});
|
||||
|
||||
const isFavorite =
|
||||
'userFavorite' in data && (data as { userFavorite: boolean }).userFavorite;
|
||||
const userRating =
|
||||
'userRating' in data &&
|
||||
typeof (data as { userRating: null | number }).userRating === 'number'
|
||||
? (data as { userRating: null | number }).userRating
|
||||
: null;
|
||||
const hasRating = showRating && userRating !== null && userRating > 0;
|
||||
|
||||
const imageContainerContent = (
|
||||
<>
|
||||
{itemType === LibraryItem.GENRE &&
|
||||
data &&
|
||||
'name' in data &&
|
||||
typeof (data as Genre).name === 'string' ? (
|
||||
<GenreImagePlaceholder
|
||||
className={clsx(styles.image, styles.genrePlaceholder, {
|
||||
[styles.isRound]: isRound,
|
||||
})}
|
||||
name={(data as Genre).name}
|
||||
/>
|
||||
) : (
|
||||
<ItemImage
|
||||
className={clsx(styles.image, { [styles.isRound]: isRound })}
|
||||
enableDebounce={false}
|
||||
explicitStatus={
|
||||
'explicitStatus' in data && data ? data.explicitStatus : null
|
||||
}
|
||||
fetchPriority={imageFetchPriority}
|
||||
id={(data as { imageId: string })?.imageId}
|
||||
itemType={itemType}
|
||||
src={(data as { imageUrl: string })?.imageUrl}
|
||||
type="itemCard"
|
||||
/>
|
||||
)}
|
||||
{isFavorite && <div className={styles.favoriteBadge} />}
|
||||
{hasRating && <div className={styles.ratingBadge}>{userRating}</div>}
|
||||
<AnimatePresence>
|
||||
{withControls && showControls && data && (
|
||||
<ItemCardControls
|
||||
controls={controls}
|
||||
enableExpansion={enableExpansion}
|
||||
internalState={internalState}
|
||||
item={data}
|
||||
itemType={itemType}
|
||||
showRating={showRating}
|
||||
type="poster"
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(styles.container, styles.poster, {
|
||||
@@ -946,31 +991,24 @@ const PosterItemCard = ({
|
||||
})}
|
||||
ref={ref}
|
||||
>
|
||||
{enableNavigation && navigationPath && (imageAsLink ?? !internalState) ? (
|
||||
<Link
|
||||
className={imageContainerClassName}
|
||||
draggable={false}
|
||||
onClick={handleImageClick}
|
||||
onContextMenu={handleContextMenu}
|
||||
onDragStart={handleLinkDragStart}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
state={{ item: data }}
|
||||
to={navigationPath}
|
||||
>
|
||||
{imageContainerContent}
|
||||
</Link>
|
||||
) : (
|
||||
<div
|
||||
className={imageContainerClassName}
|
||||
onClick={handleImageClick}
|
||||
onContextMenu={handleContextMenu}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
{imageContainerContent}
|
||||
</div>
|
||||
)}
|
||||
<ItemCardStandardImageArea
|
||||
controls={controls}
|
||||
data={data}
|
||||
enableExpansion={enableExpansion}
|
||||
enableNavigation={enableNavigation}
|
||||
handleContextMenu={handleContextMenu}
|
||||
handleImageClick={handleImageClick}
|
||||
handleLinkDragStart={handleLinkDragStart}
|
||||
imageAsLink={imageAsLink}
|
||||
imageFetchPriority={imageFetchPriority}
|
||||
internalState={internalState}
|
||||
isRound={isRound}
|
||||
itemType={itemType}
|
||||
navigationPath={navigationPath}
|
||||
showRating={showRating}
|
||||
variant="poster"
|
||||
withControls={withControls}
|
||||
/>
|
||||
{data && (
|
||||
<div className={styles.detailContainer}>
|
||||
{rows
|
||||
|
||||
@@ -6,8 +6,8 @@ import {
|
||||
ItemListStateItemWithRequiredProperties,
|
||||
} from '/@/renderer/components/item-list/helpers/item-list-state';
|
||||
import { ItemControls } from '/@/renderer/components/item-list/types';
|
||||
import { useHotkeys } from '/@/renderer/hooks/use-hotkeys';
|
||||
import { useHotkeySettings, usePlayButtonBehavior } from '/@/renderer/store';
|
||||
import { useHotkeys } from '/@/shared/hooks/use-hotkeys';
|
||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||
import { Play } from '/@/shared/types/types';
|
||||
|
||||
|
||||
@@ -385,8 +385,8 @@ const BaseItemGridList = ({
|
||||
rows,
|
||||
size = 'default',
|
||||
}: ItemGridListProps) => {
|
||||
const rootRef = useRef(null);
|
||||
const outerRef = useRef(null);
|
||||
const rootRef = useRef<HTMLDivElement | null>(null);
|
||||
const outerRef = useRef<HTMLDivElement | null>(null);
|
||||
const listRef = useRef<FixedSizeList<GridItemProps>>(null);
|
||||
const { ref: containerRef, width: containerWidth } = useElementSize();
|
||||
const { focused, ref: containerFocusRef } = useFocusWithin();
|
||||
@@ -486,7 +486,7 @@ const BaseItemGridList = ({
|
||||
}, [itemsPerRow, rows?.length, size]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const { current: container } = containerRef;
|
||||
const container = rootRef.current;
|
||||
if (!container) return;
|
||||
|
||||
throttledSetTableMeta(containerWidth, resolvedItemCount, (meta) => {
|
||||
@@ -500,13 +500,15 @@ const BaseItemGridList = ({
|
||||
current.rowCount !== meta.rowCount
|
||||
) {
|
||||
tableMetaRef.current = meta;
|
||||
container.style.setProperty('--grid-column-count', String(meta.columnCount));
|
||||
container.style.setProperty('--grid-item-height', `${meta.itemHeight}px`);
|
||||
container.style.setProperty('--grid-row-count', String(meta.rowCount));
|
||||
const el = rootRef.current;
|
||||
if (!el) return;
|
||||
el.style.setProperty('--grid-column-count', String(meta.columnCount));
|
||||
el.style.setProperty('--grid-item-height', `${meta.itemHeight}px`);
|
||||
el.style.setProperty('--grid-row-count', String(meta.rowCount));
|
||||
setTableMetaVersion((v) => v + 1);
|
||||
}
|
||||
});
|
||||
}, [containerWidth, resolvedItemCount, throttledSetTableMeta, containerRef]);
|
||||
}, [containerWidth, resolvedItemCount, throttledSetTableMeta]);
|
||||
|
||||
const controls = useDefaultItemListControls({
|
||||
enableMultiSelect,
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
} from '/@/renderer/features/shared/components/list-sort-by-dropdown';
|
||||
import { ListSortOrderToggleButtonControlled } from '/@/renderer/features/shared/components/list-sort-order-toggle-button';
|
||||
import { FILTER_KEYS, searchLibraryItems } from '/@/renderer/features/shared/utils';
|
||||
import { useHotkeys } from '/@/renderer/hooks/use-hotkeys';
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
import { useCurrentServer, usePlayerSong } from '/@/renderer/store';
|
||||
import { useExternalLinks, useSettingsStore } from '/@/renderer/store/settings.store';
|
||||
@@ -49,7 +50,6 @@ import { Stack } from '/@/shared/components/stack/stack';
|
||||
import { TextInput } from '/@/shared/components/text-input/text-input';
|
||||
import { Text } from '/@/shared/components/text/text';
|
||||
import { useDebouncedValue } from '/@/shared/hooks/use-debounced-value';
|
||||
import { useHotkeys } from '/@/shared/hooks/use-hotkeys';
|
||||
import {
|
||||
Album,
|
||||
AlbumListSort,
|
||||
|
||||
@@ -43,6 +43,7 @@ import { searchLibraryItems } from '/@/renderer/features/shared/utils';
|
||||
import { songsQueries } from '/@/renderer/features/songs/api/songs-api';
|
||||
import { useContainerQuery } from '/@/renderer/hooks';
|
||||
import { useGenreRoute } from '/@/renderer/hooks/use-genre-route';
|
||||
import { useHotkeys } from '/@/renderer/hooks/use-hotkeys';
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
import {
|
||||
ArtistItem,
|
||||
@@ -75,7 +76,6 @@ import { TextInput } from '/@/shared/components/text-input/text-input';
|
||||
import { TextTitle } from '/@/shared/components/text-title/text-title';
|
||||
import { Text } from '/@/shared/components/text/text';
|
||||
import { useDebouncedValue } from '/@/shared/hooks/use-debounced-value';
|
||||
import { useHotkeys } from '/@/shared/hooks/use-hotkeys';
|
||||
import { useLocalStorage } from '/@/shared/hooks/use-local-storage';
|
||||
import {
|
||||
Album,
|
||||
@@ -1214,7 +1214,7 @@ export const AlbumArtistDetailContent = ({
|
||||
artistSongsLink={artistSongsLink}
|
||||
onArtistRadio={handleArtistRadio}
|
||||
/>
|
||||
<Grid gutter="2xl">
|
||||
<Grid gap="2xl">
|
||||
<AlbumArtistMetadataGenres
|
||||
genres={detailQuery.data?.genres}
|
||||
order={genresOrder}
|
||||
|
||||
@@ -13,6 +13,15 @@ export type GroupingType = 'all' | 'primary';
|
||||
|
||||
const PRIMARY_RELEASE_TYPES = ['album', 'broadcast', 'ep', 'other', 'single'];
|
||||
|
||||
const getNormalizedReleaseTypes = (album: Album): string[] => {
|
||||
const rawReleaseTypes = [...(album.releaseTypes || []), album.releaseType || ''];
|
||||
const normalizedReleaseTypes = rawReleaseTypes
|
||||
.map((type) => type.trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
|
||||
return [...new Set(normalizedReleaseTypes)];
|
||||
};
|
||||
|
||||
export const groupAlbumsByReleaseType = (
|
||||
albums: Album[],
|
||||
routeId: string,
|
||||
@@ -44,10 +53,9 @@ export const groupAlbumsByReleaseType = (
|
||||
}
|
||||
|
||||
// Group by all release types
|
||||
const releaseTypes = album.releaseTypes || [];
|
||||
if (releaseTypes.length > 0) {
|
||||
const normalizedTypes = getNormalizedReleaseTypes(album);
|
||||
if (normalizedTypes.length > 0) {
|
||||
// Sort release types: primaries first (alphabetically), then secondaries (alphabetically)
|
||||
const normalizedTypes = releaseTypes.map((type) => type.toLowerCase());
|
||||
const primaryTypes = normalizedTypes
|
||||
.filter((type) => PRIMARY_RELEASE_TYPES.includes(type))
|
||||
.sort();
|
||||
@@ -92,8 +100,7 @@ export const groupAlbumsByReleaseType = (
|
||||
return acc;
|
||||
}
|
||||
|
||||
const releaseTypes = album.releaseTypes || [];
|
||||
const normalizedTypes = releaseTypes.map((type) => type.toLowerCase());
|
||||
const normalizedTypes = getNormalizedReleaseTypes(album);
|
||||
|
||||
let matchedType: null | string = null;
|
||||
|
||||
@@ -107,6 +114,8 @@ export const groupAlbumsByReleaseType = (
|
||||
matchedType = 'broadcast';
|
||||
} else if (normalizedTypes.includes('other')) {
|
||||
matchedType = 'other';
|
||||
} else if (normalizedTypes.length > 0) {
|
||||
matchedType = normalizedTypes[0];
|
||||
} else {
|
||||
matchedType = 'album';
|
||||
}
|
||||
@@ -292,11 +301,11 @@ export const getArtistAlbumsGrouped = (
|
||||
const types = releaseType.split('/');
|
||||
return types.some((type) => {
|
||||
const enumValue = releaseTypeToEnumMap[type];
|
||||
return enumValue ? enabledReleaseTypeEnums.has(enumValue) : false;
|
||||
return enumValue ? enabledReleaseTypeEnums.has(enumValue) : true;
|
||||
});
|
||||
}
|
||||
const enumValue = releaseTypeToEnumMap[releaseType];
|
||||
return enumValue ? enabledReleaseTypeEnums.has(enumValue) : false;
|
||||
return enumValue ? enabledReleaseTypeEnums.has(enumValue) : true;
|
||||
};
|
||||
|
||||
const releaseTypeEntries = Object.entries(albumsByReleaseType)
|
||||
|
||||
@@ -165,7 +165,7 @@ export const AddToPlaylistAction = ({ items, itemType }: AddToPlaylistActionProp
|
||||
);
|
||||
|
||||
const handleAddToPlaylist = useCallback(
|
||||
async (playlistId: string) => {
|
||||
async (playlistId: string, playlistName: string) => {
|
||||
if (items.length === 0 || !serverId) return;
|
||||
|
||||
try {
|
||||
@@ -202,10 +202,9 @@ export const AddToPlaylistAction = ({ items, itemType }: AddToPlaylistActionProp
|
||||
}
|
||||
|
||||
if (allSongIds.length === 0) {
|
||||
toast.success({
|
||||
message: t('form.addToPlaylist.success', {
|
||||
message: 0,
|
||||
numOfPlaylists: 1,
|
||||
toast.info({
|
||||
message: t('form.addToPlaylist.noneAdded', {
|
||||
playlist: playlistName,
|
||||
postProcess: 'sentenceCase',
|
||||
}),
|
||||
});
|
||||
@@ -245,10 +244,9 @@ export const AddToPlaylistAction = ({ items, itemType }: AddToPlaylistActionProp
|
||||
}
|
||||
|
||||
if (songsToAdd.length === 0) {
|
||||
toast.success({
|
||||
message: t('form.addToPlaylist.success', {
|
||||
message: 0,
|
||||
numOfPlaylists: 1,
|
||||
toast.info({
|
||||
message: t('form.addToPlaylist.noneAdded', {
|
||||
playlist: playlistName,
|
||||
postProcess: 'sentenceCase',
|
||||
}),
|
||||
});
|
||||
@@ -413,7 +411,9 @@ export const AddToPlaylistAction = ({ items, itemType }: AddToPlaylistActionProp
|
||||
<>
|
||||
<ContextMenu.Item
|
||||
key={recentPlaylist.id}
|
||||
onSelect={() => handleAddToPlaylist(recentPlaylist.id)}
|
||||
onSelect={() =>
|
||||
handleAddToPlaylist(recentPlaylist.id, recentPlaylist.name)
|
||||
}
|
||||
>
|
||||
{recentPlaylist.name}
|
||||
</ContextMenu.Item>
|
||||
@@ -428,7 +428,7 @@ export const AddToPlaylistAction = ({ items, itemType }: AddToPlaylistActionProp
|
||||
{filteredPlaylists.map((playlist) => (
|
||||
<ContextMenu.Item
|
||||
key={playlist.id}
|
||||
onSelect={() => handleAddToPlaylist(playlist.id)}
|
||||
onSelect={() => handleAddToPlaylist(playlist.id, playlist.name)}
|
||||
>
|
||||
{playlist.name}
|
||||
</ContextMenu.Item>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { SetActivity, StatusDisplayType } from '@xhayper/discord-rpc';
|
||||
import type { SetActivity } from '@xhayper/discord-rpc';
|
||||
|
||||
import isElectron from 'is-electron';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
@@ -27,6 +28,13 @@ import { LibraryItem, QueueSong, ServerType } from '/@/shared/types/domain-types
|
||||
import { PlayerStatus } from '/@/shared/types/types';
|
||||
|
||||
const discordRpc = isElectron() ? window.api.discordRpc : null;
|
||||
|
||||
const DiscordStatusDisplayType = {
|
||||
DETAILS: 2,
|
||||
NAME: 0,
|
||||
STATE: 1,
|
||||
} as const;
|
||||
|
||||
type ActivityState = [QueueSong | undefined, number, PlayerStatus];
|
||||
|
||||
const MAX_FIELD_LENGTH = 127;
|
||||
@@ -122,7 +130,7 @@ export const useDiscordRpc = () => {
|
||||
: undefined
|
||||
: sentenceCase(current[2]),
|
||||
state: truncate(artist),
|
||||
statusDisplayType: StatusDisplayType.STATE,
|
||||
statusDisplayType: DiscordStatusDisplayType.STATE,
|
||||
type: discordSettings.showAsListening ? 2 : 0,
|
||||
};
|
||||
|
||||
@@ -196,9 +204,9 @@ export const useDiscordRpc = () => {
|
||||
const artists = song?.artists.map((artist) => artist.name).join(', ');
|
||||
|
||||
const statusDisplayMap = {
|
||||
[DiscordDisplayType.ARTIST_NAME]: StatusDisplayType.STATE,
|
||||
[DiscordDisplayType.FEISHIN]: StatusDisplayType.NAME,
|
||||
[DiscordDisplayType.SONG_NAME]: StatusDisplayType.DETAILS,
|
||||
[DiscordDisplayType.ARTIST_NAME]: DiscordStatusDisplayType.STATE,
|
||||
[DiscordDisplayType.FEISHIN]: DiscordStatusDisplayType.NAME,
|
||||
[DiscordDisplayType.SONG_NAME]: DiscordStatusDisplayType.DETAILS,
|
||||
};
|
||||
|
||||
const activity: SetActivity = {
|
||||
|
||||
@@ -17,6 +17,7 @@ import { MultiSelect } from '/@/shared/components/multi-select/multi-select';
|
||||
import { NumberInput } from '/@/shared/components/number-input/number-input';
|
||||
import { SegmentedControl } from '/@/shared/components/segmented-control/segmented-control';
|
||||
import { Select } from '/@/shared/components/select/select';
|
||||
import { Slider } from '/@/shared/components/slider/slider';
|
||||
import { Stack } from '/@/shared/components/stack/stack';
|
||||
import { Switch } from '/@/shared/components/switch/switch';
|
||||
import { TextInput } from '/@/shared/components/text-input/text-input';
|
||||
@@ -185,6 +186,48 @@ export const LyricsSettingsForm = ({ settingsKey }: LyricsSettingsFormProps) =>
|
||||
postProcess: 'sentenceCase',
|
||||
}),
|
||||
},
|
||||
{
|
||||
control: (
|
||||
<Slider
|
||||
defaultValue={displaySettings.opacityNonActive}
|
||||
label={(e) => (e * 100).toFixed(0) + '%'}
|
||||
max={1.0}
|
||||
min={0.0}
|
||||
onChangeEnd={(e) => {
|
||||
updateDisplaySetting({
|
||||
opacityNonActive: e,
|
||||
});
|
||||
}}
|
||||
step={0.01}
|
||||
w={100}
|
||||
/>
|
||||
),
|
||||
description: '',
|
||||
title: t(`${t('page.fullscreenPlayer.config.lyricOpacityNonActive')}`, {
|
||||
postProcess: 'sentenceCase',
|
||||
}),
|
||||
},
|
||||
{
|
||||
control: (
|
||||
<Slider
|
||||
defaultValue={displaySettings.scaleNonActive}
|
||||
label={(e) => (e * 100).toFixed(0) + '%'}
|
||||
max={1.0}
|
||||
min={0.5}
|
||||
onChangeEnd={(e) => {
|
||||
updateDisplaySetting({
|
||||
scaleNonActive: e,
|
||||
});
|
||||
}}
|
||||
step={0.01}
|
||||
w={100}
|
||||
/>
|
||||
),
|
||||
description: '',
|
||||
title: t(`${t('page.fullscreenPlayer.config.lyricScaleNonActive')}`, {
|
||||
postProcess: 'sentenceCase',
|
||||
}),
|
||||
},
|
||||
{
|
||||
control: (
|
||||
<Switch
|
||||
|
||||
@@ -4,14 +4,18 @@
|
||||
line-height: 1.2;
|
||||
color: var(--theme-colors-foreground);
|
||||
word-break: normal;
|
||||
opacity: 0.35;
|
||||
opacity: var(--lyric-opacity);
|
||||
transform: scale(var(--lyric-scale));
|
||||
transform-origin: var(--lyric-scale-origin) center;
|
||||
transition:
|
||||
opacity 0.3s ease-in-out,
|
||||
transform 0.3s ease-in-out;
|
||||
opacity 0.6s cubic-bezier(0.16, 1, 0.3, 1),
|
||||
transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.lyric-line:global(.active) {
|
||||
opacity: 1 !important;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.lyric-line:global(.unsynchronized) {
|
||||
|
||||
@@ -49,6 +49,11 @@ export const SynchronizedLyrics = ({
|
||||
? displaySettings.fontSize
|
||||
: 24,
|
||||
gap: displaySettings.gap && displaySettings.gap !== 0 ? displaySettings.gap : 24,
|
||||
opacityNonActive: displaySettings.opacityNonActive,
|
||||
scaleNonActive:
|
||||
displaySettings.scaleNonActive && displaySettings.scaleNonActive !== 0
|
||||
? displaySettings.scaleNonActive
|
||||
: 0.95,
|
||||
};
|
||||
const { mediaSeekToTimestamp } = usePlayerActions();
|
||||
const status = usePlayerStatus();
|
||||
@@ -90,18 +95,20 @@ export const SynchronizedLyrics = ({
|
||||
const programmaticScrollRef = useRef(false);
|
||||
|
||||
const getCurrentLyric = (timeInMs: number) => {
|
||||
if (lyricRef.current) {
|
||||
const activeLyrics = lyricRef.current;
|
||||
for (let idx = 0; idx < activeLyrics.length; idx += 1) {
|
||||
if (timeInMs <= activeLyrics[idx][0]) {
|
||||
return idx === 0 ? idx : idx - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return activeLyrics.length - 1;
|
||||
const activeLyrics = lyricRef.current;
|
||||
if (!activeLyrics?.length) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return -1;
|
||||
let index = -1;
|
||||
for (let idx = 0; idx < activeLyrics.length; idx += 1) {
|
||||
if (timeInMs < activeLyrics[idx][0]) {
|
||||
break;
|
||||
}
|
||||
index = idx;
|
||||
}
|
||||
|
||||
return index;
|
||||
};
|
||||
|
||||
const setCurrentLyricRef = useRef<
|
||||
@@ -136,7 +143,20 @@ export const SynchronizedLyrics = ({
|
||||
.forEach((node) => node.classList.remove('active'));
|
||||
|
||||
if (index === -1) {
|
||||
lyricRef.current = null;
|
||||
const activeLyrics = lyricRef.current;
|
||||
if (!activeLyrics?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstTime = activeLyrics[0][0];
|
||||
if (timeInMs < firstTime) {
|
||||
const elapsed = performance.now() - start;
|
||||
const delay = Math.max(0, firstTime - timeInMs - elapsed);
|
||||
lyricTimer.current = setTimeout(() => {
|
||||
setCurrentLyricRef.current(firstTime, nextEpoch, 0);
|
||||
}, delay);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -148,7 +168,6 @@ export const SynchronizedLyrics = ({
|
||||
const offsetTop = currentLyric?.offsetTop - doc?.clientHeight / 2 || 0;
|
||||
|
||||
if (currentLyric === null) {
|
||||
lyricRef.current = null;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -308,7 +327,18 @@ export const SynchronizedLyrics = ({
|
||||
onMouseEnter={showScrollbar}
|
||||
onMouseLeave={hideScrollbar}
|
||||
ref={containerRef}
|
||||
style={{ gap: `${settings.gap}px`, ...style }}
|
||||
style={
|
||||
{
|
||||
// opacity/scale is set here for every lyric,
|
||||
// and then overwritten by CSS for active lyrics
|
||||
// to prevent expensive rerenders each lyric
|
||||
'--lyric-opacity': settings.opacityNonActive,
|
||||
'--lyric-scale': settings.scaleNonActive,
|
||||
'--lyric-scale-origin': settings.alignment,
|
||||
gap: `${settings.gap}px`,
|
||||
...style,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
{settings.showProvider && source && (
|
||||
<LyricLine
|
||||
|
||||
@@ -15,6 +15,7 @@ import { eventEmitter } from '/@/renderer/events/event-emitter';
|
||||
import { useIsPlayerFetching, usePlayer } from '/@/renderer/features/player/context/player-context';
|
||||
import { searchLibraryItems } from '/@/renderer/features/shared/utils';
|
||||
import { useDragDrop } from '/@/renderer/hooks/use-drag-drop';
|
||||
import { useHotkeys } from '/@/renderer/hooks/use-hotkeys';
|
||||
import {
|
||||
isShuffleEnabled,
|
||||
mapShuffledToQueueIndex,
|
||||
@@ -30,7 +31,6 @@ import { Flex } from '/@/shared/components/flex/flex';
|
||||
import { LoadingOverlay } from '/@/shared/components/loading-overlay/loading-overlay';
|
||||
import { useDebouncedValue } from '/@/shared/hooks/use-debounced-value';
|
||||
import { useFocusWithin } from '/@/shared/hooks/use-focus-within';
|
||||
import { useHotkeys } from '/@/shared/hooks/use-hotkeys';
|
||||
import { useMergedRef } from '/@/shared/hooks/use-merged-ref';
|
||||
import { Folder, LibraryItem, QueueSong, Song } from '/@/shared/types/domain-types';
|
||||
import { DragTarget } from '/@/shared/types/drag-and-drop';
|
||||
|
||||
@@ -140,9 +140,15 @@ export const WebPlayerEngine = (props: WebPlayerEngineProps) => {
|
||||
};
|
||||
},
|
||||
seekTo(seekTo: number) {
|
||||
let type: 'fraction' | 'seconds' | undefined = undefined;
|
||||
|
||||
if (seekTo < 1) {
|
||||
type = 'seconds';
|
||||
}
|
||||
|
||||
playerNum === 1
|
||||
? player1Ref.current?.seekTo(seekTo)
|
||||
: player2Ref.current?.seekTo(seekTo);
|
||||
? player1Ref.current?.seekTo(seekTo, type)
|
||||
: player2Ref.current?.seekTo(seekTo, type);
|
||||
},
|
||||
setVolume(volume: number) {
|
||||
setInternalVolume1(volume / 100 || 0);
|
||||
|
||||
@@ -240,10 +240,16 @@ export function WebPlayer() {
|
||||
}
|
||||
}
|
||||
|
||||
let type: 'fraction' | 'seconds' | undefined = undefined;
|
||||
|
||||
if (timestamp < 1) {
|
||||
type = 'seconds';
|
||||
}
|
||||
|
||||
if (num === 1) {
|
||||
playerRef.current?.player1()?.ref?.seekTo(timestamp);
|
||||
playerRef.current?.player1()?.ref?.seekTo(timestamp, type);
|
||||
} else {
|
||||
playerRef.current?.player2()?.ref?.seekTo(timestamp);
|
||||
playerRef.current?.player2()?.ref?.seekTo(timestamp, type);
|
||||
}
|
||||
},
|
||||
onPlayerStatus: async (properties) => {
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
SONG_DISPLAY_TYPES,
|
||||
} from '/@/renderer/features/shared/components/list-config-menu';
|
||||
import { useFastAverageColor } from '/@/renderer/hooks';
|
||||
import { useHotkeys } from '/@/renderer/hooks/use-hotkeys';
|
||||
import {
|
||||
useFullScreenPlayerStore,
|
||||
useFullScreenPlayerStoreActions,
|
||||
@@ -46,7 +47,6 @@ import { Popover } from '/@/shared/components/popover/popover';
|
||||
import { SegmentedControl } from '/@/shared/components/segmented-control/segmented-control';
|
||||
import { Slider } from '/@/shared/components/slider/slider';
|
||||
import { Switch } from '/@/shared/components/switch/switch';
|
||||
import { useHotkeys } from '/@/shared/hooks/use-hotkeys';
|
||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||
import { ItemListKey, ListDisplayType, Platform } from '/@/shared/types/types';
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useLocation } from 'react-router';
|
||||
import styles from './full-screen-visualizer.module.css';
|
||||
|
||||
import { FullScreenVisualizerSongInfo } from '/@/renderer/features/player/components/full-screen-visualizer-song-info';
|
||||
import { useHotkeys } from '/@/renderer/hooks/use-hotkeys';
|
||||
import { useIsMobile } from '/@/renderer/hooks/use-is-mobile';
|
||||
import { useFullScreenPlayerStoreActions } from '/@/renderer/store/full-screen-player.store';
|
||||
import {
|
||||
@@ -12,7 +13,6 @@ import {
|
||||
useSettingsStore,
|
||||
useWindowSettings,
|
||||
} from '/@/renderer/store/settings.store';
|
||||
import { useHotkeys } from '/@/shared/hooks/use-hotkeys';
|
||||
import { Platform } from '/@/shared/types/types';
|
||||
|
||||
const AudioMotionAnalyzerVisualizer = lazy(() =>
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
useIsRadioActive,
|
||||
useRadioPlayer,
|
||||
} from '/@/renderer/features/radio/hooks/use-radio-player';
|
||||
import { useHotkeys } from '/@/renderer/hooks/use-hotkeys';
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
import {
|
||||
useAppStore,
|
||||
@@ -34,7 +35,6 @@ import { Icon } from '/@/shared/components/icon/icon';
|
||||
import { Text } from '/@/shared/components/text/text';
|
||||
import { Tooltip } from '/@/shared/components/tooltip/tooltip';
|
||||
import { PlaybackSelectors } from '/@/shared/constants/playback-selectors';
|
||||
import { useHotkeys } from '/@/shared/hooks/use-hotkeys';
|
||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||
|
||||
export const LeftControls = () => {
|
||||
|
||||
@@ -58,7 +58,7 @@ export const PlayerbarWaveform = () => {
|
||||
height: 18,
|
||||
interact: false,
|
||||
media: audioElementRef.current,
|
||||
normalize: false,
|
||||
normalize: playerbarSlider?.stretched ?? false,
|
||||
progressColor: primaryColor,
|
||||
waveColor,
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ import { usePlayer } from '/@/renderer/features/player/context/player-context';
|
||||
import { useSetRating } from '/@/renderer/features/shared/hooks/use-set-rating';
|
||||
import { useCreateFavorite } from '/@/renderer/features/shared/mutations/create-favorite-mutation';
|
||||
import { useDeleteFavorite } from '/@/renderer/features/shared/mutations/delete-favorite-mutation';
|
||||
import { useHotkeys } from '/@/renderer/hooks/use-hotkeys';
|
||||
import {
|
||||
useAppStoreActions,
|
||||
useAutoDJSettings,
|
||||
@@ -34,7 +35,6 @@ import { Button } from '/@/shared/components/button/button';
|
||||
import { Flex } from '/@/shared/components/flex/flex';
|
||||
import { Group } from '/@/shared/components/group/group';
|
||||
import { Rating } from '/@/shared/components/rating/rating';
|
||||
import { useHotkeys } from '/@/shared/hooks/use-hotkeys';
|
||||
import { useMediaQuery } from '/@/shared/hooks/use-media-query';
|
||||
import { useThrottledCallback } from '/@/shared/hooks/use-throttled-callback';
|
||||
import { useThrottledValue } from '/@/shared/hooks/use-throttled-value';
|
||||
|
||||
@@ -273,7 +273,7 @@ export const SleepTimerButton = () => {
|
||||
|
||||
<Divider my="md" />
|
||||
|
||||
<Grid gutter="xs">
|
||||
<Grid gap="xs">
|
||||
{PRESET_OPTIONS.filter((option) => option.mode === 'timed').map(
|
||||
(option, index) => (
|
||||
<Grid.Col key={index} span={4}>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { HotkeyItem, useHotkeys } from '/@/renderer/hooks/use-hotkeys';
|
||||
import { useHotkeySettings, usePlayerStore } from '/@/renderer/store';
|
||||
import { HotkeyItem, useHotkeys } from '/@/shared/hooks/use-hotkeys';
|
||||
|
||||
export const usePlaybackHotkeys = () => {
|
||||
const { bindings } = useHotkeySettings();
|
||||
|
||||
@@ -8,17 +8,17 @@ const ipc = isElectron() ? window.api.ipc : null;
|
||||
|
||||
export const usePowerSaveBlocker = () => {
|
||||
const status = usePlayerStatus();
|
||||
const { preventSleepOnPlayback } = useWindowSettings();
|
||||
const { preventSleepOnPlayback, preventSuspendOnPlayback } = useWindowSettings();
|
||||
|
||||
const startPowerSaveBlocker = useCallback(async () => {
|
||||
if (!ipc) return;
|
||||
|
||||
try {
|
||||
await ipc.invoke('power-save-blocker-start');
|
||||
await ipc.invoke('power-save-blocker-start', { full: preventSleepOnPlayback });
|
||||
} catch (error) {
|
||||
console.error('Failed to start power save blocker:', error);
|
||||
}
|
||||
}, []);
|
||||
}, [preventSleepOnPlayback]);
|
||||
|
||||
const stopPowerSaveBlocker = useCallback(async () => {
|
||||
if (!ipc) return;
|
||||
@@ -31,16 +31,21 @@ export const usePowerSaveBlocker = () => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!preventSleepOnPlayback) return;
|
||||
if (!preventSleepOnPlayback || !preventSuspendOnPlayback) return;
|
||||
|
||||
if (status === PlayerStatus.PLAYING) {
|
||||
startPowerSaveBlocker();
|
||||
} else {
|
||||
stopPowerSaveBlocker();
|
||||
}
|
||||
}, [status, preventSleepOnPlayback, startPowerSaveBlocker, stopPowerSaveBlocker]);
|
||||
}, [
|
||||
status,
|
||||
preventSleepOnPlayback,
|
||||
startPowerSaveBlocker,
|
||||
stopPowerSaveBlocker,
|
||||
preventSuspendOnPlayback,
|
||||
]);
|
||||
|
||||
// Clean up on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
stopPowerSaveBlocker();
|
||||
@@ -56,8 +61,11 @@ const PowerSaveBlockerHookInner = () => {
|
||||
export const PowerSaveBlockerHook = () => {
|
||||
const isElectronEnv = isElectron();
|
||||
const preventSleepOnPlayback = useSettingsStore((state) => state.window.preventSleepOnPlayback);
|
||||
const preventSuspendOnPlayback = useSettingsStore(
|
||||
(state) => state.window.preventSuspendOnPlayback,
|
||||
);
|
||||
|
||||
if (!isElectronEnv || !preventSleepOnPlayback) {
|
||||
if (!isElectronEnv || !preventSleepOnPlayback || !preventSuspendOnPlayback) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ export function useVisualizerSystemAudio(options: {
|
||||
onDeniedRef.current = onSystemAudioCaptureDenied;
|
||||
onSuccessRef.current = onSystemAudioCaptureSuccess;
|
||||
const playbackType = usePlaybackType();
|
||||
const isMacOS = Boolean(window.api?.utils?.isMacOS?.());
|
||||
const { setWebAudio, webAudio } = useWebAudio();
|
||||
const webAudioRef = useRef(webAudio);
|
||||
const streamRef = useRef<MediaStream | null>(null);
|
||||
@@ -80,7 +81,7 @@ export function useVisualizerSystemAudio(options: {
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getDisplayMedia({
|
||||
audio: true,
|
||||
video: false,
|
||||
video: isMacOS, // On macOS, getDisplayMedia requires video to be requested in order to capture system audio
|
||||
});
|
||||
|
||||
const audioTracks = stream.getAudioTracks();
|
||||
@@ -124,7 +125,7 @@ export function useVisualizerSystemAudio(options: {
|
||||
} finally {
|
||||
connectInFlightRef.current = false;
|
||||
}
|
||||
}, [disconnect, setWebAudio]);
|
||||
}, [disconnect, isMacOS, setWebAudio]);
|
||||
|
||||
const connectRef = useRef(connect);
|
||||
connectRef.current = connect;
|
||||
|
||||
@@ -552,7 +552,7 @@ const PlaylistTableItem = memo(
|
||||
|
||||
return (
|
||||
<Box className={styles.container} w="100%">
|
||||
<Grid align="center" gutter="xs" w="100%">
|
||||
<Grid align="center" gap="xs" w="100%">
|
||||
<Grid.Col span="content">
|
||||
<Flex align="center" justify="center" px="sm">
|
||||
<ItemImage
|
||||
|
||||
@@ -477,6 +477,32 @@ export const ControlSettings = memo(() => {
|
||||
postProcess: 'sentenceCase',
|
||||
}),
|
||||
},
|
||||
{
|
||||
control: (
|
||||
<Switch
|
||||
defaultChecked={playerbarSlider?.stretched ?? false}
|
||||
onChange={(e) =>
|
||||
setSettings({
|
||||
general: {
|
||||
...settings,
|
||||
playerbarSlider: {
|
||||
...playerbarSlider,
|
||||
stretched: e.currentTarget.checked,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
),
|
||||
description: t('setting.playerbarWaveformStretch', {
|
||||
context: 'description',
|
||||
postProcess: 'sentenceCase',
|
||||
}),
|
||||
isHidden: false,
|
||||
title: t('setting.playerbarWaveformStretch', {
|
||||
postProcess: 'sentenceCase',
|
||||
}),
|
||||
},
|
||||
{
|
||||
control: (
|
||||
<NumberInput
|
||||
|
||||
@@ -146,6 +146,7 @@ export const ThemeSettings = memo(() => {
|
||||
}
|
||||
}}
|
||||
renderOption={renderThemeOption}
|
||||
searchable
|
||||
width={240}
|
||||
/>
|
||||
),
|
||||
|
||||
@@ -184,7 +184,7 @@ export const WindowSettings = memo(() => {
|
||||
<Switch
|
||||
aria-label="Toggle prevent sleep on playback"
|
||||
defaultChecked={settings.preventSleepOnPlayback}
|
||||
disabled={!isElectron()}
|
||||
disabled={!isElectron() || settings.preventSuspendOnPlayback}
|
||||
onChange={(e) => {
|
||||
if (!e) return;
|
||||
localSettings?.set(
|
||||
@@ -206,6 +206,33 @@ export const WindowSettings = memo(() => {
|
||||
isHidden: !isElectron(),
|
||||
title: t('setting.preventSleepOnPlayback', { postProcess: 'sentenceCase' }),
|
||||
},
|
||||
{
|
||||
control: (
|
||||
<Switch
|
||||
aria-label="Toggle prevent suspend on playback"
|
||||
defaultChecked={settings.preventSuspendOnPlayback}
|
||||
disabled={!isElectron() || settings.preventSleepOnPlayback}
|
||||
onChange={(e) => {
|
||||
if (!e) return;
|
||||
localSettings?.set(
|
||||
'window_prevent_suspend_on_playback',
|
||||
e.currentTarget.checked,
|
||||
);
|
||||
setSettings({
|
||||
window: {
|
||||
preventSuspendOnPlayback: e.currentTarget.checked,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
),
|
||||
description: t('setting.preventSuspendOnPlayback', {
|
||||
context: 'description',
|
||||
postProcess: 'sentenceCase',
|
||||
}),
|
||||
isHidden: !isElectron(),
|
||||
title: t('setting.preventSuspendOnPlayback', { postProcess: 'sentenceCase' }),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
background-image: var(--theme-overlay-subheader);
|
||||
transform: translate3d(0, 0, 0);
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
|
||||
.background-overlay {
|
||||
@@ -30,6 +32,18 @@
|
||||
var(--dither);
|
||||
}
|
||||
|
||||
.background-image-stack {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 0;
|
||||
width: 100%;
|
||||
pointer-events: none;
|
||||
isolation: isolate;
|
||||
transform: translate3d(0, 0, 0);
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
|
||||
.background-image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -37,6 +51,7 @@
|
||||
width: 100%;
|
||||
background-position: center !important;
|
||||
background-size: cover !important;
|
||||
isolation: isolate;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ export const LibraryBackgroundImage = ({ blur, headerRef, imageUrl }: LibraryBac
|
||||
const url = imageUrl ? `url(${imageUrl})` : undefined;
|
||||
const height = useHeaderHeight(headerRef);
|
||||
return (
|
||||
<>
|
||||
<div className={styles.backgroundImageStack}>
|
||||
<div
|
||||
className={styles.backgroundImage}
|
||||
style={{
|
||||
@@ -91,7 +91,7 @@ export const LibraryBackgroundImage = ({ blur, headerRef, imageUrl }: LibraryBac
|
||||
height: height ? `${height + 64}px` : undefined,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -9,12 +9,12 @@ import {
|
||||
} from 'react';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
||||
import { useHotkeys } from '/@/renderer/hooks/use-hotkeys';
|
||||
import { useSettingsStore } from '/@/renderer/store';
|
||||
import { ActionIcon, ActionIconProps } from '/@/shared/components/action-icon/action-icon';
|
||||
import { Box } from '/@/shared/components/box/box';
|
||||
import { Icon } from '/@/shared/components/icon/icon';
|
||||
import { TextInput, TextInputProps } from '/@/shared/components/text-input/text-input';
|
||||
import { useHotkeys } from '/@/shared/hooks/use-hotkeys';
|
||||
|
||||
interface SearchInputProps extends TextInputProps {
|
||||
buttonProps?: Partial<ActionIconProps>;
|
||||
|
||||
@@ -18,12 +18,7 @@ export const ActionBar = () => {
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<Grid
|
||||
display="flex"
|
||||
gutter="sm"
|
||||
style={{ padding: '0 var(--theme-spacing-md)' }}
|
||||
w="100%"
|
||||
>
|
||||
<Grid display="flex" gap="sm" style={{ padding: '0 var(--theme-spacing-md)' }} w="100%">
|
||||
<Grid.Col span={7}>
|
||||
<TextInput
|
||||
leftSection={<Icon icon="search" />}
|
||||
|
||||
@@ -6,7 +6,7 @@ import styles from './sidebar-item.module.css';
|
||||
|
||||
import { Button, ButtonProps } from '/@/shared/components/button/button';
|
||||
|
||||
interface SidebarItemProps extends ButtonProps {
|
||||
interface SidebarItemProps extends Omit<ButtonProps, 'component' | 'ref'> {
|
||||
to: LinkProps['to'];
|
||||
}
|
||||
|
||||
|
||||
@@ -31,8 +31,7 @@
|
||||
.image-container {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
width: var(--sidebar-image-height);
|
||||
height: var(--sidebar-image-height);
|
||||
min-height: 0;
|
||||
padding: var(--theme-spacing-md);
|
||||
cursor: pointer;
|
||||
animation: fade-in 0.2s ease-in-out;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import clsx from 'clsx';
|
||||
import { AnimatePresence, motion } from 'motion/react';
|
||||
import { CSSProperties, MouseEvent, useMemo } from 'react';
|
||||
import { MouseEvent, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import styles from './sidebar.module.css';
|
||||
@@ -159,7 +159,6 @@ export const Sidebar = () => {
|
||||
|
||||
const SidebarImage = () => {
|
||||
const { t } = useTranslation();
|
||||
const leftWidth = useAppStore((state) => state.sidebar.leftWidth);
|
||||
const { setSideBar } = useAppStoreActions();
|
||||
const currentSong = usePlayerSong();
|
||||
const isRadioActive = useIsRadioActive();
|
||||
@@ -216,11 +215,7 @@ const SidebarImage = () => {
|
||||
onClick={expandFullScreenPlayer}
|
||||
onContextMenu={handleToggleContextMenu}
|
||||
role="button"
|
||||
style={
|
||||
{
|
||||
'--sidebar-image-height': leftWidth,
|
||||
} as CSSProperties
|
||||
}
|
||||
style={{ aspectRatio: 1 }}
|
||||
transition={{ duration: 0.3, ease: 'easeInOut' }}
|
||||
>
|
||||
<Tooltip
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import isElectron from 'is-electron';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useIsLocalVisualizerSurfaceVisible } from '/@/renderer/features/player/hooks/use-is-local-visualizer-surface-visible';
|
||||
import { useVisualizerSystemAudio } from '/@/renderer/features/player/hooks/use-visualizer-system-audio';
|
||||
import { closeLocalVisualizerSurfaces } from '/@/renderer/features/player/utils/close-local-visualizer-surfaces';
|
||||
import { usePlaybackType } from '/@/renderer/store';
|
||||
import { useMpvSettings, usePlaybackType } from '/@/renderer/store';
|
||||
import { Button } from '/@/shared/components/button/button';
|
||||
import { Group } from '/@/shared/components/group/group';
|
||||
import { Modal } from '/@/shared/components/modal/modal';
|
||||
import { Stack } from '/@/shared/components/stack/stack';
|
||||
import { Text } from '/@/shared/components/text/text';
|
||||
import { toast } from '/@/shared/components/toast/toast';
|
||||
import { useDisclosure } from '/@/shared/hooks/use-disclosure';
|
||||
import { PlayerType } from '/@/shared/types/types';
|
||||
|
||||
@@ -31,12 +32,21 @@ export function VisualizerSystemAudioBridgeHook() {
|
||||
function VisualizerSystemAudioBridge() {
|
||||
const { t } = useTranslation();
|
||||
const playbackType = usePlaybackType();
|
||||
const { audioExclusiveMode } = useMpvSettings();
|
||||
const isVisualizerSurfaceVisible = useIsLocalVisualizerSurfaceVisible();
|
||||
const [promptState, setPromptState] = useState<PromptState>('loading');
|
||||
const [sessionAllowCapture, setSessionAllowCapture] = useState(false);
|
||||
const wasBlockedByExclusiveModeRef = useRef(false);
|
||||
const [isPromptOpen, { close: closePrompt, open: openPrompt, toggle: togglePrompt }] =
|
||||
useDisclosure(false);
|
||||
|
||||
const isExclusiveModeEnabled = audioExclusiveMode === 'yes';
|
||||
const isVisualizerBlockedByExclusiveMode =
|
||||
isElectron() &&
|
||||
playbackType === PlayerType.LOCAL &&
|
||||
isVisualizerSurfaceVisible &&
|
||||
isExclusiveModeEnabled;
|
||||
|
||||
const persistConsent = useCallback((granted: boolean) => {
|
||||
if (!isElectron() || !window.api.localSettings) {
|
||||
return;
|
||||
@@ -67,6 +77,7 @@ function VisualizerSystemAudioBridge() {
|
||||
const eligibleForPrompt =
|
||||
isElectron() &&
|
||||
playbackType === PlayerType.LOCAL &&
|
||||
!isExclusiveModeEnabled &&
|
||||
isVisualizerSurfaceVisible &&
|
||||
promptState !== 'loading' &&
|
||||
!promptState.consent &&
|
||||
@@ -80,9 +91,25 @@ function VisualizerSystemAudioBridge() {
|
||||
}
|
||||
}, [eligibleForPrompt, closePrompt, openPrompt]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isVisualizerBlockedByExclusiveMode && !wasBlockedByExclusiveModeRef.current) {
|
||||
toast.error({
|
||||
message: t('visualizer.systemAudioExclusiveModeNotSupported', {
|
||||
postProcess: 'sentenceCase',
|
||||
}),
|
||||
});
|
||||
setSessionAllowCapture(false);
|
||||
closePrompt();
|
||||
closeLocalVisualizerSurfaces();
|
||||
}
|
||||
|
||||
wasBlockedByExclusiveModeRef.current = isVisualizerBlockedByExclusiveMode;
|
||||
}, [closePrompt, isVisualizerBlockedByExclusiveMode, t]);
|
||||
|
||||
const shouldAttemptConnection =
|
||||
isElectron() &&
|
||||
playbackType === PlayerType.LOCAL &&
|
||||
!isExclusiveModeEnabled &&
|
||||
isVisualizerSurfaceVisible &&
|
||||
promptState !== 'loading' &&
|
||||
(promptState.consent || sessionAllowCapture);
|
||||
|
||||
@@ -3,5 +3,6 @@ export * from './use-check-for-updates';
|
||||
export * from './use-container-query';
|
||||
export * from './use-fast-average-color';
|
||||
export * from './use-hide-scrollbar';
|
||||
export * from './use-hotkeys';
|
||||
export * from './use-is-mounted';
|
||||
export * from './use-should-pad-titlebar';
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import {
|
||||
type HotkeyItem as MantineHotkeyItem,
|
||||
useHotkeys as useMantineHotkeys,
|
||||
} from '@mantine/hooks';
|
||||
|
||||
import { useAppStore } from '/@/renderer/store';
|
||||
|
||||
const EMPTY_HOTKEYS: MantineHotkeyItem[] = [];
|
||||
|
||||
export const useHotkeys = (
|
||||
hotkeys: MantineHotkeyItem[],
|
||||
tagsToIgnore?: string[],
|
||||
triggerOnContentEditable?: boolean,
|
||||
) => {
|
||||
const commandPaletteOpened = useAppStore((state) => state.commandPalette.opened);
|
||||
useMantineHotkeys(
|
||||
commandPaletteOpened ? EMPTY_HOTKEYS : hotkeys,
|
||||
tagsToIgnore,
|
||||
triggerOnContentEditable,
|
||||
);
|
||||
};
|
||||
|
||||
export type HotkeyItem = MantineHotkeyItem;
|
||||
@@ -1,6 +1,6 @@
|
||||
import clsx from 'clsx';
|
||||
import { AnimatePresence } from 'motion/react';
|
||||
import { lazy, Suspense } from 'react';
|
||||
import { Suspense } from 'react';
|
||||
import { Outlet } from 'react-router';
|
||||
|
||||
import styles from './mobile-layout.module.css';
|
||||
@@ -10,6 +10,7 @@ import { FullScreenVisualizer } from '/@/renderer/features/player/components/ful
|
||||
import { MobileFullscreenPlayer } from '/@/renderer/features/player/components/mobile-fullscreen-player';
|
||||
import { MobileSidebar } from '/@/renderer/features/sidebar/components/mobile-sidebar';
|
||||
import { PlayerBar } from '/@/renderer/layouts/default-layout/player-bar';
|
||||
import { WindowBar } from '/@/renderer/layouts/window-bar';
|
||||
import { useFullScreenPlayerOverlayState, useWindowBarStyle } from '/@/renderer/store';
|
||||
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
||||
import { Drawer } from '/@/shared/components/drawer/drawer';
|
||||
@@ -17,12 +18,6 @@ import { Spinner } from '/@/shared/components/spinner/spinner';
|
||||
import { useDisclosure } from '/@/shared/hooks/use-disclosure';
|
||||
import { Platform } from '/@/shared/types/types';
|
||||
|
||||
const WindowBar = lazy(() =>
|
||||
import('/@/renderer/layouts/window-bar').then((module) => ({
|
||||
default: module.WindowBar,
|
||||
})),
|
||||
);
|
||||
|
||||
interface MobileLayoutProps {
|
||||
shell?: boolean;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useNavigate } from 'react-router';
|
||||
import { useAppTracker } from '/@/renderer/features/analytics/hooks/use-app-tracker';
|
||||
import { CommandPalette } from '/@/renderer/features/search/components/command-palette';
|
||||
import { useGarbageCollection } from '/@/renderer/hooks/use-garbage-collection';
|
||||
import { HotkeyItem, useHotkeys } from '/@/renderer/hooks/use-hotkeys';
|
||||
import { useIsMobile } from '/@/renderer/hooks/use-is-mobile';
|
||||
import { DefaultLayout } from '/@/renderer/layouts/default-layout';
|
||||
import { MobileLayout } from '/@/renderer/layouts/mobile-layout/mobile-layout';
|
||||
@@ -15,7 +16,6 @@ import {
|
||||
useSettingsStoreActions,
|
||||
useZoomFactor,
|
||||
} from '/@/renderer/store';
|
||||
import { HotkeyItem, useHotkeys } from '/@/shared/hooks/use-hotkeys';
|
||||
|
||||
interface ResponsiveLayoutProps {
|
||||
shell?: boolean;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { lazy, Suspense } from 'react';
|
||||
import { HashRouter, Route, Routes } from 'react-router';
|
||||
|
||||
import { ShuffleAllContextModal } from '/@/renderer/features/player/components/shuffle-all-modal';
|
||||
import { RouterErrorBoundary } from '/@/renderer/features/shared/components/router-error-boundary';
|
||||
import { AuthenticationOutlet } from '/@/renderer/layouts/authentication-outlet';
|
||||
import { ResponsiveLayout } from '/@/renderer/layouts/responsive-layout';
|
||||
@@ -96,18 +97,6 @@ const LyricsSettingsContextModal = (props: any) => (
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
const LazyShuffleAllContextModal = lazy(() =>
|
||||
import('/@/renderer/features/player/components/shuffle-all-modal').then((module) => ({
|
||||
default: module.ShuffleAllContextModal,
|
||||
})),
|
||||
);
|
||||
|
||||
const ShuffleAllContextModal = (props: any) => (
|
||||
<Suspense fallback={<Spinner container />}>
|
||||
<LazyShuffleAllContextModal {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
const LazyAddToPlaylistContextModal = lazy(() =>
|
||||
import('/@/renderer/features/playlists/components/add-to-playlist-context-modal').then(
|
||||
(module) => ({
|
||||
@@ -200,7 +189,7 @@ const appRouterModals = {
|
||||
|
||||
export const AppRouter = () => {
|
||||
const router = (
|
||||
<HashRouter unstable_useTransitions>
|
||||
<HashRouter unstable_useTransitions={false}>
|
||||
<ModalsProvider modals={appRouterModals}>
|
||||
<RouterErrorBoundary>
|
||||
<Routes>
|
||||
|
||||
@@ -10,6 +10,8 @@ const APP_THEMES = new Set([
|
||||
'defaultDark',
|
||||
'defaultLight',
|
||||
'dracula',
|
||||
'everforestDark',
|
||||
'everforestLight',
|
||||
'githubDark',
|
||||
'githubLight',
|
||||
'glassyDark',
|
||||
|
||||
@@ -776,7 +776,7 @@ export const usePlayerStoreBase = createWithEqualityFn<PlayerState>()(
|
||||
|
||||
// For nextSong calculation, we need to consider the shuffled order
|
||||
let nextSong: QueueSong | undefined;
|
||||
if (isShuffleEnabled(state)) {
|
||||
if (isShuffleEnabled(state) && repeat !== PlayerRepeat.ONE) {
|
||||
// Calculate next in shuffled order
|
||||
const nextShuffledIndex = index + 1;
|
||||
if (nextShuffledIndex < state.queue.shuffled.length) {
|
||||
@@ -925,7 +925,7 @@ export const usePlayerStoreBase = createWithEqualityFn<PlayerState>()(
|
||||
const currentSong = queue.items[currentQueueIndex];
|
||||
|
||||
let nextSong: QueueSong | undefined;
|
||||
if (isShuffle) {
|
||||
if (isShuffle && repeat !== PlayerRepeat.ONE) {
|
||||
const nextShuffledIndex = nextPlaybackIndex + 1;
|
||||
if (nextShuffledIndex < stateSnapshot.queue.shuffled.length) {
|
||||
const nextQueueIndex = stateSnapshot.queue.shuffled[nextShuffledIndex];
|
||||
@@ -933,8 +933,6 @@ export const usePlayerStoreBase = createWithEqualityFn<PlayerState>()(
|
||||
} else if (repeat === PlayerRepeat.ALL) {
|
||||
const firstQueueIndex = stateSnapshot.queue.shuffled[0];
|
||||
nextSong = queue.items[firstQueueIndex];
|
||||
} else if (repeat === PlayerRepeat.ONE) {
|
||||
nextSong = currentSong;
|
||||
}
|
||||
} else {
|
||||
nextSong = calculateNextSong(currentQueueIndex, queue.items, repeat);
|
||||
@@ -1733,7 +1731,7 @@ export const subscribeNextSongInsertion = (onChange: (song: QueueSong | undefine
|
||||
|
||||
// Calculate next song based on shuffle and repeat settings
|
||||
let nextSong: QueueSong | undefined;
|
||||
if (isShuffleEnabled(state)) {
|
||||
if (isShuffleEnabled(state) && repeat !== PlayerRepeat.ONE) {
|
||||
// Calculate next in shuffled order
|
||||
const nextShuffledIndex = state.player.index + 1;
|
||||
if (nextShuffledIndex < state.queue.shuffled.length) {
|
||||
@@ -1940,7 +1938,7 @@ export const usePlayerData = (): PlayerData => {
|
||||
|
||||
// For nextSong calculation, we need to consider the shuffled order
|
||||
let nextSong: QueueSong | undefined;
|
||||
if (isShuffleEnabled(state)) {
|
||||
if (isShuffleEnabled(state) && repeat !== PlayerRepeat.ONE) {
|
||||
// Calculate next in shuffled order
|
||||
const nextShuffledIndex = index + 1;
|
||||
if (nextShuffledIndex < state.queue.shuffled.length) {
|
||||
|
||||
@@ -306,6 +306,7 @@ const PlayerbarSliderSchema = z.object({
|
||||
barRadius: z.number(),
|
||||
barWidth: z.number(),
|
||||
loadingDelay: z.number(),
|
||||
stretched: z.boolean(),
|
||||
type: PlayerbarSliderTypeSchema,
|
||||
});
|
||||
|
||||
@@ -536,6 +537,8 @@ const LyricsDisplaySettingsSchema = z.object({
|
||||
fontSizeUnsync: z.number(),
|
||||
gap: z.number(),
|
||||
gapUnsync: z.number(),
|
||||
opacityNonActive: z.number(),
|
||||
scaleNonActive: z.number(),
|
||||
});
|
||||
|
||||
const LyricsSettingsSchema = z.object({
|
||||
@@ -635,6 +638,7 @@ const WindowSettingsSchema = z.object({
|
||||
exitToTray: z.boolean(),
|
||||
minimizeToTray: z.boolean(),
|
||||
preventSleepOnPlayback: z.boolean(),
|
||||
preventSuspendOnPlayback: z.boolean(),
|
||||
releaseChannel: z.enum(['alpha', 'beta', 'latest']),
|
||||
startMinimized: z.boolean(),
|
||||
tray: z.boolean(),
|
||||
@@ -1150,6 +1154,7 @@ const initialState: SettingsState = {
|
||||
barRadius: 4,
|
||||
barWidth: 2,
|
||||
loadingDelay: 2,
|
||||
stretched: false,
|
||||
type: PlayerbarSliderType.SLIDER,
|
||||
},
|
||||
playerItems,
|
||||
@@ -1794,6 +1799,8 @@ const initialState: SettingsState = {
|
||||
fontSizeUnsync: 24,
|
||||
gap: 24,
|
||||
gapUnsync: 24,
|
||||
opacityNonActive: 0.2,
|
||||
scaleNonActive: 0.95,
|
||||
},
|
||||
},
|
||||
playback: {
|
||||
@@ -1908,6 +1915,7 @@ const initialState: SettingsState = {
|
||||
exitToTray: false,
|
||||
minimizeToTray: false,
|
||||
preventSleepOnPlayback: false,
|
||||
preventSuspendOnPlayback: false,
|
||||
releaseChannel: 'latest',
|
||||
startMinimized: false,
|
||||
tray: true,
|
||||
@@ -2211,7 +2219,10 @@ export const useSettingsStore = createWithEqualityFn<SettingsSlice>()(
|
||||
|
||||
state.lyrics = mainSettings;
|
||||
state.lyricsDisplay = {
|
||||
default: displaySettings,
|
||||
default: {
|
||||
...state.lyricsDisplay.default,
|
||||
...displaySettings,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ export const THEME_DATA = [
|
||||
{ label: 'One Dark', type: 'dark', value: AppTheme.ONE_DARK },
|
||||
{ label: 'Solarized Dark', type: 'dark', value: AppTheme.SOLARIZED_DARK },
|
||||
{ label: 'Solarized Light', type: 'light', value: AppTheme.SOLARIZED_LIGHT },
|
||||
{ label: 'Everforest Dark', type: 'dark', value: AppTheme.EVERFOREST_DARK },
|
||||
{ label: 'Everforest Light', type: 'light', value: AppTheme.EVERFOREST_LIGHT },
|
||||
{ label: 'GitHub Dark', type: 'dark', value: AppTheme.GITHUB_DARK },
|
||||
{ label: 'GitHub Light', type: 'light', value: AppTheme.GITHUB_LIGHT },
|
||||
{ label: 'Glassy Dark', type: 'dark', value: AppTheme.GLASSY_DARK },
|
||||
|
||||
@@ -278,6 +278,8 @@ const normalizeSong = (
|
||||
const normalizeAlbum = (
|
||||
item: z.infer<typeof jfType._response.album>,
|
||||
server: null | ServerListItem,
|
||||
pathReplace?: string,
|
||||
pathReplaceWith?: string,
|
||||
): Album => {
|
||||
const { originalYear, releaseDate, releaseYear } = jellyfinPremiereFields(item);
|
||||
|
||||
@@ -340,7 +342,7 @@ const normalizeAlbum = (
|
||||
releaseYear,
|
||||
size: null,
|
||||
songCount: item?.ChildCount || null,
|
||||
songs: item.Songs?.map((song) => normalizeSong(song, server)),
|
||||
songs: item.Songs?.map((song) => normalizeSong(song, server, pathReplace, pathReplaceWith)),
|
||||
sortName: item.SortName || item.Name,
|
||||
tags: getTags(item),
|
||||
updatedAt: item?.DateLastMediaAdded || item.DateCreated,
|
||||
|
||||
@@ -28,4 +28,4 @@ const BaseBadge = ({ children, classNames, variant = 'default', ...props }: Badg
|
||||
);
|
||||
};
|
||||
|
||||
export const Badge = createPolymorphicComponent<'button', BadgeProps>(BaseBadge);
|
||||
export const Badge = createPolymorphicComponent<'div', BadgeProps>(BaseBadge);
|
||||
|
||||
@@ -10,6 +10,8 @@ export enum AppTheme {
|
||||
DEFAULT_DARK = 'defaultDark',
|
||||
DEFAULT_LIGHT = 'defaultLight',
|
||||
DRACULA = 'dracula',
|
||||
EVERFOREST_DARK = 'everforestDark',
|
||||
EVERFOREST_LIGHT = 'everforestLight',
|
||||
GITHUB_DARK = 'githubDark',
|
||||
GITHUB_LIGHT = 'githubLight',
|
||||
GLASSY_DARK = 'glassyDark',
|
||||
|
||||
@@ -11,6 +11,8 @@ import { defaultTheme } from '/@/shared/themes/default';
|
||||
import { defaultDark } from '/@/shared/themes/default-dark/default-dark';
|
||||
import { defaultLight } from '/@/shared/themes/default-light/default-light';
|
||||
import { dracula } from '/@/shared/themes/dracula/dracula';
|
||||
import { everforestDark } from '/@/shared/themes/everforest-dark/everforest-dark';
|
||||
import { everforestLight } from '/@/shared/themes/everforest-light/everforest-light';
|
||||
import { githubDark } from '/@/shared/themes/github-dark/github-dark';
|
||||
import { githubLight } from '/@/shared/themes/github-light/github-light';
|
||||
import { glassyDark } from '/@/shared/themes/glassy-dark/glassy-dark';
|
||||
@@ -42,6 +44,8 @@ export const appTheme: Record<AppTheme, AppThemeConfiguration> = {
|
||||
[AppTheme.DEFAULT_DARK]: defaultDark,
|
||||
[AppTheme.DEFAULT_LIGHT]: defaultLight,
|
||||
[AppTheme.DRACULA]: dracula,
|
||||
[AppTheme.EVERFOREST_DARK]: everforestDark,
|
||||
[AppTheme.EVERFOREST_LIGHT]: everforestLight,
|
||||
[AppTheme.GITHUB_DARK]: githubDark,
|
||||
[AppTheme.GITHUB_LIGHT]: githubLight,
|
||||
[AppTheme.GLASSY_DARK]: glassyDark,
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { AppThemeConfiguration } from '/@/shared/themes/app-theme-types';
|
||||
|
||||
export const everforestDark: AppThemeConfiguration = {
|
||||
app: {
|
||||
'overlay-header':
|
||||
'linear-gradient(transparent 0%, rgb(39 46 41 / 85%) 100%), var(--theme-background-noise)',
|
||||
'overlay-subheader':
|
||||
'linear-gradient(180deg, rgb(39 46 41 / 5%) 0%, var(--theme-colors-background) 100%), var(--theme-background-noise)',
|
||||
'scrollbar-handle-background': 'rgba(160, 160, 160, 20%)',
|
||||
'scrollbar-handle-hover-background': 'rgba(160, 160, 160, 40%)',
|
||||
},
|
||||
colors: {
|
||||
background: 'rgb(35, 42, 46)',
|
||||
'background-alternate': 'rgb(35, 42, 46)',
|
||||
black: 'rgb(0, 0, 0)',
|
||||
foreground: 'rgb(211, 198, 170)',
|
||||
'foreground-muted': 'rgb(211, 198, 170)',
|
||||
primary: 'rgb(167, 192, 128)',
|
||||
'state-error': 'rgb(230, 126, 128)',
|
||||
'state-info': 'rgb(127, 187, 179)',
|
||||
'state-success': 'rgb(167, 192, 128)',
|
||||
'state-warning': 'rgb(219, 188, 127)',
|
||||
surface: 'rgb(52, 63, 56)',
|
||||
'surface-foreground': 'rgb(211, 198, 170)',
|
||||
white: 'rgb(255, 255, 255)',
|
||||
},
|
||||
mode: 'dark',
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
import { AppThemeConfiguration } from '/@/shared/themes/app-theme-types';
|
||||
|
||||
export const everforestLight: AppThemeConfiguration = {
|
||||
app: {
|
||||
'overlay-header':
|
||||
'linear-gradient(transparent 0%, rgb(253 246 227 / 85%) 100%), var(--theme-background-noise)',
|
||||
'overlay-subheader':
|
||||
'linear-gradient(180deg, rgb(253 246 227 / 5%) 0%, var(--theme-colors-background) 100%), var(--theme-background-noise)',
|
||||
'scrollbar-handle-background': 'rgba(90, 107, 78, 20%)',
|
||||
'scrollbar-handle-hover-background': 'rgba(90, 107, 78, 40%)',
|
||||
},
|
||||
colors: {
|
||||
background: 'rgb(253, 246, 227)',
|
||||
'background-alternate': 'rgb(237, 230, 211)',
|
||||
black: 'rgb(0, 0, 0)',
|
||||
foreground: 'rgb(92, 103, 76)',
|
||||
'foreground-muted': 'rgb(131, 145, 112)',
|
||||
primary: 'rgb(141, 165, 96)',
|
||||
'state-error': 'rgb(241, 103, 98)',
|
||||
'state-info': 'rgb(59, 145, 152)',
|
||||
'state-success': 'rgb(141, 165, 96)',
|
||||
'state-warning': 'rgb(223, 163, 59)',
|
||||
surface: 'rgb(237, 230, 211)',
|
||||
'surface-foreground': 'rgb(92, 103, 76)',
|
||||
white: 'rgb(255, 255, 255)',
|
||||
},
|
||||
mode: 'light',
|
||||
};
|
||||
Reference in New Issue
Block a user