mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 04:20:12 +02:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2329bc77d8 | |||
| 8869278898 | |||
| 16c9e6cc1b | |||
| 2a6e9b6ad3 | |||
| 167b42df2b | |||
| e6a2bc3acf | |||
| ca3c7015c6 | |||
| c7c15d917a | |||
| 6adb29bc38 | |||
| 2c3cd7af24 |
@@ -24,6 +24,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and Publish releases
|
- name: Build and Publish releases
|
||||||
env:
|
env:
|
||||||
|
NODE_OPTIONS: --max-old-space-size=4096
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
uses: nick-invision/retry@v3.0.2
|
uses: nick-invision/retry@v3.0.2
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -43,10 +43,11 @@ mac:
|
|||||||
icon: assets/icons/icon.icns
|
icon: assets/icons/icon.icns
|
||||||
type: distribution
|
type: distribution
|
||||||
hardenedRuntime: false
|
hardenedRuntime: false
|
||||||
identity: "-"
|
identity: '-'
|
||||||
gatekeeperAssess: false
|
gatekeeperAssess: false
|
||||||
notarize: false
|
notarize: false
|
||||||
|
extendInfo:
|
||||||
|
NSLocalNetworkUsageDescription: 'Local network is necessary for accessing servers hosted on the same system as Feishin'
|
||||||
|
|
||||||
dmg:
|
dmg:
|
||||||
contents: [{ x: 130, y: 220 }, { x: 410, y: 220, type: link, path: /Applications }]
|
contents: [{ x: 130, y: 220 }, { x: 410, y: 220, type: link, path: /Applications }]
|
||||||
@@ -61,7 +62,7 @@ linux:
|
|||||||
artifactName: ${productName}-${os}-${arch}.${ext}
|
artifactName: ${productName}-${os}-${arch}.${ext}
|
||||||
|
|
||||||
toolsets:
|
toolsets:
|
||||||
appimage: "1.0.2"
|
appimage: '1.0.2'
|
||||||
|
|
||||||
npmRebuild: false
|
npmRebuild: false
|
||||||
|
|
||||||
|
|||||||
@@ -43,9 +43,11 @@ mac:
|
|||||||
icon: assets/icons/icon.icns
|
icon: assets/icons/icon.icns
|
||||||
type: distribution
|
type: distribution
|
||||||
hardenedRuntime: false
|
hardenedRuntime: false
|
||||||
identity: "-"
|
identity: '-'
|
||||||
gatekeeperAssess: false
|
gatekeeperAssess: false
|
||||||
notarize: false
|
notarize: false
|
||||||
|
extendInfo:
|
||||||
|
NSLocalNetworkUsageDescription: 'Local network is necessary for accessing servers hosted on the same system as Feishin'
|
||||||
|
|
||||||
dmg:
|
dmg:
|
||||||
contents: [{ x: 130, y: 220 }, { x: 410, y: 220, type: link, path: /Applications }]
|
contents: [{ x: 130, y: 220 }, { x: 410, y: 220, type: link, path: /Applications }]
|
||||||
@@ -60,7 +62,7 @@ linux:
|
|||||||
artifactName: ${productName}-${os}-${arch}.${ext}
|
artifactName: ${productName}-${os}-${arch}.${ext}
|
||||||
|
|
||||||
toolsets:
|
toolsets:
|
||||||
appimage: "1.0.2"
|
appimage: '1.0.2'
|
||||||
|
|
||||||
npmRebuild: false
|
npmRebuild: false
|
||||||
publish:
|
publish:
|
||||||
|
|||||||
@@ -1381,6 +1381,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pasteGradient": "Vložit přechod",
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+40
-27
@@ -242,7 +242,7 @@
|
|||||||
"criticRating": "Kritikerbewertung",
|
"criticRating": "Kritikerbewertung",
|
||||||
"album": "$t(entity.album, {\"count\": 1})",
|
"album": "$t(entity.album, {\"count\": 1})",
|
||||||
"trackNumber": "Track",
|
"trackNumber": "Track",
|
||||||
"channels": "$t(common.channel_other)",
|
"channels": "$t(common.channel,{\"count\":2})",
|
||||||
"owner": "$t(common.owner)",
|
"owner": "$t(common.owner)",
|
||||||
"genre": "$t(entity.genre, {\"count\": 1})",
|
"genre": "$t(entity.genre, {\"count\": 1})",
|
||||||
"artist": "$t(entity.artist, {\"count\": 1})",
|
"artist": "$t(entity.artist, {\"count\": 1})",
|
||||||
@@ -273,13 +273,13 @@
|
|||||||
"input_name": "Servername",
|
"input_name": "Servername",
|
||||||
"success": "Server erfolgreich hinzugefügt",
|
"success": "Server erfolgreich hinzugefügt",
|
||||||
"input_savePassword": "Passwort speichern",
|
"input_savePassword": "Passwort speichern",
|
||||||
"ignoreSsl": "SSL ignorieren $t(common.restartRequired)",
|
"ignoreSsl": "SSL ignorieren ($t(common.restartRequired))",
|
||||||
"ignoreCors": "CORS ignorieren $t(common.restartRequired)",
|
"ignoreCors": "CORS ignorieren ($t(common.restartRequired))",
|
||||||
"error_savePassword": "Beim Speichern des Passworts ist ein Fehler aufgetreten",
|
"error_savePassword": "Beim Speichern des Passworts ist ein Fehler aufgetreten",
|
||||||
"input_preferInstantMix": "Instant-Mix bevorzugen",
|
"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_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_preferRemoteUrl": "öffentliche URL bevorzugen",
|
||||||
"input_remoteUrl": "Öffentliche URL",
|
"input_remoteUrl": "öffentliche URL",
|
||||||
"input_remoteUrlPlaceholder": "Optional: öffentliche URL für externe Funktionen"
|
"input_remoteUrlPlaceholder": "Optional: öffentliche URL für externe Funktionen"
|
||||||
},
|
},
|
||||||
"addToPlaylist": {
|
"addToPlaylist": {
|
||||||
@@ -357,6 +357,9 @@
|
|||||||
"input_offset": "$t(setting.lyricOffset)",
|
"input_offset": "$t(setting.lyricOffset)",
|
||||||
"export": "Songtexte exportieren",
|
"export": "Songtexte exportieren",
|
||||||
"input_synced": "Synchronisierte Songtexte exportieren"
|
"input_synced": "Synchronisierte Songtexte exportieren"
|
||||||
|
},
|
||||||
|
"editRadioStation": {
|
||||||
|
"success": "Radiosender erfolgreich aktualisiert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
@@ -426,8 +429,8 @@
|
|||||||
"pagination": "Seitenzahlen",
|
"pagination": "Seitenzahlen",
|
||||||
"pagination_itemsPerPage": "Elemente pro Seite",
|
"pagination_itemsPerPage": "Elemente pro Seite",
|
||||||
"pagination_infinite": "unendlich",
|
"pagination_infinite": "unendlich",
|
||||||
"moveUp": "Nach oben bewegen",
|
"moveUp": "nach oben",
|
||||||
"moveDown": "Nach unten bewegen",
|
"moveDown": "nach unten",
|
||||||
"pinToLeft": "links anheften",
|
"pinToLeft": "links anheften",
|
||||||
"pinToRight": "rechts anheften",
|
"pinToRight": "rechts anheften",
|
||||||
"itemGap": "Item Abstand (px)",
|
"itemGap": "Item Abstand (px)",
|
||||||
@@ -450,13 +453,13 @@
|
|||||||
"albumArtist": "$t(entity.albumArtist, {\"count\": 1})",
|
"albumArtist": "$t(entity.albumArtist, {\"count\": 1})",
|
||||||
"artist": "$t(entity.artist, {\"count\": 1})",
|
"artist": "$t(entity.artist, {\"count\": 1})",
|
||||||
"favorite": "$t(common.favorite)",
|
"favorite": "$t(common.favorite)",
|
||||||
"actions": "$t(common.action_other)",
|
"actions": "$t(common.action,{\"count\":2})",
|
||||||
"genre": "$t(entity.genre, {\"count\": 1})",
|
"genre": "$t(entity.genre, {\"count\": 1})",
|
||||||
"album": "$t(entity.album, {\"count\": 1})",
|
"album": "$t(entity.album, {\"count\": 1})",
|
||||||
"size": "$t(common.size)",
|
"size": "$t(common.size)",
|
||||||
"bpm": "$t(common.bpm)",
|
"bpm": "$t(common.bpm)",
|
||||||
"titleCombined": "$t(common.title) (kombiniert)",
|
"titleCombined": "$t(common.title) (kombiniert)",
|
||||||
"channels": "$t(common.channel_other)",
|
"channels": "$t(common.channel,{\"count\":2})",
|
||||||
"duration": "$t(common.duration)",
|
"duration": "$t(common.duration)",
|
||||||
"note": "$t(common.note)",
|
"note": "$t(common.note)",
|
||||||
"owner": "$t(common.owner)",
|
"owner": "$t(common.owner)",
|
||||||
@@ -493,7 +496,7 @@
|
|||||||
"rating": "Bewertung",
|
"rating": "Bewertung",
|
||||||
"albumCount": "$t(entity.album, {\"count\": 2})",
|
"albumCount": "$t(entity.album, {\"count\": 2})",
|
||||||
"artist": "$t(entity.artist, {\"count\": 1})",
|
"artist": "$t(entity.artist, {\"count\": 1})",
|
||||||
"channels": "$t(common.channel_other)",
|
"channels": "$t(common.channel,{\"count\":2})",
|
||||||
"comment": "Kommentar",
|
"comment": "Kommentar",
|
||||||
"dateAdded": "hinzugefügt am",
|
"dateAdded": "hinzugefügt am",
|
||||||
"playCount": "Abgespielt",
|
"playCount": "Abgespielt",
|
||||||
@@ -716,7 +719,8 @@
|
|||||||
},
|
},
|
||||||
"releasenotes": {
|
"releasenotes": {
|
||||||
"commitsSinceStable": "Commits seit {{stable}}",
|
"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": {
|
"player": {
|
||||||
@@ -766,12 +770,12 @@
|
|||||||
"sleepTimer": "Sleep Timer",
|
"sleepTimer": "Sleep Timer",
|
||||||
"sleepTimer_custom": "Benutzerdefiniert",
|
"sleepTimer_custom": "Benutzerdefiniert",
|
||||||
"sleepTimer_hours": "{{count}} std",
|
"sleepTimer_hours": "{{count}} std",
|
||||||
"sleepTimer_minutes": "{{count}} min",
|
"sleepTimer_minutes": "{{count}} Min",
|
||||||
"trackRadio": "Song Radio",
|
"trackRadio": "Song Radio",
|
||||||
"albumRadio": "Album Radio"
|
"albumRadio": "Album Radio"
|
||||||
},
|
},
|
||||||
"setting": {
|
"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",
|
"audioExclusiveMode": "Audio-Exklusivmodus",
|
||||||
"audioDevice": "Audiogerät",
|
"audioDevice": "Audiogerät",
|
||||||
"accentColor": "Akzentfarbe",
|
"accentColor": "Akzentfarbe",
|
||||||
@@ -789,7 +793,7 @@
|
|||||||
"crossfadeDuration": "Dauer der Überblendung",
|
"crossfadeDuration": "Dauer der Überblendung",
|
||||||
"discordIdleStatus": "rich presence status im Leerlauf",
|
"discordIdleStatus": "rich presence status im Leerlauf",
|
||||||
"audioPlayer": "Audio-Player",
|
"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",
|
"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",
|
"remotePort_description": "Legt den Port des Fernsteuerungsserver fest",
|
||||||
"hotkey_skipBackward": "rückwärts springen",
|
"hotkey_skipBackward": "rückwärts springen",
|
||||||
@@ -1009,7 +1013,7 @@
|
|||||||
"transcodeFormat": "Format für Umwandlung",
|
"transcodeFormat": "Format für Umwandlung",
|
||||||
"startMinimized_description": "Startet die Anwendung im Info-Bereich",
|
"startMinimized_description": "Startet die Anwendung im Info-Bereich",
|
||||||
"startMinimized": "Im Info-Bereich starten",
|
"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",
|
"mediaSession": "Media Session aktivieren",
|
||||||
"artistBackgroundBlur": "Unschärfegrad für Künstlerhintergründe",
|
"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",
|
"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",
|
"contextMenu_description": "Legt die Einträge fest, die im Rechtsklick-Menü angezeigt werden sollen. Abgewählte Einträge werden ausgeblendet",
|
||||||
"crossfadeStyle": "Art der Überblende",
|
"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",
|
"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_optionBeta": "Beta",
|
||||||
"releaseChannel_optionLatest": "Stabil",
|
"releaseChannel_optionLatest": "Stabil",
|
||||||
"releaseChannel": "Veröffentlichungskanal",
|
"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_artistname": "Künstlername(n)",
|
||||||
"discordDisplayType_description": "Ändert den aktuellen Titel im Zuhör-Status",
|
"discordDisplayType_description": "Ändert den aktuellen Titel im Zuhör-Status",
|
||||||
"discordDisplayType_songname": "Songtitel",
|
"discordDisplayType_songname": "Songtitel",
|
||||||
@@ -1121,7 +1125,13 @@
|
|||||||
"nativeSpotify": "Spotify App benutzen",
|
"nativeSpotify": "Spotify App benutzen",
|
||||||
"qobuz_description": "Zeige Links zu Qobuz auf den Interpreten/Alben Seiten",
|
"qobuz_description": "Zeige Links zu Qobuz auf den Interpreten/Alben Seiten",
|
||||||
"spotify_description": "Zeige Links zu Spotify 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": {
|
"dragDropZone": {
|
||||||
"error_oneFileOnly": "Bitte wähle nur 1 Datei",
|
"error_oneFileOnly": "Bitte wähle nur 1 Datei",
|
||||||
@@ -1211,7 +1221,7 @@
|
|||||||
"dualVertical": "Dual-Vertikal"
|
"dualVertical": "Dual-Vertikal"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"minimumFrequency": "Mindestfrequenz",
|
"minimumFrequency": "Minimale Frequenz",
|
||||||
"minimumDecibels": "Minimale Dezibel",
|
"minimumDecibels": "Minimale Dezibel",
|
||||||
"visualizerType": "Visualizer Art",
|
"visualizerType": "Visualizer Art",
|
||||||
"cyclePresets": "Vorlagen durchrotieren",
|
"cyclePresets": "Vorlagen durchrotieren",
|
||||||
@@ -1246,10 +1256,10 @@
|
|||||||
"channelLayout": "Kanallayout",
|
"channelLayout": "Kanallayout",
|
||||||
"maxFPS": "Max FPS",
|
"maxFPS": "Max FPS",
|
||||||
"opacity": "Deckkraft",
|
"opacity": "Deckkraft",
|
||||||
"customGradients": "Benutzerdefinierte Gradienten",
|
"customGradients": "Benutzerdefinierter Farbverlauf",
|
||||||
"addCustomGradient": "Benutzerdefinierten Gradienten hinzufügen",
|
"addCustomGradient": "Benutzerdefinierten Gradienten hinzufügen",
|
||||||
"gradientName": "Gradientenname",
|
"gradientName": "Name Farbverlauf",
|
||||||
"gradientNamePlaceholder": "Gradientenname",
|
"gradientNamePlaceholder": "Name Farbverlauf",
|
||||||
"vertical": "Vertikal",
|
"vertical": "Vertikal",
|
||||||
"horizontal": "Horizontal",
|
"horizontal": "Horizontal",
|
||||||
"addColor": "Farbe hinzufügen",
|
"addColor": "Farbe hinzufügen",
|
||||||
@@ -1262,9 +1272,9 @@
|
|||||||
"builtIn": "Eingebaut",
|
"builtIn": "Eingebaut",
|
||||||
"colors": "Farben",
|
"colors": "Farben",
|
||||||
"colorMode": "Farbmodus",
|
"colorMode": "Farbmodus",
|
||||||
"gradient": "Gradienten",
|
"gradient": "Farbverlauf",
|
||||||
"gradientLeft": "Gradienten links",
|
"gradientLeft": "Farberverlauf links",
|
||||||
"gradientRight": "Gradienten rechts",
|
"gradientRight": "Farbverlauf rechts",
|
||||||
"fft": "FFT",
|
"fft": "FFT",
|
||||||
"fftSize": "FFT Größe",
|
"fftSize": "FFT Größe",
|
||||||
"smoothing": "Glätten",
|
"smoothing": "Glätten",
|
||||||
@@ -1273,17 +1283,20 @@
|
|||||||
"sensitivity": "Empfindlichkeit",
|
"sensitivity": "Empfindlichkeit",
|
||||||
"weightingFilter": "Gewichtungsfilter",
|
"weightingFilter": "Gewichtungsfilter",
|
||||||
"maximumDecibels": "Maximale Dezibel",
|
"maximumDecibels": "Maximale Dezibel",
|
||||||
"linearAmplitude": "Lineare Amplitude",
|
"linearAmplitude": "Linearer Ausschlag",
|
||||||
"linearBoost": "Linearer Boost",
|
"linearBoost": "Linearer Boost",
|
||||||
"radialSpectrum": "Radiales Spektrum",
|
"radialSpectrum": "Radiales Spektrum",
|
||||||
"radial": "Radial",
|
"radial": "Radial",
|
||||||
"radialInvert": "Radial invertiert",
|
"radialInvert": "Radial invertiert",
|
||||||
"radius": "Radius",
|
"radius": "Radius",
|
||||||
"miscellaneousSettings": "Verschiedenes Einstellungen",
|
"miscellaneousSettings": "Verschiedene Einstellungen",
|
||||||
"ansiBands": "ANSI Bänder",
|
"ansiBands": "ANSI Bänder",
|
||||||
"lowResolution": "Niedrige Auflösung",
|
"lowResolution": "Niedrige Auflösung",
|
||||||
"showFPS": "FPS anzeigen",
|
"showFPS": "FPS anzeigen",
|
||||||
"fadePeaks": "Spitzen abblenden",
|
"fadePeaks": "Spitzen abblenden",
|
||||||
"showPeaks": "Spitzen anzeigen"
|
"showPeaks": "Spitzen anzeigen",
|
||||||
|
"systemAudioConsentAllow": "Erlauben",
|
||||||
|
"systemAudioConsentDecline": "Ablehnen",
|
||||||
|
"frequencyScale": "Frequenzskala"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1001,6 +1001,9 @@
|
|||||||
"export": "Exportar letras",
|
"export": "Exportar letras",
|
||||||
"input_synced": "Exportar letras sincronizadas",
|
"input_synced": "Exportar letras sincronizadas",
|
||||||
"input_offset": "$t(setting.lyricOffset)"
|
"input_offset": "$t(setting.lyricOffset)"
|
||||||
|
},
|
||||||
|
"editRadioStation": {
|
||||||
|
"success": "Estación de radio actualizada con éxito"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"table": {
|
"table": {
|
||||||
@@ -1378,6 +1381,12 @@
|
|||||||
"lowResolution": "Baja resolución",
|
"lowResolution": "Baja resolución",
|
||||||
"splitGradient": "Dividir degradado",
|
"splitGradient": "Dividir degradado",
|
||||||
"noteLabels": "Etiquetas de notas",
|
"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."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1002,6 +1002,9 @@
|
|||||||
"export": "exporter les paroles",
|
"export": "exporter les paroles",
|
||||||
"input_synced": "exporter les paroles synchronisées",
|
"input_synced": "exporter les paroles synchronisées",
|
||||||
"input_offset": "$t(setting.lyricOffset)"
|
"input_offset": "$t(setting.lyricOffset)"
|
||||||
|
},
|
||||||
|
"editRadioStation": {
|
||||||
|
"success": "station de radio a été mise à jour avec succès"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
@@ -1379,6 +1382,12 @@
|
|||||||
},
|
},
|
||||||
"lumiBars": "Lumi Bars",
|
"lumiBars": "Lumi Bars",
|
||||||
"outlineBars": "Outline 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}}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1037,7 +1037,7 @@
|
|||||||
},
|
},
|
||||||
"updateServer": {
|
"updateServer": {
|
||||||
"title": "サーバーをアップデート",
|
"title": "サーバーをアップデート",
|
||||||
"success": "サーバーがアップデートされました"
|
"success": "サーバーの更新に成功しました"
|
||||||
},
|
},
|
||||||
"queryEditor": {
|
"queryEditor": {
|
||||||
"input_optionMatchAll": "すべて一致",
|
"input_optionMatchAll": "すべて一致",
|
||||||
@@ -1102,6 +1102,9 @@
|
|||||||
},
|
},
|
||||||
"saveQueue": {
|
"saveQueue": {
|
||||||
"success": "プレイキューをサーバーに保存しました"
|
"success": "プレイキューをサーバーに保存しました"
|
||||||
|
},
|
||||||
|
"editRadioStation": {
|
||||||
|
"success": "ラジオ局の更新に成功しました"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
@@ -1332,6 +1335,12 @@
|
|||||||
"d": "D",
|
"d": "D",
|
||||||
"z": "Z"
|
"z": "Z"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"systemAudioConsentAllow": "許可",
|
||||||
|
"systemAudioConsentBody": "ビジュアライザーを機能させるためには、システムオーディオへのアクセスが必要です",
|
||||||
|
"systemAudioConsentDecline": "拒否",
|
||||||
|
"systemAudioConsentTitle": "システムオーディオへのアクセスを許可しますか?",
|
||||||
|
"systemAudioCaptureFailed": "キャプチャを開始できませんでした: {{message}}",
|
||||||
|
"systemAudioNoAudioTrack": "音声トラックが返されませんでした。プロンプトが表示されたら、音声キャプチャが有効になっていることを確認してください。"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1381,6 +1381,12 @@
|
|||||||
},
|
},
|
||||||
"pasteGradient": "Wklej Gradient",
|
"pasteGradient": "Wklej Gradient",
|
||||||
"pasteGradientPlaceholder": "Wklej tutaj JSON gradientu...",
|
"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."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1337,6 +1337,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"systemAudioCaptureFailed": "無法開始擷取:{{message}}",
|
"systemAudioCaptureFailed": "無法開始擷取:{{message}}",
|
||||||
"systemAudioNoAudioTrack": "沒有回傳任何音軌。確保在提示時啟用音訊擷取。"
|
"systemAudioNoAudioTrack": "沒有回傳任何音軌。確保在提示時啟用音訊擷取。",
|
||||||
|
"systemAudioConsentAllow": "允許",
|
||||||
|
"systemAudioConsentBody": "此視覺化器需要存取系統音訊才能運作",
|
||||||
|
"systemAudioConsentDecline": "拒絕",
|
||||||
|
"systemAudioConsentTitle": "允許存取系統音訊?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,9 @@
|
|||||||
import './core';
|
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 {};
|
||||||
|
|||||||
+1
-14
@@ -5,7 +5,6 @@ import {
|
|||||||
app,
|
app,
|
||||||
BrowserWindow,
|
BrowserWindow,
|
||||||
BrowserWindowConstructorOptions,
|
BrowserWindowConstructorOptions,
|
||||||
desktopCapturer,
|
|
||||||
globalShortcut,
|
globalShortcut,
|
||||||
ipcMain,
|
ipcMain,
|
||||||
Menu,
|
Menu,
|
||||||
@@ -734,19 +733,7 @@ async function createWindow(first = true): Promise<void> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
mainWindow.webContents.session.setDisplayMediaRequestHandler((_request, callback) => {
|
mainWindow.webContents.session.setDisplayMediaRequestHandler((_request, callback) => {
|
||||||
desktopCapturer
|
callback({ audio: 'loopback' });
|
||||||
.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) {
|
if (!disableAutoUpdates() && store.get('disable_auto_updates') !== true) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { SetActivity } from '@xhayper/discord-rpc';
|
import type { SetActivity } from '@xhayper/discord-rpc';
|
||||||
|
|
||||||
import { ipcRenderer } from 'electron';
|
import { ipcRenderer } from 'electron';
|
||||||
|
|
||||||
const initialize = (clientId: string) => {
|
const initialize = (clientId: string) => {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { initClient, initContract } from '@ts-rest/core';
|
import { initClient, initContract } from '@ts-rest/core';
|
||||||
import axios, { AxiosError, AxiosRequestConfig, AxiosResponse, isAxiosError } from 'axios';
|
import axios, { AxiosError, AxiosRequestConfig, AxiosResponse, isAxiosError } from 'axios';
|
||||||
import omitBy from 'lodash/omitBy';
|
|
||||||
import qs from 'qs';
|
import qs from 'qs';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
@@ -380,8 +379,26 @@ axiosClient.interceptors.response.use(
|
|||||||
const parsePath = (fullPath: string) => {
|
const parsePath = (fullPath: string) => {
|
||||||
const [path, params] = fullPath.split('?');
|
const [path, params] = fullPath.split('?');
|
||||||
|
|
||||||
const parsedParams = qs.parse(params, { arrayLimit: 99999, parameterLimit: 99999 });
|
const url = new URLSearchParams(params);
|
||||||
const notNilParams = omitBy(parsedParams, (value) => value === 'undefined' || value === 'null');
|
const notNilParams: Record<string, string[]> = {};
|
||||||
|
|
||||||
|
for (const [key, value] of url) {
|
||||||
|
if (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 {
|
return {
|
||||||
params: notNilParams,
|
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) {
|
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;
|
const td = transcodeDecision.body.transcodeDecision;
|
||||||
|
|||||||
@@ -22,12 +22,7 @@ import { WebAudio } from '/@/shared/types/types';
|
|||||||
import '/@/shared/styles/global.css';
|
import '/@/shared/styles/global.css';
|
||||||
import { PlayerProvider } from '/@/renderer/features/player/context/player-context';
|
import { PlayerProvider } from '/@/renderer/features/player/context/player-context';
|
||||||
import { AudioPlayers } from '/@/renderer/features/player/components/audio-players';
|
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(() =>
|
const UpdateAvailableDialog = lazy(() =>
|
||||||
import('./update-available-dialog').then((module) => ({
|
import('./update-available-dialog').then((module) => ({
|
||||||
@@ -82,8 +77,8 @@ const AppShell = memo(function AppShell() {
|
|||||||
<AppRouter />
|
<AppRouter />
|
||||||
</PlayerProvider>
|
</PlayerProvider>
|
||||||
</WebAudioContext.Provider>
|
</WebAudioContext.Provider>
|
||||||
<Suspense fallback={null}>
|
|
||||||
<ReleaseNotesModal />
|
<ReleaseNotesModal />
|
||||||
|
<Suspense fallback={null}>
|
||||||
<UpdateAvailableDialog />
|
<UpdateAvailableDialog />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -169,6 +169,292 @@ export interface ItemCardDerivativeProps extends Omit<ItemCardProps, 'type'> {
|
|||||||
showRating: boolean;
|
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 = ({
|
const CompactItemCard = ({
|
||||||
controls,
|
controls,
|
||||||
data,
|
data,
|
||||||
@@ -185,7 +471,6 @@ const CompactItemCard = ({
|
|||||||
showRating,
|
showRating,
|
||||||
withControls,
|
withControls,
|
||||||
}: ItemCardDerivativeProps) => {
|
}: ItemCardDerivativeProps) => {
|
||||||
const [showControls, setShowControls] = useState(false);
|
|
||||||
const itemRowId =
|
const itemRowId =
|
||||||
data && internalState && typeof data === 'object' && 'id' in data
|
data && internalState && typeof data === 'object' && 'id' in data
|
||||||
? internalState.extractRowId(data)
|
? internalState.extractRowId(data)
|
||||||
@@ -297,18 +582,6 @@ const CompactItemCard = ({
|
|||||||
if (data) {
|
if (data) {
|
||||||
const navigationPath = getItemNavigationPath(data, itemType);
|
const navigationPath = getItemNavigationPath(data, itemType);
|
||||||
|
|
||||||
const handleMouseEnter = () => {
|
|
||||||
if (withControls) {
|
|
||||||
setShowControls(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseLeave = () => {
|
|
||||||
if (withControls) {
|
|
||||||
setShowControls(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleContextMenu = (e: React.MouseEvent<HTMLElement>) => {
|
const handleContextMenu = (e: React.MouseEvent<HTMLElement>) => {
|
||||||
if (!data || !controls) {
|
if (!data || !controls) {
|
||||||
return;
|
return;
|
||||||
@@ -338,81 +611,6 @@ const CompactItemCard = ({
|
|||||||
e.stopPropagation();
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx(styles.container, styles.compact, {
|
className={clsx(styles.container, styles.compact, {
|
||||||
@@ -421,31 +619,24 @@ const CompactItemCard = ({
|
|||||||
})}
|
})}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
{enableNavigation && navigationPath && (imageAsLink ?? !internalState) ? (
|
<CompactItemCardImageArea
|
||||||
<Link
|
controls={controls}
|
||||||
className={imageContainerClassName}
|
data={data}
|
||||||
draggable={false}
|
enableExpansion={enableExpansion}
|
||||||
onClick={handleImageClick}
|
enableNavigation={enableNavigation}
|
||||||
onContextMenu={handleContextMenu}
|
handleContextMenu={handleContextMenu}
|
||||||
onDragStart={handleLinkDragStart}
|
handleImageClick={handleImageClick}
|
||||||
onMouseEnter={handleMouseEnter}
|
handleLinkDragStart={handleLinkDragStart}
|
||||||
onMouseLeave={handleMouseLeave}
|
imageAsLink={imageAsLink}
|
||||||
state={{ item: data }}
|
imageFetchPriority={imageFetchPriority}
|
||||||
to={navigationPath}
|
internalState={internalState}
|
||||||
>
|
isRound={isRound}
|
||||||
{imageContainerContent}
|
itemType={itemType}
|
||||||
</Link>
|
navigationPath={navigationPath}
|
||||||
) : (
|
rows={rows}
|
||||||
<div
|
showRating={showRating}
|
||||||
className={imageContainerClassName}
|
withControls={withControls}
|
||||||
onClick={handleImageClick}
|
/>
|
||||||
onContextMenu={handleContextMenu}
|
|
||||||
onMouseEnter={handleMouseEnter}
|
|
||||||
onMouseLeave={handleMouseLeave}
|
|
||||||
>
|
|
||||||
{imageContainerContent}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -491,7 +682,6 @@ const DefaultItemCard = ({
|
|||||||
showRating,
|
showRating,
|
||||||
withControls,
|
withControls,
|
||||||
}: ItemCardDerivativeProps) => {
|
}: ItemCardDerivativeProps) => {
|
||||||
const [showControls, setShowControls] = useState(false);
|
|
||||||
const itemRowId =
|
const itemRowId =
|
||||||
data && internalState && typeof data === 'object' && 'id' in data
|
data && internalState && typeof data === 'object' && 'id' in data
|
||||||
? internalState.extractRowId(data)
|
? internalState.extractRowId(data)
|
||||||
@@ -538,18 +728,6 @@ const DefaultItemCard = ({
|
|||||||
if (data) {
|
if (data) {
|
||||||
const navigationPath = getItemNavigationPath(data, itemType);
|
const navigationPath = getItemNavigationPath(data, itemType);
|
||||||
|
|
||||||
const handleMouseEnter = () => {
|
|
||||||
if (withControls) {
|
|
||||||
setShowControls(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseLeave = () => {
|
|
||||||
if (withControls) {
|
|
||||||
setShowControls(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleContextMenu = (e: React.MouseEvent<HTMLElement>) => {
|
const handleContextMenu = (e: React.MouseEvent<HTMLElement>) => {
|
||||||
if (!data || !controls) {
|
if (!data || !controls) {
|
||||||
return;
|
return;
|
||||||
@@ -579,93 +757,30 @@ const DefaultItemCard = ({
|
|||||||
e.stopPropagation();
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx(styles.container, {
|
className={clsx(styles.container, {
|
||||||
[styles.selected]: isSelected,
|
[styles.selected]: isSelected,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{enableNavigation && navigationPath && (imageAsLink ?? !internalState) ? (
|
<ItemCardStandardImageArea
|
||||||
<Link
|
controls={controls}
|
||||||
className={imageContainerClassName}
|
data={data}
|
||||||
draggable={false}
|
enableExpansion={enableExpansion}
|
||||||
onClick={handleImageClick}
|
enableNavigation={enableNavigation}
|
||||||
onContextMenu={handleContextMenu}
|
handleContextMenu={handleContextMenu}
|
||||||
onDragStart={handleLinkDragStart}
|
handleImageClick={handleImageClick}
|
||||||
onMouseEnter={handleMouseEnter}
|
handleLinkDragStart={handleLinkDragStart}
|
||||||
onMouseLeave={handleMouseLeave}
|
imageAsLink={imageAsLink}
|
||||||
state={{ item: data }}
|
imageFetchPriority={imageFetchPriority}
|
||||||
to={navigationPath}
|
internalState={internalState}
|
||||||
>
|
isRound={isRound}
|
||||||
{imageContainerContent}
|
itemType={itemType}
|
||||||
</Link>
|
navigationPath={navigationPath}
|
||||||
) : (
|
showRating={showRating}
|
||||||
<div
|
variant="default"
|
||||||
className={imageContainerClassName}
|
withControls={withControls}
|
||||||
onClick={handleImageClick}
|
/>
|
||||||
onContextMenu={handleContextMenu}
|
|
||||||
onMouseEnter={handleMouseEnter}
|
|
||||||
onMouseLeave={handleMouseLeave}
|
|
||||||
>
|
|
||||||
{imageContainerContent}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className={styles.detailContainer}>
|
<div className={styles.detailContainer}>
|
||||||
{rows
|
{rows
|
||||||
.filter(
|
.filter(
|
||||||
@@ -728,7 +843,6 @@ const PosterItemCard = ({
|
|||||||
showRating,
|
showRating,
|
||||||
withControls,
|
withControls,
|
||||||
}: ItemCardDerivativeProps) => {
|
}: ItemCardDerivativeProps) => {
|
||||||
const [showControls, setShowControls] = useState(false);
|
|
||||||
const itemRowId =
|
const itemRowId =
|
||||||
data && internalState && typeof data === 'object' && 'id' in data
|
data && internalState && typeof data === 'object' && 'id' in data
|
||||||
? internalState.extractRowId(data)
|
? internalState.extractRowId(data)
|
||||||
@@ -840,18 +954,6 @@ const PosterItemCard = ({
|
|||||||
if (data) {
|
if (data) {
|
||||||
const navigationPath = getItemNavigationPath(data, itemType);
|
const navigationPath = getItemNavigationPath(data, itemType);
|
||||||
|
|
||||||
const handleMouseEnter = () => {
|
|
||||||
if (withControls) {
|
|
||||||
setShowControls(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseLeave = () => {
|
|
||||||
if (withControls) {
|
|
||||||
setShowControls(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleContextMenu = (e: React.MouseEvent<HTMLElement>) => {
|
const handleContextMenu = (e: React.MouseEvent<HTMLElement>) => {
|
||||||
if (!data || !controls) {
|
if (!data || !controls) {
|
||||||
return;
|
return;
|
||||||
@@ -881,63 +983,6 @@ const PosterItemCard = ({
|
|||||||
e.stopPropagation();
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx(styles.container, styles.poster, {
|
className={clsx(styles.container, styles.poster, {
|
||||||
@@ -946,31 +991,24 @@ const PosterItemCard = ({
|
|||||||
})}
|
})}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
{enableNavigation && navigationPath && (imageAsLink ?? !internalState) ? (
|
<ItemCardStandardImageArea
|
||||||
<Link
|
controls={controls}
|
||||||
className={imageContainerClassName}
|
data={data}
|
||||||
draggable={false}
|
enableExpansion={enableExpansion}
|
||||||
onClick={handleImageClick}
|
enableNavigation={enableNavigation}
|
||||||
onContextMenu={handleContextMenu}
|
handleContextMenu={handleContextMenu}
|
||||||
onDragStart={handleLinkDragStart}
|
handleImageClick={handleImageClick}
|
||||||
onMouseEnter={handleMouseEnter}
|
handleLinkDragStart={handleLinkDragStart}
|
||||||
onMouseLeave={handleMouseLeave}
|
imageAsLink={imageAsLink}
|
||||||
state={{ item: data }}
|
imageFetchPriority={imageFetchPriority}
|
||||||
to={navigationPath}
|
internalState={internalState}
|
||||||
>
|
isRound={isRound}
|
||||||
{imageContainerContent}
|
itemType={itemType}
|
||||||
</Link>
|
navigationPath={navigationPath}
|
||||||
) : (
|
showRating={showRating}
|
||||||
<div
|
variant="poster"
|
||||||
className={imageContainerClassName}
|
withControls={withControls}
|
||||||
onClick={handleImageClick}
|
/>
|
||||||
onContextMenu={handleContextMenu}
|
|
||||||
onMouseEnter={handleMouseEnter}
|
|
||||||
onMouseLeave={handleMouseLeave}
|
|
||||||
>
|
|
||||||
{imageContainerContent}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{data && (
|
{data && (
|
||||||
<div className={styles.detailContainer}>
|
<div className={styles.detailContainer}>
|
||||||
{rows
|
{rows
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { SetActivity, StatusDisplayType } from '@xhayper/discord-rpc';
|
import type { SetActivity } from '@xhayper/discord-rpc';
|
||||||
|
|
||||||
import isElectron from 'is-electron';
|
import isElectron from 'is-electron';
|
||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
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';
|
import { PlayerStatus } from '/@/shared/types/types';
|
||||||
|
|
||||||
const discordRpc = isElectron() ? window.api.discordRpc : null;
|
const discordRpc = isElectron() ? window.api.discordRpc : null;
|
||||||
|
|
||||||
|
const DiscordStatusDisplayType = {
|
||||||
|
DETAILS: 2,
|
||||||
|
NAME: 0,
|
||||||
|
STATE: 1,
|
||||||
|
} as const;
|
||||||
|
|
||||||
type ActivityState = [QueueSong | undefined, number, PlayerStatus];
|
type ActivityState = [QueueSong | undefined, number, PlayerStatus];
|
||||||
|
|
||||||
const MAX_FIELD_LENGTH = 127;
|
const MAX_FIELD_LENGTH = 127;
|
||||||
@@ -122,7 +130,7 @@ export const useDiscordRpc = () => {
|
|||||||
: undefined
|
: undefined
|
||||||
: sentenceCase(current[2]),
|
: sentenceCase(current[2]),
|
||||||
state: truncate(artist),
|
state: truncate(artist),
|
||||||
statusDisplayType: StatusDisplayType.STATE,
|
statusDisplayType: DiscordStatusDisplayType.STATE,
|
||||||
type: discordSettings.showAsListening ? 2 : 0,
|
type: discordSettings.showAsListening ? 2 : 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -196,9 +204,9 @@ export const useDiscordRpc = () => {
|
|||||||
const artists = song?.artists.map((artist) => artist.name).join(', ');
|
const artists = song?.artists.map((artist) => artist.name).join(', ');
|
||||||
|
|
||||||
const statusDisplayMap = {
|
const statusDisplayMap = {
|
||||||
[DiscordDisplayType.ARTIST_NAME]: StatusDisplayType.STATE,
|
[DiscordDisplayType.ARTIST_NAME]: DiscordStatusDisplayType.STATE,
|
||||||
[DiscordDisplayType.FEISHIN]: StatusDisplayType.NAME,
|
[DiscordDisplayType.FEISHIN]: DiscordStatusDisplayType.NAME,
|
||||||
[DiscordDisplayType.SONG_NAME]: StatusDisplayType.DETAILS,
|
[DiscordDisplayType.SONG_NAME]: DiscordStatusDisplayType.DETAILS,
|
||||||
};
|
};
|
||||||
|
|
||||||
const activity: SetActivity = {
|
const activity: SetActivity = {
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ export const ThemeSettings = memo(() => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
renderOption={renderThemeOption}
|
renderOption={renderThemeOption}
|
||||||
|
searchable
|
||||||
width={240}
|
width={240}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { AnimatePresence } from 'motion/react';
|
import { AnimatePresence } from 'motion/react';
|
||||||
import { lazy, Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
import { Outlet } from 'react-router';
|
import { Outlet } from 'react-router';
|
||||||
|
|
||||||
import styles from './mobile-layout.module.css';
|
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 { MobileFullscreenPlayer } from '/@/renderer/features/player/components/mobile-fullscreen-player';
|
||||||
import { MobileSidebar } from '/@/renderer/features/sidebar/components/mobile-sidebar';
|
import { MobileSidebar } from '/@/renderer/features/sidebar/components/mobile-sidebar';
|
||||||
import { PlayerBar } from '/@/renderer/layouts/default-layout/player-bar';
|
import { PlayerBar } from '/@/renderer/layouts/default-layout/player-bar';
|
||||||
|
import { WindowBar } from '/@/renderer/layouts/window-bar';
|
||||||
import { useFullScreenPlayerOverlayState, useWindowBarStyle } from '/@/renderer/store';
|
import { useFullScreenPlayerOverlayState, useWindowBarStyle } from '/@/renderer/store';
|
||||||
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
||||||
import { Drawer } from '/@/shared/components/drawer/drawer';
|
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 { useDisclosure } from '/@/shared/hooks/use-disclosure';
|
||||||
import { Platform } from '/@/shared/types/types';
|
import { Platform } from '/@/shared/types/types';
|
||||||
|
|
||||||
const WindowBar = lazy(() =>
|
|
||||||
import('/@/renderer/layouts/window-bar').then((module) => ({
|
|
||||||
default: module.WindowBar,
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
|
|
||||||
interface MobileLayoutProps {
|
interface MobileLayoutProps {
|
||||||
shell?: boolean;
|
shell?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { lazy, Suspense } from 'react';
|
import { lazy, Suspense } from 'react';
|
||||||
import { HashRouter, Route, Routes } from 'react-router';
|
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 { RouterErrorBoundary } from '/@/renderer/features/shared/components/router-error-boundary';
|
||||||
import { AuthenticationOutlet } from '/@/renderer/layouts/authentication-outlet';
|
import { AuthenticationOutlet } from '/@/renderer/layouts/authentication-outlet';
|
||||||
import { ResponsiveLayout } from '/@/renderer/layouts/responsive-layout';
|
import { ResponsiveLayout } from '/@/renderer/layouts/responsive-layout';
|
||||||
@@ -96,18 +97,6 @@ const LyricsSettingsContextModal = (props: any) => (
|
|||||||
</Suspense>
|
</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(() =>
|
const LazyAddToPlaylistContextModal = lazy(() =>
|
||||||
import('/@/renderer/features/playlists/components/add-to-playlist-context-modal').then(
|
import('/@/renderer/features/playlists/components/add-to-playlist-context-modal').then(
|
||||||
(module) => ({
|
(module) => ({
|
||||||
@@ -200,7 +189,7 @@ const appRouterModals = {
|
|||||||
|
|
||||||
export const AppRouter = () => {
|
export const AppRouter = () => {
|
||||||
const router = (
|
const router = (
|
||||||
<HashRouter unstable_useTransitions>
|
<HashRouter unstable_useTransitions={false}>
|
||||||
<ModalsProvider modals={appRouterModals}>
|
<ModalsProvider modals={appRouterModals}>
|
||||||
<RouterErrorBoundary>
|
<RouterErrorBoundary>
|
||||||
<Routes>
|
<Routes>
|
||||||
|
|||||||
Reference in New Issue
Block a user