Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot] c04ae9244b Bump vite in the npm_and_yarn group across 1 directory
Bumps the npm_and_yarn group with 1 update in the / directory: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).


Updates `vite` from 7.3.1 to 7.3.2
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v7.3.2/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.3.2/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.3.2
  dependency-type: direct:development
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-06 21:45:52 +00:00
23 changed files with 414 additions and 501 deletions
-1
View File
@@ -24,7 +24,6 @@ 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:
+3 -4
View File
@@ -43,11 +43,10 @@ mac:
icon: assets/icons/icon.icns
type: distribution
hardenedRuntime: false
identity: '-'
identity: "-"
gatekeeperAssess: false
notarize: false
extendInfo:
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 }]
@@ -62,7 +61,7 @@ linux:
artifactName: ${productName}-${os}-${arch}.${ext}
toolsets:
appimage: '1.0.2'
appimage: "1.0.2"
npmRebuild: false
+2 -4
View File
@@ -43,11 +43,9 @@ mac:
icon: assets/icons/icon.icns
type: distribution
hardenedRuntime: false
identity: '-'
identity: "-"
gatekeeperAssess: false
notarize: false
extendInfo:
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 }]
@@ -62,7 +60,7 @@ linux:
artifactName: ${productName}-${os}-${arch}.${ext}
toolsets:
appimage: '1.0.2'
appimage: "1.0.2"
npmRebuild: false
publish:
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "feishin",
"version": "1.11.0",
"version": "1.10.0",
"description": "A modern self-hosted music player.",
"keywords": [
"subsonic",
@@ -180,7 +180,7 @@
"stylelint-config-recess-order": "^7.7.0",
"stylelint-config-standard": "^39.0.1",
"typescript": "^5.9.3",
"vite": "^7.3.1",
"vite": "^7.3.2",
"vite-plugin-conditional-import": "^0.1.7",
"vite-plugin-dynamic-import": "^1.6.0",
"vite-plugin-ejs": "^1.7.0",
+21 -21
View File
@@ -258,7 +258,7 @@ importers:
version: 8.18.1
'@vitejs/plugin-react':
specifier: ^5.2.0
version: 5.2.0(vite@7.3.1(@types/node@24.12.2)(jiti@2.6.1)(sass-embedded@1.89.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(yaml@2.8.1))
version: 5.2.0(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(sass-embedded@1.89.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(yaml@2.8.1))
babel-plugin-react-compiler:
specifier: ^1.0.0
version: 1.0.0
@@ -279,7 +279,7 @@ importers:
version: 4.0.0
electron-vite:
specifier: ^4.0.1
version: 4.0.1(vite@7.3.1(@types/node@24.12.2)(jiti@2.6.1)(sass-embedded@1.89.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(yaml@2.8.1))
version: 4.0.1(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(sass-embedded@1.89.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(yaml@2.8.1))
eslint:
specifier: ^9.39.4
version: 9.39.4(jiti@2.6.1)
@@ -329,8 +329,8 @@ importers:
specifier: ^5.9.3
version: 5.9.3
vite:
specifier: ^7.3.1
version: 7.3.1(@types/node@24.12.2)(jiti@2.6.1)(sass-embedded@1.89.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(yaml@2.8.1)
specifier: ^7.3.2
version: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(sass-embedded@1.89.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(yaml@2.8.1)
vite-plugin-conditional-import:
specifier: ^0.1.7
version: 0.1.7
@@ -339,10 +339,10 @@ importers:
version: 1.6.0
vite-plugin-ejs:
specifier: ^1.7.0
version: 1.7.0(vite@7.3.1(@types/node@24.12.2)(jiti@2.6.1)(sass-embedded@1.89.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(yaml@2.8.1))
version: 1.7.0(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(sass-embedded@1.89.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(yaml@2.8.1))
vite-plugin-pwa:
specifier: ^1.2.0
version: 1.2.0(vite@7.3.1(@types/node@24.12.2)(jiti@2.6.1)(sass-embedded@1.89.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(yaml@2.8.1))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0)
version: 1.2.0(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(sass-embedded@1.89.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(yaml@2.8.1))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0)
packages:
@@ -4778,8 +4778,8 @@ packages:
regjsgen@0.8.0:
resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==}
regjsparser@0.13.0:
resolution: {integrity: sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==}
regjsparser@0.13.1:
resolution: {integrity: sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==}
hasBin: true
remove-trailing-separator@1.1.0:
@@ -5667,8 +5667,8 @@ packages:
'@vite-pwa/assets-generator':
optional: true
vite@7.3.1:
resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==}
vite@7.3.2:
resolution: {integrity: sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
peerDependencies:
@@ -7857,7 +7857,7 @@ snapshots:
'@typescript-eslint/types': 8.58.0
eslint-visitor-keys: 5.0.1
'@vitejs/plugin-react@5.2.0(vite@7.3.1(@types/node@24.12.2)(jiti@2.6.1)(sass-embedded@1.89.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(yaml@2.8.1))':
'@vitejs/plugin-react@5.2.0(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(sass-embedded@1.89.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(yaml@2.8.1))':
dependencies:
'@babel/core': 7.29.0
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0)
@@ -7865,7 +7865,7 @@ snapshots:
'@rolldown/pluginutils': 1.0.0-rc.3
'@types/babel__core': 7.20.5
react-refresh: 0.18.0
vite: 7.3.1(@types/node@24.12.2)(jiti@2.6.1)(sass-embedded@1.89.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(yaml@2.8.1)
vite: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(sass-embedded@1.89.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(yaml@2.8.1)
transitivePeerDependencies:
- supports-color
@@ -8787,7 +8787,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
electron-vite@4.0.1(vite@7.3.1(@types/node@24.12.2)(jiti@2.6.1)(sass-embedded@1.89.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(yaml@2.8.1)):
electron-vite@4.0.1(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(sass-embedded@1.89.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(yaml@2.8.1)):
dependencies:
'@babel/core': 7.29.0
'@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.0)
@@ -8795,7 +8795,7 @@ snapshots:
esbuild: 0.25.12
magic-string: 0.30.21
picocolors: 1.1.1
vite: 7.3.1(@types/node@24.12.2)(jiti@2.6.1)(sass-embedded@1.89.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(yaml@2.8.1)
vite: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(sass-embedded@1.89.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(yaml@2.8.1)
transitivePeerDependencies:
- supports-color
@@ -10762,13 +10762,13 @@ snapshots:
regenerate: 1.4.2
regenerate-unicode-properties: 10.2.2
regjsgen: 0.8.0
regjsparser: 0.13.0
regjsparser: 0.13.1
unicode-match-property-ecmascript: 2.0.0
unicode-match-property-value-ecmascript: 2.2.1
regjsgen@0.8.0: {}
regjsparser@0.13.0:
regjsparser@0.13.1:
dependencies:
jsesc: 3.1.0
@@ -11773,23 +11773,23 @@ snapshots:
fast-glob: 3.3.3
magic-string: 0.30.21
vite-plugin-ejs@1.7.0(vite@7.3.1(@types/node@24.12.2)(jiti@2.6.1)(sass-embedded@1.89.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(yaml@2.8.1)):
vite-plugin-ejs@1.7.0(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(sass-embedded@1.89.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(yaml@2.8.1)):
dependencies:
ejs: 3.1.10
vite: 7.3.1(@types/node@24.12.2)(jiti@2.6.1)(sass-embedded@1.89.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(yaml@2.8.1)
vite: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(sass-embedded@1.89.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(yaml@2.8.1)
vite-plugin-pwa@1.2.0(vite@7.3.1(@types/node@24.12.2)(jiti@2.6.1)(sass-embedded@1.89.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(yaml@2.8.1))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0):
vite-plugin-pwa@1.2.0(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(sass-embedded@1.89.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(yaml@2.8.1))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0):
dependencies:
debug: 4.4.3
pretty-bytes: 6.1.1
tinyglobby: 0.2.15
vite: 7.3.1(@types/node@24.12.2)(jiti@2.6.1)(sass-embedded@1.89.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(yaml@2.8.1)
vite: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(sass-embedded@1.89.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(yaml@2.8.1)
workbox-build: 7.3.0(@types/babel__core@7.20.5)
workbox-window: 7.3.0
transitivePeerDependencies:
- supports-color
vite@7.3.1(@types/node@24.12.2)(jiti@2.6.1)(sass-embedded@1.89.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(yaml@2.8.1):
vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(sass-embedded@1.89.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(yaml@2.8.1):
dependencies:
esbuild: 0.27.7
fdir: 6.5.0(picomatch@4.0.4)
+1 -7
View File
@@ -1381,12 +1381,6 @@
}
},
"pasteGradient": "Vložit přechod",
"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"
"pasteGradientPlaceholder": "Sem vložte JSON přechodu…"
}
}
+27 -40
View File
@@ -242,7 +242,7 @@
"criticRating": "Kritikerbewertung",
"album": "$t(entity.album, {\"count\": 1})",
"trackNumber": "Track",
"channels": "$t(common.channel,{\"count\":2})",
"channels": "$t(common.channel_other)",
"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,9 +357,6 @@
"input_offset": "$t(setting.lyricOffset)",
"export": "Songtexte exportieren",
"input_synced": "Synchronisierte Songtexte exportieren"
},
"editRadioStation": {
"success": "Radiosender erfolgreich aktualisiert"
}
},
"entity": {
@@ -429,8 +426,8 @@
"pagination": "Seitenzahlen",
"pagination_itemsPerPage": "Elemente pro Seite",
"pagination_infinite": "unendlich",
"moveUp": "nach oben",
"moveDown": "nach unten",
"moveUp": "Nach oben bewegen",
"moveDown": "Nach unten bewegen",
"pinToLeft": "links anheften",
"pinToRight": "rechts anheften",
"itemGap": "Item Abstand (px)",
@@ -453,13 +450,13 @@
"albumArtist": "$t(entity.albumArtist, {\"count\": 1})",
"artist": "$t(entity.artist, {\"count\": 1})",
"favorite": "$t(common.favorite)",
"actions": "$t(common.action,{\"count\":2})",
"actions": "$t(common.action_other)",
"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,{\"count\":2})",
"channels": "$t(common.channel_other)",
"duration": "$t(common.duration)",
"note": "$t(common.note)",
"owner": "$t(common.owner)",
@@ -496,7 +493,7 @@
"rating": "Bewertung",
"albumCount": "$t(entity.album, {\"count\": 2})",
"artist": "$t(entity.artist, {\"count\": 1})",
"channels": "$t(common.channel,{\"count\":2})",
"channels": "$t(common.channel_other)",
"comment": "Kommentar",
"dateAdded": "hinzugefügt am",
"playCount": "Abgespielt",
@@ -719,8 +716,7 @@
},
"releasenotes": {
"commitsSinceStable": "Commits seit {{stable}}",
"noStableReleaseToCompare": "Kein stable Relase zum vergleichen verfügbar",
"noNewCommits": "keine neuen Beiträge in diesem Bereich"
"noStableReleaseToCompare": "Kein stable Relase zum vergleichen verfügbar"
}
},
"player": {
@@ -770,12 +766,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": "das für die Wiedergabe zu verwendende Audiogerät auswählen",
"audioDevice_description": "wählen Sie das Audiogerät aus, das für die Wiedergabe verwendet werden soll (nur Webplayer)",
"audioExclusiveMode": "Audio-Exklusivmodus",
"audioDevice": "Audiogerät",
"accentColor": "Akzentfarbe",
@@ -793,7 +789,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",
@@ -1013,7 +1009,7 @@
"transcodeFormat": "Format für Umwandlung",
"startMinimized_description": "Startet die Anwendung im Info-Bereich",
"startMinimized": "Im Info-Bereich starten",
"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_description": "Aktiviert die Windows Media Session-Integration, zeigt Mediensteuerelemente und Metadaten im Systemlautstärke-Overlay und auf dem Sperrbildschirm an (nur Windows)",
"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",
@@ -1021,11 +1017,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 (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",
"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",
"releaseChannel_optionBeta": "Beta",
"releaseChannel_optionLatest": "Stabil",
"releaseChannel": "Veröffentlichungskanal",
"releaseChannel_description": "zwischen stabilen, Beta- oder Alpha-Versionen (Nightly) für automatische Updates wählen",
"releaseChannel_description": "Zwischen stabilen und beta Veröffentlichungen für automatische Aktualisierungen wählen",
"discordDisplayType_artistname": "Künstlername(n)",
"discordDisplayType_description": "Ändert den aktuellen Titel im Zuhör-Status",
"discordDisplayType_songname": "Songtitel",
@@ -1125,13 +1121,7 @@
"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",
"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"
"artistReleaseTypeConfiguration": "Interpreten Release Typ Einstellung"
},
"dragDropZone": {
"error_oneFileOnly": "Bitte wähle nur 1 Datei",
@@ -1221,7 +1211,7 @@
"dualVertical": "Dual-Vertikal"
}
},
"minimumFrequency": "Minimale Frequenz",
"minimumFrequency": "Mindestfrequenz",
"minimumDecibels": "Minimale Dezibel",
"visualizerType": "Visualizer Art",
"cyclePresets": "Vorlagen durchrotieren",
@@ -1256,10 +1246,10 @@
"channelLayout": "Kanallayout",
"maxFPS": "Max FPS",
"opacity": "Deckkraft",
"customGradients": "Benutzerdefinierter Farbverlauf",
"customGradients": "Benutzerdefinierte Gradienten",
"addCustomGradient": "Benutzerdefinierten Gradienten hinzufügen",
"gradientName": "Name Farbverlauf",
"gradientNamePlaceholder": "Name Farbverlauf",
"gradientName": "Gradientenname",
"gradientNamePlaceholder": "Gradientenname",
"vertical": "Vertikal",
"horizontal": "Horizontal",
"addColor": "Farbe hinzufügen",
@@ -1272,9 +1262,9 @@
"builtIn": "Eingebaut",
"colors": "Farben",
"colorMode": "Farbmodus",
"gradient": "Farbverlauf",
"gradientLeft": "Farberverlauf links",
"gradientRight": "Farbverlauf rechts",
"gradient": "Gradienten",
"gradientLeft": "Gradienten links",
"gradientRight": "Gradienten rechts",
"fft": "FFT",
"fftSize": "FFT Größe",
"smoothing": "Glätten",
@@ -1283,20 +1273,17 @@
"sensitivity": "Empfindlichkeit",
"weightingFilter": "Gewichtungsfilter",
"maximumDecibels": "Maximale Dezibel",
"linearAmplitude": "Linearer Ausschlag",
"linearAmplitude": "Lineare Amplitude",
"linearBoost": "Linearer Boost",
"radialSpectrum": "Radiales Spektrum",
"radial": "Radial",
"radialInvert": "Radial invertiert",
"radius": "Radius",
"miscellaneousSettings": "Verschiedene Einstellungen",
"miscellaneousSettings": "Verschiedenes Einstellungen",
"ansiBands": "ANSI Bänder",
"lowResolution": "Niedrige Auflösung",
"showFPS": "FPS anzeigen",
"fadePeaks": "Spitzen abblenden",
"showPeaks": "Spitzen anzeigen",
"systemAudioConsentAllow": "Erlauben",
"systemAudioConsentDecline": "Ablehnen",
"frequencyScale": "Frequenzskala"
"showPeaks": "Spitzen anzeigen"
}
}
+1 -10
View File
@@ -1001,9 +1001,6 @@
"export": "Exportar letras",
"input_synced": "Exportar letras sincronizadas",
"input_offset": "$t(setting.lyricOffset)"
},
"editRadioStation": {
"success": "Estación de radio actualizada con éxito"
}
},
"table": {
@@ -1381,12 +1378,6 @@
"lowResolution": "Baja resolución",
"splitGradient": "Dividir degradado",
"noteLabels": "Etiquetas de notas",
"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."
"lumiBars": "Barras luminiscentes"
}
}
+1 -10
View File
@@ -1002,9 +1002,6 @@
"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": {
@@ -1382,12 +1379,6 @@
},
"lumiBars": "Lumi Bars",
"outlineBars": "Outline Bars",
"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}}"
"splitGradient": "Split Gradient"
}
}
+2 -11
View File
@@ -1037,7 +1037,7 @@
},
"updateServer": {
"title": "サーバーをアップデート",
"success": "サーバーの更新に成功しました"
"success": "サーバーがアップデートされました"
},
"queryEditor": {
"input_optionMatchAll": "すべて一致",
@@ -1102,9 +1102,6 @@
},
"saveQueue": {
"success": "プレイキューをサーバーに保存しました"
},
"editRadioStation": {
"success": "ラジオ局の更新に成功しました"
}
},
"entity": {
@@ -1335,12 +1332,6 @@
"d": "D",
"z": "Z"
}
},
"systemAudioConsentAllow": "許可",
"systemAudioConsentBody": "ビジュアライザーを機能させるためには、システムオーディオへのアクセスが必要です",
"systemAudioConsentDecline": "拒否",
"systemAudioConsentTitle": "システムオーディオへのアクセスを許可しますか?",
"systemAudioCaptureFailed": "キャプチャを開始できませんでした: {{message}}",
"systemAudioNoAudioTrack": "音声トラックが返されませんでした。プロンプトが表示されたら、音声キャプチャが有効になっていることを確認してください。"
}
}
}
+1 -7
View File
@@ -1381,12 +1381,6 @@
},
"pasteGradient": "Wklej Gradient",
"pasteGradientPlaceholder": "Wklej tutaj JSON gradientu...",
"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."
"ansiBands": "Paski ANSI"
}
}
+1 -5
View File
@@ -1337,10 +1337,6 @@
}
},
"systemAudioCaptureFailed": "無法開始擷取:{{message}}",
"systemAudioNoAudioTrack": "沒有回傳任何音軌。確保在提示時啟用音訊擷取。",
"systemAudioConsentAllow": "允許",
"systemAudioConsentBody": "此視覺化器需要存取系統音訊才能運作",
"systemAudioConsentDecline": "拒絕",
"systemAudioConsentTitle": "允許存取系統音訊?"
"systemAudioNoAudioTrack": "沒有回傳任何音軌。確保在提示時啟用音訊擷取。"
}
}
+1 -8
View File
@@ -1,9 +1,2 @@
import './core';
if (process.platform === 'linux') {
import('./linux');
} else if (process.platform === 'darwin') {
import('./darwin');
} else if (process.platform === 'win32') {
import('./win32');
}
import(`./${process.platform}`);
-1
View File
@@ -1 +0,0 @@
export {};
+14 -1
View File
@@ -5,6 +5,7 @@ import {
app,
BrowserWindow,
BrowserWindowConstructorOptions,
desktopCapturer,
globalShortcut,
ipcMain,
Menu,
@@ -733,7 +734,19 @@ async function createWindow(first = true): Promise<void> {
});
mainWindow.webContents.session.setDisplayMediaRequestHandler((_request, callback) => {
callback({ audio: 'loopback' });
desktopCapturer
.getSources({ types: ['screen'] })
.then((sources) => {
if (sources.length > 0) {
callback({ audio: 'loopback', video: sources[0] });
} else {
callback({});
}
})
.catch((err) => {
log.warn('desktopCapturer.getSources failed', err);
callback({});
});
});
if (!disableAutoUpdates() && store.get('disable_auto_updates') !== true) {
+1 -2
View File
@@ -1,5 +1,4 @@
import type { SetActivity } from '@xhayper/discord-rpc';
import { SetActivity } from '@xhayper/discord-rpc';
import { ipcRenderer } from 'electron';
const initialize = (clientId: string) => {
@@ -2015,12 +2015,8 @@ 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) {
logFn.error(
`Failed to get transcode decision for song ${id}, falling back to direct stream`,
);
return streamUrl;
throw new Error('Failed to get transcode decision');
}
const td = transcodeDecision.body.transcodeDecision;
+7 -2
View File
@@ -22,7 +22,12 @@ 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';
import { ReleaseNotesModal } from '/@/renderer/release-notes-modal';
const ReleaseNotesModal = lazy(() =>
import('./release-notes-modal').then((module) => ({
default: module.ReleaseNotesModal,
})),
);
const UpdateAvailableDialog = lazy(() =>
import('./update-available-dialog').then((module) => ({
@@ -77,8 +82,8 @@ const AppShell = memo(function AppShell() {
<AppRouter />
</PlayerProvider>
</WebAudioContext.Provider>
<ReleaseNotesModal />
<Suspense fallback={null}>
<ReleaseNotesModal />
<UpdateAvailableDialog />
</Suspense>
</>
+302 -340
View File
@@ -169,292 +169,6 @@ 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,
@@ -471,6 +185,7 @@ const CompactItemCard = ({
showRating,
withControls,
}: ItemCardDerivativeProps) => {
const [showControls, setShowControls] = useState(false);
const itemRowId =
data && internalState && typeof data === 'object' && 'id' in data
? internalState.extractRowId(data)
@@ -582,6 +297,18 @@ 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;
@@ -611,6 +338,81 @@ 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, {
@@ -619,24 +421,31 @@ const CompactItemCard = ({
})}
ref={ref}
>
<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}
/>
{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>
)}
</div>
);
}
@@ -682,6 +491,7 @@ const DefaultItemCard = ({
showRating,
withControls,
}: ItemCardDerivativeProps) => {
const [showControls, setShowControls] = useState(false);
const itemRowId =
data && internalState && typeof data === 'object' && 'id' in data
? internalState.extractRowId(data)
@@ -728,6 +538,18 @@ 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;
@@ -757,30 +579,93 @@ 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,
})}
>
<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}
/>
{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>
)}
<div className={styles.detailContainer}>
{rows
.filter(
@@ -843,6 +728,7 @@ const PosterItemCard = ({
showRating,
withControls,
}: ItemCardDerivativeProps) => {
const [showControls, setShowControls] = useState(false);
const itemRowId =
data && internalState && typeof data === 'object' && 'id' in data
? internalState.extractRowId(data)
@@ -954,6 +840,18 @@ 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;
@@ -983,6 +881,63 @@ 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, {
@@ -991,24 +946,31 @@ const PosterItemCard = ({
})}
ref={ref}
>
<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}
/>
{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>
)}
{data && (
<div className={styles.detailContainer}>
{rows
@@ -1,5 +1,4 @@
import type { SetActivity } from '@xhayper/discord-rpc';
import { SetActivity, StatusDisplayType } from '@xhayper/discord-rpc';
import isElectron from 'is-electron';
import React, { useCallback, useEffect, useRef, useState } from 'react';
@@ -28,13 +27,6 @@ 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;
@@ -130,7 +122,7 @@ export const useDiscordRpc = () => {
: undefined
: sentenceCase(current[2]),
state: truncate(artist),
statusDisplayType: DiscordStatusDisplayType.STATE,
statusDisplayType: StatusDisplayType.STATE,
type: discordSettings.showAsListening ? 2 : 0,
};
@@ -204,9 +196,9 @@ export const useDiscordRpc = () => {
const artists = song?.artists.map((artist) => artist.name).join(', ');
const statusDisplayMap = {
[DiscordDisplayType.ARTIST_NAME]: DiscordStatusDisplayType.STATE,
[DiscordDisplayType.FEISHIN]: DiscordStatusDisplayType.NAME,
[DiscordDisplayType.SONG_NAME]: DiscordStatusDisplayType.DETAILS,
[DiscordDisplayType.ARTIST_NAME]: StatusDisplayType.STATE,
[DiscordDisplayType.FEISHIN]: StatusDisplayType.NAME,
[DiscordDisplayType.SONG_NAME]: StatusDisplayType.DETAILS,
};
const activity: SetActivity = {
@@ -131,9 +131,7 @@ export const LyricsActions = ({
uppercase
variant="subtle"
>
{hasLyrics
? t('common.clear', { postProcess: 'sentenceCase' })
: t('common.refresh', { postProcess: 'sentenceCase' })}
{t('common.clear', { postProcess: 'sentenceCase' })}
</Button>
) : null}
</Group>
@@ -1,6 +1,6 @@
import clsx from 'clsx';
import { AnimatePresence } from 'motion/react';
import { Suspense } from 'react';
import { lazy, Suspense } from 'react';
import { Outlet } from 'react-router';
import styles from './mobile-layout.module.css';
@@ -10,7 +10,6 @@ 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';
@@ -18,6 +17,12 @@ 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;
}
+13 -2
View File
@@ -1,7 +1,6 @@
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';
@@ -97,6 +96,18 @@ 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) => ({
@@ -189,7 +200,7 @@ const appRouterModals = {
export const AppRouter = () => {
const router = (
<HashRouter unstable_useTransitions={false}>
<HashRouter unstable_useTransitions>
<ModalsProvider modals={appRouterModals}>
<RouterErrorBoundary>
<Routes>