Compare commits

...

7 Commits

Author SHA1 Message Date
navidrome-bot
fc9c23268f fix(ui): update Danish, German, Greek, Japanese, Polish, Russian, Swedish, Thai, Ukrainian translations from POEditor 2025-11-24 10:08:32 +00:00
Kendall Garner
c40f12e65b
fix(scanner): Use repeated arg instead of comma split (#4727) 2025-11-23 22:16:10 -05:00
Deluan
12d0898585 chore(docker): remove GODEBUG=asyncpreemptoff=1 flag, as it should not be needed on Go 1.15+
Signed-off-by: Deluan <deluan@navidrome.org>
2025-11-22 21:36:44 -05:00
Deluan
c21aee7360 fix(config): enables quoted ; as values in ini files
Signed-off-by: Deluan <deluan@navidrome.org>
2025-11-22 20:14:44 -05:00
Xavier Araque
ee51bd9281
feat(ui): add SquiddiesGlass Theme (#4632)
* feat: Add SquiddiesGlass Theme

* feat: fix commnets by gemini-code-assist in PR

* feat: fix Prettier format

* feat: fix play button, and text mobile

* feat: fix play button, and text mobile, prettier

* feat: fix chip, title artist

* fix: loading albbun, play button color

* prettier

Signed-off-by: Deluan <deluan@navidrome.org>

---------

Signed-off-by: Deluan <deluan@navidrome.org>
Co-authored-by: Xavier Araque <francisco.araque@toolfactory.net>
Co-authored-by: Deluan <deluan@navidrome.org>
2025-11-22 13:41:59 -05:00
Stephan Wahlen
2451e9e7ae
feat(ui): add AMusic (Apple Music inspired) theme (#4723)
* first show at AMuisc Theme

* prettier

* fix Duplicate key 'MuiButton'

* fix file name

* Update amusic.js

* Add styles for NDAlbumGridView in amusic.js

* Fix MuiToolbar background property in amusic.js

* Fix syntax error in amusic.js background property

* run prettier

* fix banded table styling and more

* more styling to player

- fix some appearances of green in queue
- match queue styling to rest of theme
- round albumart in player and prevent rotation

* fix queue panel background and border

to make it stand out more against the background

* fix stray comma

and lint+prettier

* queue hover still green

and player preview image not rounded properly

* Update amusic.css.js

* more mobile color fixes

* artist page

* prettier

* rounded art in albumgridview

* small tweaks to colors and radiuses

* artist and album heading

* external links colors

* unify font colors + albumgrid corner radius

* get rid of queue hover green

* unify colors in player

same red shades as primary

* mobile player floating panel background shade of green

* unify border colors

and attempt to get album cover corner radius working

* final touches

* Update amusic.css.js

* fix invisible button color fir muibutton

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* fix css syntax on player queue color overrides

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* remove unused MuiTableHead

* sort theme list in index.js alphabetically

* remove unused properties

* Revert "fix css syntax on player queue color overrides"

This reverts commit 503bba321d958aed5251667c58214822ceb70f59.

---------

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2025-11-22 11:23:02 -05:00
Deluan
f6b2ab5726 feat(ui): add loading state to artist action buttons for improved user experience
Signed-off-by: Deluan <deluan@navidrome.org>
2025-11-21 22:23:38 -05:00
24 changed files with 1372 additions and 98 deletions

View File

@ -137,7 +137,6 @@ ENV ND_MUSICFOLDER=/music
ENV ND_DATAFOLDER=/data
ENV ND_CONFIGFILE=/data/navidrome.toml
ENV ND_PORT=4533
ENV GODEBUG="asyncpreemptoff=1"
RUN touch /.nddockerenv
EXPOSE ${ND_PORT}

View File

@ -4,7 +4,6 @@ import (
"context"
"encoding/gob"
"os"
"strings"
"github.com/navidrome/navidrome/core"
"github.com/navidrome/navidrome/db"
@ -19,13 +18,13 @@ import (
var (
fullScan bool
subprocess bool
targets string
targets []string
)
func init() {
scanCmd.Flags().BoolVarP(&fullScan, "full", "f", false, "check all subfolders, ignoring timestamps")
scanCmd.Flags().BoolVarP(&subprocess, "subprocess", "", false, "run as subprocess (internal use)")
scanCmd.Flags().StringVarP(&targets, "targets", "t", "", "comma-separated list of libraryID:folderPath pairs (e.g., \"1:Music/Rock,1:Music/Jazz,2:Classical\")")
scanCmd.Flags().StringArrayVarP(&targets, "target", "t", []string{}, "list of libraryID:folderPath pairs, can be repeated (e.g., \"-t 1:Music/Rock -t 1:Music/Jazz -t 2:Classical\")")
rootCmd.AddCommand(scanCmd)
}
@ -74,9 +73,9 @@ func runScanner(ctx context.Context) {
// Parse targets if provided
var scanTargets []model.ScanTarget
if targets != "" {
if len(targets) > 0 {
var err error
scanTargets, err = model.ParseTargets(strings.Split(targets, ","))
scanTargets, err = model.ParseTargets(targets)
if err != nil {
log.Fatal(ctx, "Failed to parse targets", err)
}

View File

@ -617,7 +617,12 @@ func init() {
func InitConfig(cfgFile string) {
codecRegistry := viper.NewCodecRegistry()
_ = codecRegistry.RegisterCodec("ini", ini.Codec{})
_ = codecRegistry.RegisterCodec("ini", ini.Codec{
LoadOptions: ini.LoadOptions{
UnescapeValueDoubleQuotes: true,
UnescapeValueCommentSymbols: true,
},
})
viper.SetOptions(viper.WithCodecRegistry(codecRegistry))
cfgFile = getConfigFile(cfgFile)

View File

@ -39,6 +39,7 @@ var _ = Describe("Configuration", func() {
Expect(conf.Server.MusicFolder).To(Equal(fmt.Sprintf("/%s/music", format)))
Expect(conf.Server.UIWelcomeMessage).To(Equal("Welcome " + format))
Expect(conf.Server.Tags["custom"].Aliases).To(Equal([]string{format, "test"}))
Expect(conf.Server.Tags["artist"].Split).To(Equal([]string{";"}))
// The config file used should be the one we created
Expect(conf.Server.ConfigFile).To(Equal(filename))

View File

@ -1,6 +1,7 @@
[default]
MusicFolder = /ini/music
UIWelcomeMessage = Welcome ini
UIWelcomeMessage = 'Welcome ini' ; Just a comment to test the LoadOptions
[Tags]
Custom.Aliases = ini,test
Custom.Aliases = ini,test
artist.Split = ";" # Should be able to read ; as a separator

View File

@ -2,6 +2,9 @@
"musicFolder": "/json/music",
"uiWelcomeMessage": "Welcome json",
"Tags": {
"artist": {
"split": ";"
},
"custom": {
"aliases": [
"json",

View File

@ -1,5 +1,7 @@
musicFolder = "/toml/music"
uiWelcomeMessage = "Welcome toml"
Tags.artist.Split = ';'
[Tags.custom]
aliases = ["toml", "test"]

View File

@ -1,6 +1,8 @@
musicFolder: "/yaml/music"
uiWelcomeMessage: "Welcome yaml"
Tags:
artist:
split: [";"]
custom:
aliases:
- yaml

View File

@ -83,7 +83,7 @@
"actions": {
"playAll": "Afspil",
"playNext": "Afspil næste",
"addToQueue": "Afspil senere",
"addToQueue": "Føj til kø",
"shuffle": "Bland",
"addToPlaylist": "Føj til afspilningsliste",
"download": "Download",
@ -301,14 +301,19 @@
"actions": {
"scan": "Scanningsbibliotek",
"manageUsers": "Administrer brugeradgang",
"viewDetails": "Se detaljer"
"viewDetails": "Se detaljer",
"quickScan": "hurtig skanning",
"fullScan": "Fuld skanning"
},
"notifications": {
"created": "Bibliotek oprettet",
"updated": "Biblioteket er blevet opdateret",
"deleted": "Biblioteket er blevet slettet",
"scanStarted": "Biblioteksscanning startet",
"scanCompleted": "Biblioteksscanning fuldført"
"scanCompleted": "Biblioteksscanning fuldført",
"quickScanStarted": "hurtig skanning startet",
"fullScanStarted": "Fuld skanning startet",
"scanError": "Kan ikke starte skanning. Tjek loggen"
},
"validation": {
"nameRequired": "Biblioteksnavn er påkrævet",
@ -549,7 +554,7 @@
"closeText": "Luk",
"notContentText": "Ingen musik",
"clickToPlayText": "Tryk for at afspille",
"clickToPauseText": "Tryk for at pause",
"clickToPauseText": "Tryk for at sætte på pause",
"nextTrackText": "Næste nummer",
"previousTrackText": "Forrige nummer",
"reloadText": "Genindlæs",
@ -604,7 +609,8 @@
"serverDown": "OFFLINE",
"scanType": "Type",
"status": "Scanningsfejl",
"elapsedTime": "Medgået tid"
"elapsedTime": "Medgået tid",
"selectiveScan": "Selektiv"
},
"help": {
"title": "Navidrome genvejstaster",

View File

@ -301,14 +301,19 @@
"actions": {
"scan": "Bibliothek scannen",
"manageUsers": "Zugriff verwalten",
"viewDetails": "Details ansehen"
"viewDetails": "Details ansehen",
"quickScan": "Schneller Scan",
"fullScan": "Kompletter Scan"
},
"notifications": {
"created": "Bibliothek erfolgreich erstellt",
"updated": "Bibliothek erfolgreich geändert",
"deleted": "Bibliothek erfolgreich gelöscht",
"scanStarted": "Bibliothek Scan gestartet",
"scanCompleted": "Bibliothek Scan vollständig"
"scanCompleted": "Bibliothek Scan vollständig",
"quickScanStarted": "Schneller Scan gestartet",
"fullScanStarted": "Kompletter Scan gestartet",
"scanError": "Fehler beim Starten des Scans. Logs prüfen"
},
"validation": {
"nameRequired": "Bibliotheksname ist Pflichtfeld",
@ -604,7 +609,8 @@
"serverDown": "OFFLINE",
"scanType": "Typ",
"status": "Scan Fehler",
"elapsedTime": "Laufzeit"
"elapsedTime": "Laufzeit",
"selectiveScan": "Selektiver Scan"
},
"help": {
"title": "Navidrome Hotkeys",

View File

@ -301,14 +301,19 @@
"actions": {
"scan": "Σάρωση βιβλιοθήκης",
"manageUsers": "Διαχείριση πρόσβασης χρήστη",
"viewDetails": "Προβολή λεπτομερειών"
"viewDetails": "Προβολή λεπτομερειών",
"quickScan": "Γρήγορη σάρωση",
"fullScan": "Πλήρης σάρωση"
},
"notifications": {
"created": "Η βιβλιοθήκη δημιουργήθηκε με επιτυχία",
"updated": "Η βιβλιοθήκη ενημερώθηκε με επιτυχία",
"deleted": "Η βιβλιοθήκη διαγράφηκε με επιτυχία",
"scanStarted": "Ξεκίνησε η σάρωση της βιβλιοθήκης",
"scanCompleted": "Η σάρωση της βιβλιοθήκης ολοκληρώθηκε"
"scanCompleted": "Η σάρωση της βιβλιοθήκης ολοκληρώθηκε",
"quickScanStarted": "Η Γρήγορη Σάρωση ξεκίνησε",
"fullScanStarted": "Η πλήρης σάρωση ξεκίνησε",
"scanError": "Σφάλμα κατά την έναρξη της σάρωσης. Ελέγξτε τα αρχεία καταγραφής."
},
"validation": {
"nameRequired": "Απαιτείται όνομα βιβλιοθήκης",
@ -604,7 +609,8 @@
"serverDown": "ΕΚΤΟΣ ΣΥΝΔΕΣΗΣ",
"scanType": "Τύπος",
"status": "Σφάλμα σάρωσης",
"elapsedTime": "Χρόνος που πέρασε"
"elapsedTime": "Χρόνος που πέρασε",
"selectiveScan": "Εκλεκτικός"
},
"help": {
"title": "Συντομεύσεις του Navidrome",

View File

@ -27,12 +27,16 @@
"playDate": "最後の再生",
"channels": "チャンネル",
"createdAt": "追加日",
"grouping": "",
"mood": "",
"participants": "",
"tags": "",
"mappedTags": "",
"rawTags": ""
"grouping": "グループ分け",
"mood": "ムード",
"participants": "追加参加者",
"tags": "追加タグ",
"mappedTags": "マッピング済みタグ",
"rawTags": "未処理タグ",
"bitDepth": "ビット深度",
"sampleRate": "サンプリングレート",
"missing": "不明",
"libraryName": "ライブラリ"
},
"actions": {
"addToQueue": "最後に再生",
@ -41,7 +45,8 @@
"shuffleAll": "全曲シャッフル",
"download": "ダウンロード",
"playNext": "次に再生",
"info": "詳細"
"info": "詳細",
"showInPlaylist": "含まれるプレイリスト"
}
},
"album": {
@ -65,12 +70,15 @@
"releaseDate": "リリース日",
"releases": "リリース",
"released": "リリース",
"recordLabel": "",
"catalogNum": "",
"releaseType": "",
"grouping": "",
"media": "",
"mood": ""
"recordLabel": "ラベル",
"catalogNum": "カタログ番号",
"releaseType": "タイプ",
"grouping": "グループ分け",
"media": "メディア",
"mood": "ムード",
"date": "録音日",
"missing": "不明",
"libraryName": "ライブラリ"
},
"actions": {
"playAll": "再生",
@ -102,22 +110,29 @@
"rating": "レート",
"genre": "ジャンル",
"size": "サイズ",
"role": ""
"role": "役割",
"missing": "不明"
},
"roles": {
"albumartist": "",
"artist": "",
"composer": "",
"conductor": "",
"lyricist": "",
"arranger": "",
"producer": "",
"director": "",
"engineer": "",
"mixer": "",
"remixer": "",
"djmixer": "",
"performer": ""
"albumartist": "アルバムアーティスト",
"artist": "アーティスト",
"composer": "作曲家",
"conductor": "指揮者",
"lyricist": "作詞家",
"arranger": "編曲者",
"producer": "プロデューサー",
"director": "ディレクター",
"engineer": "エンジニア",
"mixer": "ミキサー",
"remixer": "リミキサー",
"djmixer": "DJ ミキサー",
"performer": "演奏者",
"maincredit": "アルバムアーティストもしくはアーティスト"
},
"actions": {
"shuffle": "シャッフル",
"radio": "ラジオ",
"topSongs": "トップソング"
}
},
"user": {
@ -134,10 +149,12 @@
"currentPassword": "現在のパスワード",
"newPassword": "新しいパスワード",
"token": "トークン",
"lastAccessAt": "最終アクセス"
"lastAccessAt": "最終アクセス",
"libraries": "ライブラリ"
},
"helperTexts": {
"name": "名前の変更は次回ログイン以降反映されます"
"name": "名前の変更は次回ログイン以降反映されます",
"libraries": "このユーザーに対して特定ライブラリを選択するか、デフォルトのライブラリを使用する場合は空欄のままにします"
},
"notifications": {
"created": "ユーザーが作成されました",
@ -146,7 +163,12 @@
},
"message": {
"listenBrainzToken": "ListenBrainzユーザートークンを入力",
"clickHereForToken": "ここをクリックしトークンを入手"
"clickHereForToken": "ここをクリックしトークンを入手",
"selectAllLibraries": "全てのライブラリを選択",
"adminAutoLibraries": "管理者ユーザーは自動的にすべてのライブラリにアクセスできます"
},
"validation": {
"librariesRequired": "管理者以外のユーザーには少なくとも1つのライブラリを選択する必要があります"
}
},
"player": {
@ -190,11 +212,17 @@
"addNewPlaylist": "'%{name}' を作成",
"export": "エクスポート",
"makePublic": "公開する",
"makePrivate": "非公開にする"
"makePrivate": "非公開にする",
"saveQueue": "キューをプレイリストに保存",
"searchOrCreate": "プレイリストを検索または入力して新規作成...",
"pressEnterToCreate": "Enterキーを押して新しいプレイリストを作成",
"removeFromSelection": "選択から削除"
},
"message": {
"duplicate_song": "重複する曲を追加",
"song_exist": "既にプレイリストに存在する曲です。追加しますか?"
"song_exist": "既にプレイリストに存在する曲です。追加しますか?",
"noPlaylistsFound": "プレイリストが見つかりません",
"noPlaylists": "利用可能なプレイリストはありません"
}
},
"radio": {
@ -228,17 +256,77 @@
}
},
"missing": {
"name": "",
"name": "欠落したファイル",
"fields": {
"path": "",
"size": "",
"updatedAt": ""
"path": "パス",
"size": "サイズ",
"updatedAt": "欠落日",
"libraryName": "ライブラリ"
},
"actions": {
"remove": ""
"remove": "削除",
"remove_all": "全て削除"
},
"notifications": {
"removed": ""
"removed": "欠落ファイルが削除されました"
},
"empty": "ファイルの欠落はありません"
},
"library": {
"name": "ライブラリ",
"fields": {
"name": "名前",
"path": "パス",
"remotePath": "リモートパス",
"lastScanAt": "最終スキャン",
"songCount": "曲数",
"albumCount": "アルバム数",
"artistCount": "アーティスト数",
"totalSongs": "曲数",
"totalAlbums": "アルバム数",
"totalArtists": "アーティスト数",
"totalFolders": "フォルダー数",
"totalFiles": "ファイル数",
"totalMissingFiles": "欠落したファイル",
"totalSize": "合計サイズ",
"totalDuration": "合計時間",
"defaultNewUsers": "新規ユーザーに対するデフォルト",
"createdAt": "作成日",
"updatedAt": "更新日"
},
"sections": {
"basic": "基本情報",
"statistics": "統計"
},
"actions": {
"scan": "ライブラリをスキャン",
"manageUsers": "ユーザーアクセス管理",
"viewDetails": "詳細を表示",
"quickScan": "クイックスキャン",
"fullScan": "フルスキャン"
},
"notifications": {
"created": "ライブラリが正常に作成されました",
"updated": "ライブラリが正常に更新されました",
"deleted": "ライブラリが正常に削除されました",
"scanStarted": "スキャンを開始しました",
"scanCompleted": "スキャンが完了しました",
"quickScanStarted": "クイックスキャンを開始しました",
"fullScanStarted": "フルスキャンを開始しました",
"scanError": "スキャン開始中にエラーが発生。ログを確認してください"
},
"validation": {
"nameRequired": "ライブラリの名前が必要です",
"pathRequired": "ライブラリのパスが必要です",
"pathNotDirectory": "ライブラリパスはディレクトリである必要があります",
"pathNotFound": "ライブラリのパスが見つかりません",
"pathNotAccessible": "ライブラリパスへアクセスできません",
"pathInvalid": "無効なライブラリパス"
},
"messages": {
"deleteConfirm": "このライブラリを削除しますか?関連する全てのデータとユーザーアクセスが削除されます。",
"scanInProgress": "スキャン中...",
"noLibrariesAssigned": "このユーザーに割り当てられているライブラリはありません"
}
}
},
@ -418,8 +506,12 @@
"shareFailure": "コピーに失敗しました %{url}",
"downloadDialogTitle": "ダウンロード %{resource} '%{name}' (%{size})",
"shareCopyToClipboard": "クリップボードへコピー: Ctrl+C, Enter",
"remove_missing_title": "",
"remove_missing_content": ""
"remove_missing_title": "欠落ファイルを削除",
"remove_missing_content": "選択した欠落ファイルをデータベースから削除してもよろしいですか?これにより、再生数や評価を含むそれらのファイルへの参照が完全に削除されます。",
"remove_all_missing_title": "全ての欠落ファイルを削除",
"remove_all_missing_content": "データベースから欠落ファイルをすべて削除してもよろしいですか?これにより、再生数や評価を含むそれらのファイルへの参照が永久に削除されます。",
"noSimilarSongsFound": "類似の曲が見つかりませんでした",
"noTopSongsFound": "トップソングが見つかりません"
},
"menu": {
"library": "ライブラリ",
@ -448,7 +540,13 @@
"albumList": "アルバム",
"about": "詳細",
"playlists": "プレイリスト",
"sharedPlaylists": "共有プレイリスト"
"sharedPlaylists": "共有プレイリスト",
"librarySelector": {
"allLibraries": "全てのライブラリ( %{count} )",
"multipleLibraries": "%{selected} 個 / %{total} 個のライブラリ",
"selectLibraries": "ライブラリを選択",
"none": "無し"
}
},
"player": {
"playListsText": "再生リスト",
@ -485,15 +583,34 @@
"disabled": "無効",
"waiting": "待機中"
}
},
"tabs": {
"about": "詳細",
"config": "設定"
},
"config": {
"configName": "設定名",
"environmentVariable": "環境変数",
"currentValue": "現在値",
"configurationFile": "設定ファイル",
"exportToml": "設定をエクスポート(TOML)",
"exportSuccess": "設定をTOML形式でクリップボードへエクスポートしました",
"exportFailed": "設定のコピーに失敗しました",
"devFlagsHeader": "開発フラグ(変更・削除の可能性あり)",
"devFlagsComment": "これらは実験的な設定であり、将来のバージョンで削除される可能性があります"
}
},
"activity": {
"title": "活動",
"totalScanned": "スキャン済みフォルダー",
"quickScan": "クイックスキャン",
"fullScan": "フルスキャン",
"quickScan": "クイック",
"fullScan": "フル",
"serverUptime": "サーバー稼働時間",
"serverDown": "サーバーオフライン"
"serverDown": "サーバーオフライン",
"scanType": "最終スキャン",
"status": "スキャンエラー",
"elapsedTime": "経過時間",
"selectiveScan": "選択的スキャン"
},
"help": {
"title": "ホットキー",
@ -508,5 +625,10 @@
"toggle_love": "星の付け外し",
"current_song": "現在の曲へ移動"
}
},
"nowPlaying": {
"title": "再生中",
"empty": "何も再生されていません",
"minutesAgo": "%{smart_count} 分前 |||| %{smart_count} 分前"
}
}

View File

@ -301,14 +301,19 @@
"actions": {
"scan": "Skanuj Bibliotekę",
"manageUsers": "Zarządzaj Dostępami Użytkownika",
"viewDetails": "Zobacz Szczegóły"
"viewDetails": "Zobacz Szczegóły",
"quickScan": "Szybkie Skanowanie",
"fullScan": "Pełne Skanowanie"
},
"notifications": {
"created": "Biblioteka utworzona prawidłowo",
"updated": "Biblioteka zaktualizowana prawidłowo",
"deleted": "Biblioteka usunięta prawidłowo",
"scanStarted": "Rozpoczęto skan biblioteki",
"scanCompleted": "Zakończono skan biblioteki"
"scanCompleted": "Zakończono skan biblioteki",
"quickScanStarted": "Szybkie skanowanie rozpoczęte",
"fullScanStarted": "Pełne skanowanie rozpoczęte",
"scanError": "Błąd podczas startu skanowania. Sprawdź logi"
},
"validation": {
"nameRequired": "Nazwa biblioteki jest wymagana",
@ -604,7 +609,8 @@
"serverDown": "NIEDOSTĘPNY",
"scanType": "Typ",
"status": "Błąd Skanowania",
"elapsedTime": "Upłynięty Czas"
"elapsedTime": "Upłynięty Czas",
"selectiveScan": "Selektywne"
},
"help": {
"title": "Skróty Klawiszowe Navidrome",

View File

@ -301,20 +301,25 @@
"actions": {
"scan": "Сканировать библиотеку",
"manageUsers": "Управление доступом пользователей",
"viewDetails": "Просмотреть подробности"
"viewDetails": "Просмотреть подробности",
"quickScan": "Быстрое сканирование",
"fullScan": "Полное сканирование"
},
"notifications": {
"created": "Библиотека успешно создана",
"updated": "Библиотека успешно обновлена",
"deleted": "Библиотека успешно удалена",
"scanStarted": "Сканирование библиотеки начато",
"scanCompleted": "Сканирование библиотеки закончено"
"scanCompleted": "Сканирование библиотеки закончено",
"quickScanStarted": "Быстрое сканирование началось",
"fullScanStarted": "Началось полное сканирование",
"scanError": "Ошибка при запуске сканирования. Проверьте логи"
},
"validation": {
"nameRequired": "Имя библиотеки обязательно",
"pathRequired": "Путь к библиотеке обязателен",
"pathNotDirectory": "Путь к библиотеке должен быть директорией",
"pathNotFound": "Путь к библиотеке не найдено",
"pathNotFound": "Путь к библиотеке не найден",
"pathNotAccessible": "Путь к библиотеке недоступен",
"pathInvalid": "Неверный путь к библиотеке"
},
@ -604,7 +609,8 @@
"serverDown": "Оффлайн",
"scanType": "Тип",
"status": "Ошибка сканирования",
"elapsedTime": "Прошедшее время"
"elapsedTime": "Прошедшее время",
"selectiveScan": "Избирательный"
},
"help": {
"title": "Горячие клавиши Navidrome",

View File

@ -301,14 +301,19 @@
"actions": {
"scan": "Scanna bibliotek",
"manageUsers": "Hantera användaråtkomst",
"viewDetails": "Se detaljer"
"viewDetails": "Se detaljer",
"quickScan": "Snabbscan",
"fullScan": "Komplett scan"
},
"notifications": {
"created": "Biblioteket har skapats",
"updated": "Biblioteket har uppdaterats",
"deleted": "Biblioteket har raderats",
"scanStarted": "Biblioteksscan startad",
"scanCompleted": "Biblioteksscan avslutad"
"scanCompleted": "Biblioteksscan avslutad",
"quickScanStarted": "Snabbscan startad",
"fullScanStarted": "Komplett scan startad",
"scanError": "Fel vid start av scan. Se loggarna"
},
"validation": {
"nameRequired": "Biblioteksnamn krävs",
@ -604,7 +609,8 @@
"serverDown": "OFFLINE",
"scanType": "Typ",
"status": "Fel vid scanning",
"elapsedTime": "Spelad tid"
"elapsedTime": "Spelad tid",
"selectiveScan": "Urval"
},
"help": {
"title": "Navidrome kortkommandon",

View File

@ -301,14 +301,19 @@
"actions": {
"scan": "สแกนห้องสมุด",
"manageUsers": "ตั้งค่าการเข้าถึง",
"viewDetails": "ดูรายละเอียด"
"viewDetails": "ดูรายละเอียด",
"quickScan": "สแกนแบบเร็ว",
"fullScan": "สแกนแบบเต็ม"
},
"notifications": {
"created": "สร้างห้องสมุดเรียบร้อย",
"updated": "อัพเดทห้องสมุดเรียบร้อย",
"deleted": "ลบห้องสมุดเพลงเรียบร้อยแล้ว",
"scanStarted": "เริ่มสแกนห้องสมุด",
"scanCompleted": "สแกนห้องสมุดเสร็จแล้ว"
"scanCompleted": "สแกนห้องสมุดเสร็จแล้ว",
"quickScanStarted": "เริ่มสแกนแบบเร็ว",
"fullScanStarted": "เริ่มสแกนแบบเต็ม",
"scanError": "การเริ่มสแกนผิดพลาด ดูในบันทึก"
},
"validation": {
"nameRequired": "ต้องใส่ชื่อห้องสมุดเพลง",
@ -604,7 +609,8 @@
"serverDown": "ออฟไลน์",
"scanType": "ประเภท",
"status": "สแกนผิดพลาด",
"elapsedTime": "เวลาที่ใช้"
"elapsedTime": "เวลาที่ใช้",
"selectiveScan": "เลือก"
},
"help": {
"title": "คีย์ลัด Navidrome",

View File

@ -301,14 +301,19 @@
"actions": {
"scan": "Сканувати бібліотеку",
"manageUsers": "Керування доступом користувачів",
"viewDetails": "Переглянути подробиці"
"viewDetails": "Переглянути подробиці",
"quickScan": "Швидке сканування",
"fullScan": "Повне сканування"
},
"notifications": {
"created": "Бібліотеку успішно створено",
"updated": "Бібліотеку успішно оновлено",
"deleted": "Бібліотеку успішно видалено",
"scanStarted": "Сканування бібліотеки розпочато",
"scanCompleted": "Сканування бібліотеки закінчено"
"scanCompleted": "Сканування бібліотеки закінчено",
"quickScanStarted": "Швидке сканування виконується",
"fullScanStarted": "Повне сканування виконується",
"scanError": "Помилка при виконанні сканування. Перевірте лоґи"
},
"validation": {
"nameRequired": "Ім'я бібліотеки обов'язкове",
@ -604,7 +609,8 @@
"serverDown": "Оффлайн",
"scanType": "Тип",
"status": "Помилка сканування",
"elapsedTime": "Пройдений час"
"elapsedTime": "Пройдений час",
"selectiveScan": "Вибірковий"
},
"help": {
"title": "Гарячі клавіші Navidrome",

View File

@ -8,12 +8,10 @@ import (
"io"
"os"
"os/exec"
"strings"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/utils/slice"
)
// scannerExternal is a scanner that runs an external process to do the scanning. It is used to avoid
@ -47,9 +45,10 @@ func (s *scannerExternal) scan(ctx context.Context, fullScan bool, targets []mod
// Add targets if provided
if len(targets) > 0 {
targetsStr := strings.Join(slice.Map(targets, func(t model.ScanTarget) string { return t.String() }), ",")
args = append(args, "--targets", targetsStr)
log.Debug(ctx, "Spawning external scanner process with targets", "fullScan", fullScan, "path", exe, "targets", targetsStr)
for _, target := range targets {
args = append(args, "-t", target.String())
}
log.Debug(ctx, "Spawning external scanner process with targets", "fullScan", fullScan, "path", exe, "targets", targets)
} else {
log.Debug(ctx, "Spawning external scanner process", "fullScan", fullScan, "path", exe)
}

View File

@ -1,7 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import { useDispatch } from 'react-redux'
import { useMediaQuery } from '@material-ui/core'
import { useMediaQuery, CircularProgress } from '@material-ui/core'
import { makeStyles } from '@material-ui/core/styles'
import {
Button,
@ -45,6 +45,12 @@ const useStyles = makeStyles((theme) => ({
},
}))
const LoadingButton = ({ loading, icon, ...rest }) => (
<Button {...rest}>
{loading ? <CircularProgress size={20} color="inherit" /> : icon}
</Button>
)
const ArtistActions = ({ className, record, ...rest }) => {
const dispatch = useDispatch()
const translate = useTranslate()
@ -52,34 +58,45 @@ const ArtistActions = ({ className, record, ...rest }) => {
const notify = useNotify()
const classes = useStyles()
const isMobile = useMediaQuery((theme) => theme.breakpoints.down('xs'))
const [loadingAction, setLoadingAction] = React.useState(null)
const isLoading = !!loadingAction
const handlePlay = React.useCallback(async () => {
setLoadingAction('play')
try {
await playTopSongs(dispatch, notify, record.name)
} catch (e) {
// eslint-disable-next-line no-console
console.error('Error fetching top songs for artist:', e)
notify('ra.page.error', 'warning')
} finally {
setLoadingAction(null)
}
}, [dispatch, notify, record])
const handleShuffle = React.useCallback(async () => {
setLoadingAction('shuffle')
try {
await playShuffle(dataProvider, dispatch, record.id)
} catch (e) {
// eslint-disable-next-line no-console
console.error('Error fetching songs for shuffle:', e)
notify('ra.page.error', 'warning')
} finally {
setLoadingAction(null)
}
}, [dataProvider, dispatch, record, notify])
const handleRadio = React.useCallback(async () => {
setLoadingAction('radio')
try {
await playSimilar(dispatch, notify, record.id)
} catch (e) {
// eslint-disable-next-line no-console
console.error('Error starting radio for artist:', e)
notify('ra.page.error', 'warning')
} finally {
setLoadingAction(null)
}
}, [dispatch, notify, record])
@ -88,30 +105,33 @@ const ArtistActions = ({ className, record, ...rest }) => {
className={`${className} ${classes.toolbar}`}
{...sanitizeListRestProps(rest)}
>
<Button
<LoadingButton
onClick={handlePlay}
label={translate('resources.artist.actions.topSongs')}
className={classes.button}
size={isMobile ? 'small' : 'medium'}
>
<PlayArrowIcon />
</Button>
<Button
disabled={isLoading}
loading={loadingAction === 'play'}
icon={<PlayArrowIcon />}
/>
<LoadingButton
onClick={handleShuffle}
label={translate('resources.artist.actions.shuffle')}
className={classes.button}
size={isMobile ? 'small' : 'medium'}
>
<ShuffleIcon />
</Button>
<Button
disabled={isLoading}
loading={loadingAction === 'shuffle'}
icon={<ShuffleIcon />}
/>
<LoadingButton
onClick={handleRadio}
label={translate('resources.artist.actions.radio')}
className={classes.button}
size={isMobile ? 'small' : 'medium'}
>
<IoIosRadio className={classes.radioIcon} />
</Button>
disabled={isLoading}
loading={loadingAction === 'radio'}
icon={<IoIosRadio className={classes.radioIcon} />}
/>
</TopToolbar>
)
}

View File

@ -0,0 +1,175 @@
const stylesheet = `
.react-jinke-music-player-main .music-player-panel .panel-content .rc-slider-handle {
background: #c231ab
}
.react-jinke-music-player-main .music-player-panel .panel-content .rc-slider-track,
.react-jinke-music-player-mobile-progress .rc-slider-track {
background: linear-gradient(to left, #c231ab, #380eff)
}
.react-jinke-music-player-mobile {
background-color: #171717 !important;
}
.react-jinke-music-player-mobile-progress .rc-slider-handle {
background: #c231ab;
height: 20px;
width: 20px;
margin-top: -9px;
}
.react-jinke-music-player-main ::-webkit-scrollbar-thumb {
background-color: #c231ab;
}
.react-jinke-music-player-pause-icon {
background-color: #c231ab;
border-radius: 50%;
outline: auto;
color: white;
}
.react-jinke-music-player-main .music-player-panel .panel-content .player-content {
z-index: 99999;
}
.react-jinke-music-player-main .music-player-panel .panel-content .player-content .play-btn svg {
border-radius: 50%;
outline: auto;
color: white;
}
.react-jinke-music-player-main .music-player-panel .panel-content .player-content .play-btn svg:hover {
background-color: #c231ab;
border-radius: 50%;
outline: auto;
color: white;
}
.react-jinke-music-player-main svg:hover {
color: #c231ab;
}
.react-jinke-music-player .music-player-controller {
color: #c231ab;
border: 1px solid #e14ac2;
}
.react-jinke-music-player .music-player-controller.music-player-playing:before {
border: 1px solid rgba(194, 49, 171, 0.3);
}
.react-jinke-music-player .music-player .destroy-btn {
background-color: #c2c1c2;
top: -7px;
border-radius: 50%;
display: flex;
}
.react-jinke-music-player .music-player .destroy-btn svg {
font-size: 20px;
}
@media screen and (max-width: 767px) {
.react-jinke-music-player .music-player .destroy-btn {
right: -12px;
}
}
.react-jinke-music-player-mobile-header-right {
right: 0;
top: 0;
}
@media screen and (max-width: 767px) {
.react-jinke-music-player-main svg {
font-size: 32px;
}
}
@keyframes gradientFlow {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
.RaBulkActionsToolbar .MuiButton-label {
color: white;
}
a[aria-current="page"] {
color: #c231ab !important;
font-weight: bold;
}
a[aria-current="page"] .MuiListItemIcon-root {
color: #c231ab !important;
}
.panel-content {
position: relative;
overflow: hidden;
background: linear-gradient(90deg, #311f2f, #0a0912, #2f0c28);
background-size: 300% 300%;
animation: gradientFlow 10s ease-in-out infinite;
}
/* Equalizer bars */
.panel-content::before {
content: "";
position: absolute;
inset: 0;
background: repeating-linear-gradient(
90deg,
rgba(255, 255, 255, 0.05) 0px,
rgba(255, 255, 255, 0.05) 2px,
transparent 1px,
transparent 3px
);
animation: equalizer 1.8s infinite ease-in-out;
filter: blur(1px);
opacity: 0.5;
}
@keyframes backgroundFlow {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
/* Vertical movement, equalizer type */
@keyframes equalizer {
0%, 100% {
transform: scaleY(1);
opacity: 0.2;
}
25% {
transform: scaleY(1.4);
opacity: 0.9;
}
50% {
transform: scaleY(0.7);
opacity: 0.2;
}
75% {
transform: scaleY(1.2);
opacity: 0.8;
}
}
@keyframes pulse {
0% { opacity: 0.5; }
100% { opacity: 1; }
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
`
export default stylesheet

View File

@ -0,0 +1,608 @@
import stylesheet from './SquiddiesGlass.css.js'
/**
* Color constants used throughout the Squiddies Glass theme.
* Provides a consistent color palette with pink, gray, purple, and basic colors.
* @type {Object}
*/
const colors = {
pink: {
100: '#fbe3f4',
200: '#f5b9e3',
300: '#ec7cd6',
400: '#e14ac2',
500: '#c231ab', // base
600: '#a31a92',
700: '#8b0f7e',
800: '#7a006d',
900: '#670066',
},
gray: {
50: '#c2c1c2',
100: '#b3b3b3', // light gray
200: '#282828', // medium dark
300: '#1d1d1d', // darker
400: '#181818', // even darker
500: '#171717', // darkest
},
purple: {
400: '#524590',
500: '#4d3249',
600: '#6d1c5e',
},
black: '#000',
white: '#fff',
dark: '#121212',
}
/**
* Shared style object for music list action buttons.
* Defines common styling for buttons in music lists, including hover effects and responsive scaling.
* @type {Object}
*/
const musicListActions = {
padding: '1rem 0',
alignItems: 'center',
'@global': {
button: {
border: '1px solid transparent',
backgroundColor: 'inherit',
color: colors.gray[100],
'&:hover': {
border: `1px solid ${colors.gray[100]}`,
backgroundColor: 'inherit !important',
},
},
'button:first-child:not(:only-child)': {
'@media screen and (max-width: 720px)': {
transform: 'scale(1.3)',
margin: '1em',
'&:hover': {
transform: 'scale(1.2) !important',
},
},
transform: 'scale(1.3)',
margin: '1em',
minWidth: 0,
padding: 5,
transition: 'transform .3s ease',
background: colors.pink[500],
color: `${colors.black} !important`,
borderRadius: 500,
border: 0,
'&:hover': {
transform: 'scale(1.2)',
backgroundColor: `${colors.pink[500]} !important`,
border: 0,
},
},
'button:only-child': {
marginTop: '0.3em',
},
'button:first-child>span:first-child': {
padding: 0,
color: `${colors.black} !important`,
},
'button:first-child>span:first-child>span': {
display: 'none',
},
'button>span:first-child>span, button:not(:first-child)>span:first-child>svg':
{
color: colors.gray[100],
},
},
}
/**
* Squiddies Glass theme configuration object.
* Defines the complete theme structure including typography, palette, component overrides, and player settings.
* @type {Object}
*/
export default {
/**
* The name of the theme.
* @type {string}
*/
themeName: 'Squiddies Glass',
/**
* Typography settings for the theme.
* Specifies font family and heading sizes.
* @type {Object}
*/
typography: {
fontFamily: "system-ui, 'Helvetica Neue', Helvetica, Arial, sans-serif",
h6: {
fontSize: '1rem', // AppBar title
},
},
/**
* Color palette configuration.
* Defines primary, secondary, and background colors for the theme.
* @type {Object}
*/
palette: {
primary: {
light: colors.pink[300],
main: colors.pink[500],
},
secondary: {
main: colors.white,
contrastText: colors.white,
},
background: {
default: colors.dark,
paper: colors.dark,
},
type: 'dark',
},
/**
* Component overrides for Material-UI and custom Navidrome components.
* Customizes the appearance and behavior of various UI components.
* @type {Object}
*/
overrides: {
// Material-UI Components
MuiAppBar: {
positionFixed: {
backgroundColor: `${colors.black} !important`,
boxShadow: 'none',
},
},
MuiButton: {
root: {
background: colors.pink[500],
color: colors.white,
border: '1px solid transparent',
borderRadius: 500,
'&:hover': {
background: `${colors.pink[900]} !important`,
},
},
textSecondary: {
border: `1px solid ${colors.gray[100]}`,
background: colors.black,
'&:hover': {
border: `1px solid ${colors.white} !important`,
background: `${colors.black} !important`,
},
},
label: {
color: colors.white,
paddingRight: '1rem',
paddingLeft: '0.7rem',
},
},
MuiCardMedia: {
root: {
position: 'relative',
overflow: 'hidden',
boxShadow: `0 2px 32px rgba(0,0,0,0.5), 0px 1px 5px rgba(0,0,0,0.1)`,
},
},
MuiDivider: {
root: {
margin: '.75rem 0',
},
},
MuiDrawer: {
root: {
background: colors.gray[500],
paddingTop: '10px',
},
},
MuiFormGroup: {
root: {
color: colors.pink[500],
},
},
MuiMenuItem: {
root: {
fontSize: '0.875rem',
},
},
MuiTableCell: {
root: {
borderBottom: `1px solid ${colors.gray[300]}`,
padding: '10px !important',
color: `${colors.gray[100]} !important`,
'& img': {
filter:
'brightness(0) saturate(100%) invert(36%) sepia(93%) saturate(7463%) hue-rotate(289deg) brightness(95%) contrast(102%);',
},
'& img + span': {
color: colors.pink[500],
},
},
head: {
borderBottom: `1px solid ${colors.gray[200]}`,
fontSize: '0.75rem',
textTransform: 'uppercase',
letterSpacing: 1.2,
},
},
MuiTableRow: {
root: {
padding: '10px 0',
transition: 'background-color .3s ease',
'&:hover': {
backgroundColor: `${colors.gray[300]} !important`,
},
'@global': {
'td:nth-child(4)': {
color: `${colors.white} !important`,
},
},
},
},
// React Admin Components
RaBulkActionsToolbar: {
topToolbar: {
gap: '8px',
},
},
RaFilter: {
form: {
'& .MuiOutlinedInput-input:-webkit-autofill': {
'-webkit-box-shadow': `0 0 0 100px ${colors.gray[50]} inset`,
'-webkit-text-fill-color': colors.white,
},
},
},
RaFilterButton: {
root: {
marginRight: '1rem',
},
},
RaLayout: {
content: {
padding: '0 !important',
background: `linear-gradient(${colors.dark}, ${colors.gray[500]})`,
borderTopRightRadius: '8px',
borderTopLeftRadius: '8px',
},
contentWithSidebar: {
gap: '2px',
},
},
RaList: {
content: {
backgroundColor: 'inherit',
},
bulkActionsDisplayed: {
marginTop: '-20px',
},
},
RaListToolbar: {
toolbar: {
padding: '0 .55rem !important',
},
},
RaPaginationActions: {
currentPageButton: {
border: `1px solid ${colors.gray[100]}`,
},
button: {
backgroundColor: 'inherit',
minWidth: 48,
margin: '0 4px',
border: `1px solid ${colors.gray[200]}`,
'@global': {
'> .MuiButton-label': {
padding: 0,
},
},
},
actions: {
'@global': {
'.next-page': {
marginLeft: 8,
marginRight: 8,
},
'.previous-page': {
marginRight: 8,
},
},
},
},
RaSearchInput: {
input: {
paddingLeft: '.9rem',
border: 0,
'& .MuiInputBase-root': {
backgroundColor: `${colors.white} !important`,
borderRadius: '20px !important',
color: colors.black,
border: '0px',
'& fieldset': {
borderColor: colors.white,
},
'&:hover fieldset': {
borderColor: colors.white,
},
'&.Mui-focused fieldset': {
borderColor: colors.white,
},
'& svg': {
color: `${colors.black} !important`,
},
'& .MuiOutlinedInput-input:-webkit-autofill': {
borderRadius: '20px 0px 0px 20px',
'-webkit-box-shadow': `0 0 0 100px ${colors.gray[50]} inset`,
'-webkit-text-fill-color': colors.black,
},
},
},
},
RaSidebar: {
root: {
height: 'initial',
borderTopRightRadius: '8px',
borderTopLeftRadius: '8px',
},
},
// Navidrome Custom Components
NDAlbumDetails: {
root: {
boxShadow: 'none',
background: `linear-gradient(45deg, ${colors.purple[500]}, ${colors.purple[400]}, ${colors.purple[600]})`,
backgroundSize: '200% 200%',
animation: 'gradientFlow 8s ease-in-out infinite',
position: 'relative',
'&:before': {
content: '""',
position: 'absolute',
top: '0',
left: '0',
width: '100%',
height: '100%',
background: `linear-gradient(to bottom, transparent, ${colors.dark})`,
},
},
cardContents: {
alignItems: 'flex-start',
},
coverParent: {
zIndex: '99999',
position: 'relative',
backgroundColor: 'rgba(0, 0, 0, 0.5)',
'&::before': {
content: '""',
position: 'absolute',
inset: '0',
width: '100%',
height: '100%',
borderRadius: '50%',
animation: 'pulse 1.5s ease-in-out infinite alternate',
zIndex: -1,
},
'&::after': {
content: '""',
position: 'absolute',
inset: '0',
zIndex: '-1',
borderRadius: '50%',
background:
'repeating-conic-gradient(from 0deg, rgba(255,255,255,0.08) 0deg, rgba(255,255,255,0.08) 0.5deg, rgba(0,0,0,1) 1deg)',
filter: 'contrast(999) sepia(1)',
boxShadow:
'inset 0 0 25px rgba(255,255,255,0.05), inset 0 0 95px rgba(0,0,0,0.9)',
animation: 'spin 6s linear infinite',
},
},
details: {
zIndex: '99999',
},
recordName: {
fontSize: 'calc(1rem + 1.5vw)',
fontWeight: 900,
},
recordArtist: {
fontSize: '1.5rem',
fontWeight: 700,
textShadow: '0 2px 16px rgba(0, 0, 0, 0.3)',
},
recordMeta: {
fontSize: '.875rem',
color: `rgba(${colors.white}, 0.8)`,
},
content: {
paddingBottom: '0px !important',
paddingTop: '0px',
},
},
RaSingleFieldList: {
root: {
'& a:first-of-type > .MuiChip-root': {
marginLeft: '0px',
},
'& a > .MuiChip-root': {
backgroundColor: colors.pink[500],
fontSize: '0.6rem',
height: '20px',
'& .MuiChip-label': {
color: colors.white,
paddingLeft: '5px',
paddingRight: '5px',
},
},
},
},
MuiGridListTile: {
tile: {
'&:hover': {
boxShadow: '0 2px 32px rgba(0,0,0,0.5), 0px 1px 5px rgba(0,0,0,0.1)',
},
},
},
NDAlbumGridView: {
tileBar: {
background:
'linear-gradient(to top, rgba(0, 0, 0, 0.7) 0%, rgba(0, 0, 0, 0.4) 50%, rgba(0, 0, 0, 0) 100%)',
marginBottom: '2px',
},
albumName: {
marginTop: '0.5rem',
fontWeight: 700,
textTransform: 'none',
color: colors.white,
},
albumSubtitle: {
color: colors.gray[100],
},
albumContainer: {
backgroundColor: colors.gray[400],
borderRadius: '.5rem',
padding: '.75rem',
transition: 'background-color .3s ease',
'&:hover': {
backgroundColor: colors.gray[200],
},
},
albumPlayButton: {
color: colors.black,
backgroundColor: colors.pink[500],
borderRadius: '50%',
boxShadow: '0 8px 8px rgb(0 0 0 / 30%)',
padding: '0.35rem',
transition: 'padding .3s ease',
'&:hover': {
background: `${colors.pink[500]} !important`,
padding: '0.45rem',
},
},
},
NDAlbumShow: {
albumActions: musicListActions,
},
NDArtistShow: {
actions: {
padding: '2rem 0',
alignItems: 'center',
overflow: 'visible',
minHeight: '120px',
'@global': {
button: {
border: '1px solid transparent',
backgroundColor: 'inherit',
color: colors.gray[100],
margin: '0 0.5rem',
'&:hover': {
border: `1px solid ${colors.gray[100]}`,
backgroundColor: 'inherit !important',
},
},
// Hide shuffle button label (first button)
'button:first-child>span:first-child>span': {
display: 'none',
},
// Style shuffle button (first button)
'button:first-child': {
'@media screen and (max-width: 720px)': {
transform: 'scale(1.5)',
margin: '1rem',
'&:hover': {
transform: 'scale(1.6) !important',
},
},
transform: 'scale(2)',
margin: '1.5rem',
minWidth: 0,
padding: 5,
transition: 'transform .3s ease',
background: colors.pink[500],
color: colors.white,
borderRadius: 500,
border: 0,
'&:hover': {
transform: 'scale(2.1)',
backgroundColor: `${colors.pink[500]} !important`,
border: 0,
},
},
'button:first-child>span:first-child': {
padding: 0,
color: `${colors.black} !important`,
},
'button>span:first-child>span, button:not(:first-child)>span:first-child>svg':
{
color: colors.gray[100],
},
},
},
actionsContainer: {
overflow: 'visible',
},
},
NDAudioPlayer: {
audioTitle: {
color: colors.white,
fontSize: '1.5rem',
'& span:nth-child(3)': {
fontSize: '0.8rem',
},
},
songTitle: {
fontWeight: 900,
},
songInfo: {
fontSize: '0.9rem',
color: colors.gray[100],
},
},
NDCollapsibleComment: {
commentBlock: {
fontSize: '.875rem',
color: `rgba(${colors.white}, 0.8)`,
},
},
NDLogin: {
main: {
boxShadow: `inset 0 0 0 2000px rgba(${colors.black}, .75)`,
},
systemNameLink: {
color: colors.white,
},
card: {
border: `1px solid ${colors.gray[200]}`,
},
avatar: {
marginBottom: 0,
},
},
NDPlaylistDetails: {
container: {
background: `linear-gradient(${colors.gray[300]}, transparent)`,
borderRadius: 0,
paddingTop: '2.5rem !important',
boxShadow: 'none',
},
title: {
fontSize: 'calc(1.5rem + 1.5vw)',
fontWeight: 700,
color: colors.white,
},
details: {
fontSize: '.875rem',
color: `rgba(${colors.white}, 0.8)`,
},
},
NDPlaylistShow: {
playlistActions: musicListActions,
},
},
/**
* Player configuration settings.
* Specifies the player theme and associated stylesheet.
* @type {Object}
*/
player: {
theme: 'dark',
stylesheet,
},
}

View File

@ -0,0 +1,89 @@
const stylesheet = `
.react-jinke-music-player-main svg:active, .react-jinke-music-player-main svg:hover {
color: #D60017
}
.react-jinke-music-player-main .music-player-panel .panel-content .rc-slider-handle,
.react-jinke-music-player-main .music-player-panel .panel-content .rc-slider-track {
background-color: #ff4e6b
}
.react-jinke-music-player-main ::-webkit-scrollbar-thumb,
.react-jinke-music-player-mobile-progress .rc-slider-handle,
.react-jinke-music-player-mobile-progress .rc-slider-track {
background-color: #ff4e6b
}
.react-jinke-music-player-main .music-player-panel .panel-content .rc-slider-handle:active {
box-shadow: 0 0 2px #ff4e6b
}
.audio-lists-panel-content .audio-item.playing,
.react-jinke-music-player-main .audio-item.playing svg,
.react-jinke-music-player-main .group player-delete {
color: #ff4e6b
}
.audio-lists-panel-content .audio-item:hover,
.audio-lists-panel-content .audio-item:hover svg
.audio-lists-panel-content .audio-item:active .group:not([class=".player-delete"]) svg, .audio-lists-panel-content .audio-item:hover .group:not([class=".player-delete"]) svg{
color: #D60017
}
.react-jinke-music-player-main .audio-item.playing .player-singer {
color: #ff4e6b !important
}
.react-jinke-music-player-main .lyric-btn,
.react-jinke-music-player-main .lyric-btn-active svg{
color: #ff4e6b !important
}
.react-jinke-music-player-main .lyric-btn-active {
color: #D60017 !important
}
.react-jinke-music-player-main .loading svg {
color: #ff4e6b !important
}
.react-jinke-music-player .music-player-controller .music-player-controller-setting{
background: #ff4e6b4d
}
.react-jinke-music-player-main .music-player-lyric{
color: #ff4e6b !important;
text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000
}
.react-jinke-music-player-main .music-player-panel,
.react-jinke-music-player-mobile,
.ril__outer{
background-color: #1f1f1f;
border: 1px solid #fff1;
}
.ril__toolbar{
background-color: #1d1d1d
}
.ril__toolbarItem{
font-size: 100%;
color: #eee
}
.audio-lists-panel{
background-color: #1f1f1f;
border: 1px solid #fff1;
border-radius: 6px 6px 0 0;
}
.react-jinke-music-player-main .music-player-panel .panel-content .img-rotate,
.react-jinke-music-player-mobile .react-jinke-music-player-mobile-cover img.cover,
.react-jinke-music-player-mobile-cover {
border-radius: 6px !important;
animation-duration: 0s !important
}
.react-jinke-music-player-main .music-player-panel .panel-content .img-content{
width: 60px;
height: 60px
}
.react-jinke-music-player-main .songTitle{
color: #eee
}
.react-jinke-music-player .music-player-controller{
color: #ff4e6b
}
.audio-lists-panel-mobile .audio-item:not(.audio-lists-panel-sortable-highlight-bg){
background: unset
}
.lastfm-icon,
.musicbrainz-icon{
color: #eee
}
`
export default stylesheet

197
ui/src/themes/amusic.js Normal file
View File

@ -0,0 +1,197 @@
import stylesheet from './amusic.css.js'
export default {
themeName: 'AMusic',
typography: {
fontFamily:
'-apple-system, BlinkMacSystemFont, Apple Color Emoji, SF Pro, SF Pro Icons, Helvetica Neue, Helvetica, Arial, sans-serif',
h6: {
fontSize: '1rem', // AppBar title
},
h5: {
fontSize: '2em',
fontWeight: '600',
},
},
palette: {
primary: {
main: '#ff4e6b',
},
secondary: {
main: '#D60017',
contrastText: '#eee',
},
background: {
default: '#1a1a1a',
paper: '#1a1a1a',
},
type: 'dark',
},
overrides: {
MuiFormGroup: {
root: {
color: 'white',
},
},
MuiAppBar: {
positionFixed: {
backgroundColor: '#1d1d1d !important',
boxShadow: 'none',
borderBottom: '1px solid #fff1',
},
colorSecondary: {
color: '#eee',
},
},
MuiDrawer: {
root: {
background: '#1d1d1d',
borderRight: '1px solid #fff1',
},
},
MuiToolbar: {
root: {
background: 'transparent !important',
},
},
MuiCardMedia: {
img: {
borderRadius: '10px',
boxShadow: '5px 5px 20px #111',
},
},
MuiButton: {
root: {
background: '#D60017',
color: '#fff',
borderRadius: '6px',
paddingRight: '0.5rem',
paddingLeft: '0.5rem',
marginLeft: '0.5rem',
marginBottom: '0.5rem',
textTransform: 'capitalize',
fontWeight: 600,
},
textPrimary: {
color: '#eee',
},
textSecondary: {
color: '#eee',
backgroundColor: '#ff4e6b',
},
textSizeSmall: {
fontSize: '0.8rem',
paddingRight: '0.5rem',
paddingLeft: '0.5rem',
},
label: {
paddingRight: '1rem',
paddingLeft: '0.7rem',
},
},
MuiListItemIcon: {
root: {
color: '#ff4e6b',
},
},
MuiChip: {
root: {
borderRadius: '6px',
},
},
MuiIconButton: {
root: {
color: '#ff4e6b',
},
},
MuiTableBody: {
root: {
'&>tr:nth-child(odd)': {
background: 'rgba(255, 255, 255, 0.025)',
},
},
},
MuiTableRow: {
root: {
background: 'transparent',
},
},
MuiTableCell: {
root: {
borderBottom: '0 none !important',
padding: '10px !important',
color: '#b3b3b3 !important',
},
head: {
color: '#b3b3b3 !important',
},
},
MuiMenuItem: {
root: {
fontSize: '0.875rem',
borderRadius: '10px',
color: '#eee',
},
},
NDAlbumGridView: {
albumName: {
color: '#eee',
},
albumSubtitle: {
color: '#ccc',
},
albumPlayButton: {
color: '#ff4e6b !important',
},
albumArtistName: {
color: '#ff4e6b !important',
},
cover: {
borderRadius: '10px !important',
},
},
NDLogin: {
systemNameLink: {
color: '#D60017',
},
welcome: {
color: '#eee',
},
card: {
minWidth: 300,
backgroundColor: '#1d1d1d',
},
},
MuiPaper: {
elevation1: {
boxShadow: 'none',
},
root: {
color: '#eee',
},
},
NDMobileArtistDetails: {
bgContainer: {
background: '#1a1a1a',
},
artistName: {
fontWeight: '600',
fontSize: '2em',
},
},
NDDesktopArtistDetails: {
artistName: {
fontWeight: '600',
fontSize: '2em',
},
artistDetail: {
padding: 'unset',
paddingBottom: '1rem',
},
},
},
player: {
theme: 'dark',
stylesheet,
},
}

View File

@ -10,6 +10,8 @@ import NordTheme from './nord'
import GruvboxDarkTheme from './gruvboxDark'
import CatppuccinMacchiatoTheme from './catppuccinMacchiato'
import NuclearTheme from './nuclear'
import AmusicTheme from './amusic'
import SquiddiesGlassTheme from './SquiddiesGlass'
export default {
// Classic default themes
@ -17,6 +19,7 @@ export default {
DarkTheme,
// New themes should be added here, in alphabetic order
AmusicTheme,
CatppuccinMacchiatoTheme,
ElectricPurpleTheme,
ExtraDarkTheme,
@ -27,4 +30,5 @@ export default {
NordTheme,
NuclearTheme,
SpotifyTheme,
SquiddiesGlassTheme,
}