Merge branch 'navidrome:master' into master

This commit is contained in:
Sora 2026-01-21 08:04:50 +08:00 committed by GitHub
commit db4e338941
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 972 additions and 47 deletions

6
go.mod
View File

@ -59,14 +59,14 @@ require (
github.com/robfig/cron/v3 v3.0.1
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
github.com/sirupsen/logrus v1.9.3
github.com/sirupsen/logrus v1.9.4
github.com/spf13/cobra v1.10.2
github.com/spf13/viper v1.21.0
github.com/stretchr/testify v1.11.1
github.com/tetratelabs/wazero v1.11.0
github.com/unrolled/secure v1.17.0
github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342
go.senan.xyz/taglib v0.0.0-00010101000000-000000000000
go.senan.xyz/taglib v0.11.1
go.uber.org/goleak v1.3.0
golang.org/x/image v0.35.0
golang.org/x/net v0.49.0
@ -98,7 +98,7 @@ require (
github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/pprof v0.0.0-20260111202518-71be6bfdd440 // indirect
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect
github.com/google/subcommands v1.2.0 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect

10
go.sum
View File

@ -110,8 +110,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-pipeline v0.0.0-20230411140531-6cbedfc1d3fc h1:hd+uUVsB1vdxohPneMrhGH2YfQuH5hRIK9u4/XCeUtw=
github.com/google/go-pipeline v0.0.0-20230411140531-6cbedfc1d3fc/go.mod h1:SL66SJVysrh7YbDCP9tH30b8a9o/N2HeiQNUm85EKhc=
github.com/google/pprof v0.0.0-20260111202518-71be6bfdd440 h1:oKBqR+eQXiIM7X8K1JEg9aoTEePLq/c6Awe484abOuA=
github.com/google/pprof v0.0.0-20260111202518-71be6bfdd440/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc=
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
@ -245,8 +245,8 @@ github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr
github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE=
github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
@ -275,7 +275,6 @@ github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRci
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
@ -364,7 +363,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View File

@ -4,10 +4,10 @@ go 1.25
require (
github.com/extism/go-pdk v1.1.3
github.com/onsi/ginkgo/v2 v2.27.3
github.com/onsi/gomega v1.38.3
github.com/onsi/ginkgo/v2 v2.27.5
github.com/onsi/gomega v1.39.0
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
golang.org/x/tools v0.40.0
golang.org/x/tools v0.41.0
gopkg.in/yaml.v3 v3.0.1
)
@ -16,11 +16,11 @@ require (
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/mod v0.31.0 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/mod v0.32.0 // indirect
golang.org/x/net v0.49.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/text v0.32.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/text v0.33.0 // indirect
)

View File

@ -20,8 +20,8 @@ github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc=
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE=
github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@ -32,10 +32,10 @@ github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=
github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=
github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE=
github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A=
github.com/onsi/ginkgo/v2 v2.27.3 h1:ICsZJ8JoYafeXFFlFAG75a7CxMsJHwgKwtO+82SE9L8=
github.com/onsi/ginkgo/v2 v2.27.3/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
github.com/onsi/gomega v1.38.3 h1:eTX+W6dobAYfFeGC2PV6RwXRu/MyT+cQguijutvkpSM=
github.com/onsi/gomega v1.38.3/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4=
github.com/onsi/ginkgo/v2 v2.27.5 h1:ZeVgZMx2PDMdJm/+w5fE/OyG6ILo1Y3e+QX4zSR0zTE=
github.com/onsi/ginkgo/v2 v2.27.5/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q=
github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
@ -54,18 +54,18 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

Binary file not shown.

View File

@ -228,7 +228,7 @@ const AlbumDetails = (props) => {
let notes =
albumInfo?.notes?.replace(new RegExp('<.*>', 'g'), '') || record.notes
if (notes !== undefined) {
if (notes) {
notes += '..'
}
@ -340,7 +340,7 @@ const AlbumDetails = (props) => {
)}
</Typography>
)}
{isDesktop && (
{isDesktop && notes && (
<Collapse
collapsedHeight={'2.75em'}
in={expanded}
@ -364,7 +364,7 @@ const AlbumDetails = (props) => {
{!isDesktop && record['comment'] && (
<CollapsibleComment record={record} />
)}
{!isDesktop && (
{!isDesktop && notes && (
<div className={classes.notes}>
<Collapse collapsedHeight={'1.5em'} in={expanded} timeout={'auto'}>
<Typography

View File

@ -4,18 +4,26 @@ import FavoriteIcon from '@material-ui/icons/Favorite'
import FavoriteBorderIcon from '@material-ui/icons/FavoriteBorder'
import IconButton from '@material-ui/core/IconButton'
import { makeStyles } from '@material-ui/core/styles'
import clsx from 'clsx'
import { useToggleLove } from './useToggleLove'
import { useRecordContext } from 'react-admin'
import config from '../config'
import { isDateSet } from '../utils/validations'
const useStyles = makeStyles({
const useStyles = makeStyles(
{
love: {
color: (props) => props.color,
visibility: (props) =>
props.visible === false ? 'hidden' : props.loved ? 'visible' : 'inherit',
props.visible === false
? 'hidden'
: props.loved
? 'visible'
: 'inherit',
},
})
},
{ name: 'NDLoveButton' },
)
export const LoveButton = ({
resource,
@ -25,9 +33,11 @@ export const LoveButton = ({
component: Button,
addLabel,
disabled,
className,
record: recordProp,
...rest
}) => {
const record = useRecordContext(rest) || {}
const record = useRecordContext({ record: recordProp }) || {}
const classes = useStyles({ color, visible, loved: record.starred })
const [toggleLove, loading] = useToggleLove(resource, record)
@ -48,7 +58,7 @@ export const LoveButton = ({
onClick={handleToggleLove}
size={'small'}
disabled={disabled || loading || record.missing}
className={classes.love}
className={clsx(classes.love, className)}
title={
isDateSet(record.starredAt)
? new Date(record.starredAt).toLocaleString()

View File

@ -28,6 +28,9 @@ import { useDispatch } from 'react-redux'
const useStyles = makeStyles((theme) => ({
user: {},
button: {
color: 'inherit',
},
avatar: {
width: theme.spacing(4),
height: theme.spacing(4),
@ -72,12 +75,11 @@ const UserMenu = (props) => {
<div className={classes.user}>
<Tooltip title={label && translate(label, { _: label })}>
<IconButton
className={classes.button}
aria-label={label && translate(label, { _: label })}
aria-owns={open ? 'menu-appbar' : null}
aria-haspopup={true}
color="inherit"
onClick={handleMenu}
size={'small'}
>
{loaded && identity.avatar ? (
<Avatar

View File

@ -34,7 +34,15 @@ const PlaylistEditForm = (props) => {
return (
<SimpleForm redirect="list" variant={'outlined'} {...props}>
<TextInput source="name" validate={required()} />
<TextInput multiline source="comment" />
<TextInput
multiline
minRows={3}
source="comment"
fullWidth
inputProps={{
style: { resize: 'vertical' },
}}
/>
{permissions === 'admin' ? (
<ReferenceInput
source="ownerId"

View File

@ -12,6 +12,7 @@ import CatppuccinMacchiatoTheme from './catppuccinMacchiato'
import NuclearTheme from './nuclear'
import AmusicTheme from './amusic'
import SquiddiesGlassTheme from './SquiddiesGlass'
import NautilineTheme from './nautiline'
export default {
// Classic default themes
@ -27,6 +28,7 @@ export default {
GruvboxDarkTheme,
LigeraTheme,
MonokaiTheme,
NautilineTheme,
NordTheme,
NuclearTheme,
SpotifyTheme,

905
ui/src/themes/nautiline.js Normal file
View File

@ -0,0 +1,905 @@
/**
* Nautiline Theme for Navidrome
* Light theme inspired by the Nautiline iOS app
*/
// ============================================
// CONFIGURATION
// ============================================
const ACCENT_COLOR = '#009688' // Material teal
// ============================================
// DESIGN TOKENS
// ============================================
const hexToRgb = (hex) => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
return result
? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
}
: null
}
const rgb = hexToRgb(ACCENT_COLOR)
const rgba = (alpha) =>
rgb ? `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha})` : 'transparent'
const tokens = {
colors: {
accent: {
main: ACCENT_COLOR,
faded: rgba(0.1),
hover: rgba(0.15),
},
background: {
primary: '#FFFFFF',
secondary: '#F5F5F7',
tertiary: '#E5E5EA',
},
text: {
primary: '#1A1A1A',
secondary: '#8E8E93',
tertiary: '#AEAEB2',
},
ui: {
separator: 'rgba(0, 0, 0, 0.08)',
shadow: 'rgba(0, 0, 0, 0.04)',
glassBg: 'rgba(255, 255, 255, 0.72)',
},
},
typography: {
fontFamily: {
base: [
'-apple-system',
'BlinkMacSystemFont',
'"SF Pro Text"',
'"Helvetica Neue"',
'Arial',
'sans-serif',
].join(','),
heading: '"Unbounded", sans-serif',
},
fontFace: `
@font-face {
font-family: 'Unbounded';
font-style: normal;
font-weight: 300 800;
font-display: swap;
src: url('/fonts/Unbounded-Variable.woff2') format('woff2');
}
`,
},
spacing: {
xs: '0.25rem',
sm: '0.5rem',
md: '0.75rem',
lg: '1rem',
xl: '1.5rem',
},
radii: {
sm: '0.25rem',
md: '0.5rem',
lg: '0.625rem',
xl: '0.75rem',
full: '50%',
pill: '1rem',
},
breakpoints: {
xs: 599,
sm: 600,
md: 720,
lg: 1280,
},
sizing: {
cover: {
sm: '14em',
lg: '18em',
},
icon: '1.25rem',
iconMinWidth: '2.5rem',
},
blur: '1.25rem',
}
const { colors, typography, spacing, radii, sizing, breakpoints } = tokens
// ============================================
// REUSABLE STYLE FACTORIES
// ============================================
const headingStyle = (weight, letterSpacing) => ({
fontFamily: typography.fontFamily.heading,
fontWeight: weight,
...(letterSpacing && { letterSpacing }),
})
const coverSizing = () => ({
[`@media (min-width: ${breakpoints.sm}px)`]: {
height: sizing.cover.sm,
width: sizing.cover.sm,
minWidth: sizing.cover.sm,
},
[`@media (min-width: ${breakpoints.lg}px)`]: {
height: sizing.cover.lg,
width: sizing.cover.lg,
minWidth: sizing.cover.lg,
},
})
const customTooltipStyle = () => ({
display: 'inline',
position: 'absolute',
bottom: '100%',
left: '50%',
transform: 'translateX(-50%)',
marginBottom: spacing.xs,
fontSize: '0.75rem',
whiteSpace: 'nowrap',
backgroundColor: colors.text.primary,
color: colors.background.primary,
padding: `${spacing.xs} ${spacing.sm}`,
borderRadius: radii.sm,
zIndex: 9999,
})
const actionButtonsStyle = () => ({
padding: `${spacing.lg} 0`,
alignItems: 'center',
'@global': {
button: {
border: '1px solid transparent',
backgroundColor: colors.background.secondary,
color: colors.text.secondary,
margin: `0 ${spacing.sm}`,
borderRadius: radii.full,
minWidth: 0,
padding: spacing.lg,
position: 'relative',
'&:hover': {
backgroundColor: `${colors.background.tertiary} !important`,
border: '1px solid transparent',
},
},
'button:first-child:not(:only-child)': {
[`@media screen and (max-width: ${breakpoints.md}px)`]: {
transform: 'scale(1.5)',
margin: spacing.lg,
'&:hover': {
transform: 'scale(1.6) !important',
},
},
transform: 'scale(2)',
margin: spacing.xl,
minWidth: 0,
padding: '0.3125rem',
transition: 'transform .3s ease',
background: colors.accent.main,
color: '#fff',
borderRadius: radii.full,
border: 0,
'&:hover': {
transform: 'scale(2.1)',
backgroundColor: `${colors.accent.main} !important`,
border: 0,
},
},
'button:only-child': {
margin: spacing.xl,
},
'button:first-child>span:first-child': {
padding: 0,
},
'button>span:first-child>span': {
display: 'none',
},
'button:not(:first-child):hover>span:first-child>span':
customTooltipStyle(),
'button:not(:first-child)>span:first-child>svg': {
color: colors.text.secondary,
},
},
})
const menuIconStyle = () => ({
color: colors.text.primary,
minWidth: sizing.iconMinWidth,
'& svg': {
fontSize: sizing.icon,
},
})
const activeLinkStyle = {
color: `${colors.accent.main} !important`,
'& .MuiListItemIcon-root': {
color: `${colors.accent.main} !important`,
},
}
// ============================================
// THEME DEFINITION
// ============================================
// Note: !important declarations are required to override react-admin and third-party component styles
const NautilineTheme = {
themeName: 'Nautiline',
palette: {
type: 'light',
primary: {
main: colors.accent.main,
contrastText: '#FFFFFF',
},
secondary: {
main: colors.accent.main,
contrastText: '#FFFFFF',
},
background: {
default: colors.background.primary,
paper: colors.background.primary,
},
text: {
primary: colors.text.primary,
secondary: colors.text.secondary,
},
action: {
active: colors.accent.main,
hover: colors.accent.faded,
selected: colors.accent.faded,
},
},
typography: {
fontFamily: typography.fontFamily.base,
h1: headingStyle(700, '-0.02em'),
h2: headingStyle(700, '-0.02em'),
h3: headingStyle(600, '-0.01em'),
h4: headingStyle(600),
h5: headingStyle(600),
h6: headingStyle(600),
subtitle1: { fontWeight: 500 },
subtitle2: { fontWeight: 500 },
body1: { fontWeight: 400 },
body2: { fontWeight: 400 },
button: { fontWeight: 500, textTransform: 'none' },
},
shape: {
borderRadius: radii.xl,
},
overrides: {
MuiCssBaseline: {
'@global': {
'@font-face': {
fontFamily: 'Unbounded',
fontStyle: 'normal',
fontWeight: '300 800',
fontDisplay: 'swap',
src: "url('/fonts/Unbounded-Variable.woff2') format('woff2')",
},
body: {
backgroundColor: colors.background.primary,
},
},
},
MuiAppBar: {
root: {
boxShadow: 'none',
borderBottom: `1px solid ${colors.ui.separator}`,
},
colorSecondary: {
backgroundColor: colors.background.primary,
color: colors.text.primary,
},
},
MuiToolbar: {
root: {
backgroundColor: colors.background.primary,
},
},
MuiPaper: {
root: {
backgroundColor: colors.background.primary,
},
elevation1: {
boxShadow: `0 0.0625rem 0.1875rem ${colors.ui.shadow}`,
},
elevation2: {
boxShadow: `0 0.125rem ${spacing.sm} ${colors.ui.shadow}`,
},
},
MuiCard: {
root: {
backgroundColor: colors.background.primary,
borderRadius: radii.xl,
boxShadow: `0 0.125rem ${spacing.sm} ${colors.ui.shadow}`,
},
},
MuiButton: {
root: {
borderRadius: radii.md,
textTransform: 'none',
fontWeight: 600,
},
contained: {
boxShadow: 'none',
'&:hover': { boxShadow: 'none' },
},
containedPrimary: {
backgroundColor: colors.accent.main,
'&:hover': {
backgroundColor: colors.accent.main,
filter: 'brightness(0.9)',
},
},
text: {
color: colors.accent.main,
},
},
MuiIconButton: {
root: {
color: colors.text.primary,
'&:hover': {
backgroundColor: colors.accent.faded,
},
},
colorPrimary: {
color: colors.accent.main,
},
sizeSmall: {
padding: spacing.md,
},
},
MuiSvgIcon: {
colorPrimary: {
color: colors.accent.main,
},
},
MuiCheckbox: {
root: {
color: 'rgba(0, 0, 0, 0.15)',
'&$checked': {
color: colors.accent.main,
},
},
},
MuiChip: {
root: {
backgroundColor: colors.background.secondary,
color: colors.text.primary,
borderRadius: radii.pill,
},
colorPrimary: {
backgroundColor: colors.accent.faded,
color: colors.accent.main,
},
},
MuiTableRow: {
root: {
'&:hover': {
backgroundColor: `${colors.accent.faded} !important`,
},
},
},
MuiTableCell: {
root: {
borderBottomColor: 'rgba(0, 0, 0, 0.04)',
},
head: {
backgroundColor: colors.background.secondary,
color: colors.text.secondary,
fontWeight: 600,
fontSize: '0.75rem',
textTransform: 'uppercase',
letterSpacing: '0.05em',
},
body: {
color: colors.text.primary,
},
},
MuiListItem: {
root: {
color: colors.text.primary,
'&:hover': {
backgroundColor: colors.accent.faded,
},
'&$selected': {
backgroundColor: colors.accent.faded,
color: colors.accent.main,
'& .MuiListItemIcon-root': {
color: colors.accent.main,
},
'&:hover': {
backgroundColor: colors.accent.faded,
},
},
},
button: {
color: colors.text.primary,
'&:hover': {
backgroundColor: colors.accent.faded,
color: colors.text.primary,
},
},
},
MuiListItemIcon: {
root: menuIconStyle(),
},
MuiListItemText: {
primary: {
color: 'inherit',
},
},
MuiMenuItem: {
root: {
fontSize: '0.875rem',
paddingTop: '4px',
paddingBottom: '4px',
paddingLeft: '10px',
margin: '5px',
borderRadius: radii.md,
color: colors.text.primary,
},
},
MuiDrawer: {
paper: {
backgroundColor: colors.background.primary,
borderRight: `1px solid ${colors.ui.separator}`,
},
},
MuiSlider: {
root: {
color: colors.accent.main,
},
track: {
backgroundColor: colors.accent.main,
},
thumb: {
backgroundColor: colors.accent.main,
'&:hover': {
boxShadow: `0 0 0 ${spacing.sm} ${colors.accent.faded}`,
},
},
rail: {
backgroundColor: colors.background.tertiary,
},
},
MuiLinearProgress: {
root: {
backgroundColor: colors.background.tertiary,
borderRadius: radii.sm,
},
bar: {
backgroundColor: colors.accent.main,
borderRadius: radii.sm,
},
},
MuiTabs: {
root: {
borderBottom: `1px solid ${colors.ui.separator}`,
},
indicator: {
backgroundColor: colors.accent.main,
height: '0.1875rem',
borderRadius: '0.1875rem 0.1875rem 0 0',
},
},
MuiTab: {
root: {
textTransform: 'none',
fontWeight: 500,
fontFamily: typography.fontFamily.heading,
'&$selected': {
color: colors.accent.main,
fontWeight: 600,
},
},
},
MuiInputBase: {
root: {
backgroundColor: colors.background.secondary,
borderRadius: radii.lg,
},
},
MuiOutlinedInput: {
root: {
borderRadius: radii.lg,
'& $notchedOutline': {
borderColor: colors.ui.separator,
},
'&:hover $notchedOutline': {
borderColor: colors.text.tertiary,
},
'&$focused $notchedOutline': {
borderColor: colors.accent.main,
borderWidth: '0.125rem',
},
},
},
MuiFilledInput: {
root: {
backgroundColor: colors.background.secondary,
borderRadius: radii.lg,
'&:hover': {
backgroundColor: colors.background.tertiary,
},
'&$focused': {
backgroundColor: colors.background.secondary,
},
},
},
MuiFab: {
primary: {
backgroundColor: colors.accent.main,
'&:hover': {
backgroundColor: colors.accent.main,
filter: 'brightness(0.9)',
},
},
},
MuiAvatar: {
root: {
borderRadius: radii.md,
},
},
MuiRating: {
iconFilled: {
color: colors.accent.main,
},
iconHover: {
color: colors.accent.main,
},
},
MuiTooltip: {
tooltip: {
backgroundColor: colors.text.primary,
color: colors.background.primary,
fontSize: '0.75rem',
padding: `${spacing.xs} ${spacing.sm}`,
borderRadius: radii.sm,
},
},
MuiBottomNavigation: {
root: {
backgroundColor: colors.ui.glassBg,
backdropFilter: `blur(${tokens.blur})`,
borderTop: `1px solid ${colors.ui.separator}`,
},
},
MuiBottomNavigationAction: {
root: {
color: colors.text.secondary,
'&$selected': {
color: colors.accent.main,
},
},
label: {
fontFamily: typography.fontFamily.heading,
fontSize: '0.65rem',
'&$selected': {
fontSize: '0.65rem',
},
},
},
NDAppBar: {
root: {
color: colors.text.primary,
},
},
NDLogin: {
main: {
backgroundColor: colors.background.primary,
},
card: {
backgroundColor: colors.background.primary,
borderRadius: radii.pill,
boxShadow: `0 ${spacing.xs} ${spacing.xl} ${colors.ui.shadow}`,
},
},
NDAlbumGridView: {
albumContainer: {
borderRadius: radii.md,
'& img': {
borderRadius: radii.md,
},
},
albumTitle: {
fontWeight: 600,
color: colors.text.primary,
},
albumSubtitle: {
color: colors.text.secondary,
},
albumPlayButton: {
backgroundColor: colors.accent.main,
borderRadius: radii.full,
boxShadow: `0 ${spacing.sm} ${spacing.sm} rgba(0, 0, 0, 0.15)`,
padding: '0.35rem',
transition: 'padding .3s ease',
'&:hover': {
backgroundColor: `${colors.accent.main} !important`,
padding: '0.45rem',
},
},
},
NDAlbumDetails: {
root: {
[`@media (max-width: ${breakpoints.xs}px)`]: {
padding: '0.7em',
width: '100%',
minWidth: 'unset',
},
},
cardContents: {
[`@media (max-width: ${breakpoints.xs}px)`]: {
flexDirection: 'column',
alignItems: 'center',
},
},
details: {
[`@media (max-width: ${breakpoints.xs}px)`]: {
width: '100%',
},
},
cover: {
borderRadius: radii.md,
},
coverParent: {
marginRight: spacing.xl,
[`@media (max-width: ${breakpoints.xs}px)`]: {
width: '100%',
height: 'auto',
minWidth: 'unset',
aspectRatio: '1',
marginRight: 0,
marginBottom: spacing.lg,
},
...coverSizing(),
},
recordName: {
fontSize: '1.75rem',
fontWeight: 700,
marginBottom: '0.15rem',
},
recordArtist: {
marginBottom: spacing.md,
},
recordMeta: {
marginBottom: spacing.sm,
},
genreList: {
marginTop: spacing.md,
},
loveButton: {
marginLeft: spacing.sm,
},
},
NDAlbumShow: {
albumActions: actionButtonsStyle(),
},
NDPlaylistShow: {
playlistActions: actionButtonsStyle(),
},
NDSubMenu: {
icon: menuIconStyle(),
menuHeader: {
color: colors.text.primary,
'& .MuiTypography-root': {
color: colors.text.primary,
},
},
actionIcon: {
marginLeft: spacing.sm,
},
},
RaMenuItemLink: {
root: {
color: `${colors.text.primary} !important`,
'& .MuiListItemIcon-root': menuIconStyle(),
'&[class*="makeStyles-active"]': activeLinkStyle,
},
active: activeLinkStyle,
},
NDDesktopArtistDetails: {
root: {
[`@media (min-width: ${breakpoints.sm}px)`]: {
padding: '1em',
},
[`@media (min-width: ${breakpoints.lg}px)`]: {
padding: '1em',
},
},
cover: {
borderRadius: radii.md,
...coverSizing(),
},
artistImage: {
borderRadius: radii.md,
marginRight: spacing.xl,
[`@media (min-width: ${breakpoints.sm}px)`]: {
height: sizing.cover.sm,
width: sizing.cover.sm,
minWidth: sizing.cover.sm,
maxHeight: sizing.cover.sm,
minHeight: sizing.cover.sm,
},
[`@media (min-width: ${breakpoints.lg}px)`]: {
height: sizing.cover.lg,
width: sizing.cover.lg,
minWidth: sizing.cover.lg,
maxHeight: sizing.cover.lg,
minHeight: sizing.cover.lg,
},
},
artistName: {
fontSize: '1.75rem',
fontWeight: 700,
marginBottom: spacing.sm,
},
},
NDMobileArtistDetails: {
cover: {
borderRadius: radii.md,
},
artistImage: {
borderRadius: radii.md,
},
},
RaList: {
content: {
overflow: 'visible',
},
},
RaBulkActionsToolbar: {
topToolbar: {
backgroundColor: 'transparent',
boxShadow: 'none',
padding: spacing.sm,
'@global': {
button: {
border: '1px solid transparent',
backgroundColor: colors.background.secondary,
color: colors.text.secondary,
margin: `0 ${spacing.xs}`,
borderRadius: radii.full,
minWidth: 0,
padding: spacing.sm,
position: 'relative',
'&:hover': {
backgroundColor: `${colors.background.tertiary} !important`,
border: '1px solid transparent',
},
},
'button>span:first-child>span': {
display: 'none',
},
'button:hover>span:first-child>span': customTooltipStyle(),
'button>span:first-child>svg': {
color: colors.text.secondary,
},
},
},
},
RaPaginationActions: {
currentPageButton: {
backgroundColor: colors.accent.faded,
},
},
},
player: {
theme: 'light',
stylesheet: `
@font-face {
font-family: 'Unbounded';
font-style: normal;
font-weight: 300 800;
font-display: swap;
src: url('/fonts/Unbounded-Variable.woff2') format('woff2');
}
.react-jinke-music-player-main {
background-color: ${colors.background.primary} !important;
font-family: ${typography.fontFamily.base} !important;
}
.react-jinke-music-player-main .music-player-panel {
background-color: ${colors.ui.glassBg} !important;
backdrop-filter: blur(${tokens.blur}) !important;
-webkit-backdrop-filter: blur(${tokens.blur}) !important;
border-top: 1px solid ${colors.ui.separator} !important;
box-shadow: 0 -0.125rem 1.25rem rgba(0, 0, 0, 0.06) !important;
}
.react-jinke-music-player-main svg {
color: ${colors.text.primary} !important;
}
.react-jinke-music-player-main svg:hover {
color: ${colors.accent.main} !important;
}
.react-jinke-music-player-main .rc-slider-track,
.react-jinke-music-player-main .rc-slider-handle {
background-color: ${colors.accent.main} !important;
}
.react-jinke-music-player-main .rc-slider-handle {
border-color: ${colors.accent.main} !important;
}
.react-jinke-music-player-main .rc-slider-rail {
background-color: ${colors.background.secondary} !important;
}
.react-jinke-music-player-main .rc-slider {
height: 4px !important;
}
.react-jinke-music-player-main .rc-slider-rail,
.react-jinke-music-player-main .rc-slider-track {
height: 4px !important;
border-radius: 2px !important;
}
.react-jinke-music-player-main .rc-slider-handle {
width: 12px !important;
height: 12px !important;
margin-top: -4px !important;
}
.react-jinke-music-player-main .audio-lists-panel,
.react-jinke-music-player-main .audio-lists-panel-content {
background-color: ${colors.background.primary} !important;
}
.react-jinke-music-player-main .audio-lists-panel-content .audio-item {
background-color: transparent !important;
color: ${colors.text.primary} !important;
}
.react-jinke-music-player-main .audio-lists-panel-content .audio-item:hover {
background-color: ${colors.accent.faded} !important;
}
.react-jinke-music-player-main .audio-lists-panel-content .audio-item.playing {
background-color: ${colors.accent.faded} !important;
color: ${colors.accent.main} !important;
}
.react-jinke-music-player-main .lyric-btn-active,
.react-jinke-music-player-main .play-mode-title {
color: ${colors.accent.main} !important;
}
.react-jinke-music-player-main .music-player-panel .player-content .music-player-controller .music-player-info .music-player-title {
color: ${colors.text.primary} !important;
font-weight: 600 !important;
font-family: ${typography.fontFamily.heading} !important;
}
.react-jinke-music-player-main .music-player-panel .player-content .music-player-controller .music-player-info .music-player-artist {
color: ${colors.text.secondary} !important;
}
.react-jinke-music-player-main.mini-player {
background-color: ${colors.ui.glassBg} !important;
backdrop-filter: blur(${tokens.blur}) !important;
-webkit-backdrop-filter: blur(${tokens.blur}) !important;
border-radius: ${radii.xl} !important;
box-shadow: 0 ${spacing.xs} 1.25rem rgba(0, 0, 0, 0.08) !important;
}
.MuiTypography-h1,
.MuiTypography-h2,
.MuiTypography-h3,
.MuiTypography-h4,
.MuiTypography-h5,
.MuiTypography-h6 {
font-family: ${typography.fontFamily.heading} !important;
}
`,
},
}
export default NautilineTheme