mirror of
https://github.com/navidrome/navidrome.git
synced 2025-05-15 09:36:38 +03:00
Merge branch 'master' into dlna-spike
This commit is contained in:
commit
6dcabcc35d
@ -178,7 +178,7 @@ join library on media_file.library_id = library.id`, string(os.PathSeparator)))
|
|||||||
f := model.NewFolder(lib, path)
|
f := model.NewFolder(lib, path)
|
||||||
_, err = stmt.ExecContext(ctx, f.ID, lib.ID, f.Path, f.Name, f.ParentID)
|
_, err = stmt.ExecContext(ctx, f.ID, lib.ID, f.Path, f.Name, f.ParentID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error writing folder to DB", "path", path, err)
|
log.Error("error writing folder to DB", "path", path, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
@ -187,7 +187,12 @@ join library on media_file.library_id = library.id`, string(os.PathSeparator)))
|
|||||||
return fmt.Errorf("error populating folder table: %w", err)
|
return fmt.Errorf("error populating folder table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
libPathLen := utf8.RuneCountInString(lib.Path)
|
// Count the number of characters in the library path
|
||||||
|
libPath := filepath.Clean(lib.Path)
|
||||||
|
libPathLen := utf8.RuneCountInString(libPath)
|
||||||
|
|
||||||
|
// In one go, update all paths in the media_file table, removing the library path prefix
|
||||||
|
// and replacing any backslashes with slashes (the path separator used by the io/fs package)
|
||||||
_, err = tx.ExecContext(ctx, fmt.Sprintf(`
|
_, err = tx.ExecContext(ctx, fmt.Sprintf(`
|
||||||
update media_file set path = replace(substr(path, %d), '\', '/');`, libPathLen+2))
|
update media_file set path = replace(substr(path, %d), '\', '/');`, libPathLen+2))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -27,12 +27,12 @@
|
|||||||
"playDate": "Últimas reproducciones",
|
"playDate": "Últimas reproducciones",
|
||||||
"channels": "Canales",
|
"channels": "Canales",
|
||||||
"createdAt": "Creado el",
|
"createdAt": "Creado el",
|
||||||
"grouping": "",
|
"grouping": "Agrupación",
|
||||||
"mood": "",
|
"mood": "",
|
||||||
"participants": "",
|
"participants": "Participantes",
|
||||||
"tags": "",
|
"tags": "Etiquetas",
|
||||||
"mappedTags": "",
|
"mappedTags": "Etiquetas asignadas",
|
||||||
"rawTags": ""
|
"rawTags": "Etiquetas sin procesar"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"addToQueue": "Reproducir después",
|
"addToQueue": "Reproducir después",
|
||||||
@ -65,10 +65,10 @@
|
|||||||
"releaseDate": "Publicado",
|
"releaseDate": "Publicado",
|
||||||
"releases": "Lanzamiento |||| Lanzamientos",
|
"releases": "Lanzamiento |||| Lanzamientos",
|
||||||
"released": "Publicado",
|
"released": "Publicado",
|
||||||
"recordLabel": "",
|
"recordLabel": "Discográfica",
|
||||||
"catalogNum": "",
|
"catalogNum": "Número de catálogo",
|
||||||
"releaseType": "",
|
"releaseType": "Tipo de lanzamiento",
|
||||||
"grouping": "",
|
"grouping": "Agrupación",
|
||||||
"media": "",
|
"media": "",
|
||||||
"mood": ""
|
"mood": ""
|
||||||
},
|
},
|
||||||
@ -76,7 +76,7 @@
|
|||||||
"playAll": "Reproducir",
|
"playAll": "Reproducir",
|
||||||
"playNext": "Reproducir siguiente",
|
"playNext": "Reproducir siguiente",
|
||||||
"addToQueue": "Reproducir después",
|
"addToQueue": "Reproducir después",
|
||||||
"shuffle": "Aletorio",
|
"shuffle": "Aleatorio",
|
||||||
"addToPlaylist": "Agregar a la lista",
|
"addToPlaylist": "Agregar a la lista",
|
||||||
"download": "Descargar",
|
"download": "Descargar",
|
||||||
"info": "Obtener información",
|
"info": "Obtener información",
|
||||||
@ -102,22 +102,22 @@
|
|||||||
"rating": "Calificación",
|
"rating": "Calificación",
|
||||||
"genre": "Género",
|
"genre": "Género",
|
||||||
"size": "Tamaño",
|
"size": "Tamaño",
|
||||||
"role": ""
|
"role": "Rol"
|
||||||
},
|
},
|
||||||
"roles": {
|
"roles": {
|
||||||
"albumartist": "",
|
"albumartist": "Artista del álbum",
|
||||||
"artist": "",
|
"artist": "Artista",
|
||||||
"composer": "",
|
"composer": "Compositor",
|
||||||
"conductor": "",
|
"conductor": "Director de orquesta",
|
||||||
"lyricist": "",
|
"lyricist": "Letrista",
|
||||||
"arranger": "",
|
"arranger": "Arreglista",
|
||||||
"producer": "",
|
"producer": "Productor",
|
||||||
"director": "",
|
"director": "Director",
|
||||||
"engineer": "",
|
"engineer": "Ingeniero de sonido",
|
||||||
"mixer": "",
|
"mixer": "Mezclador",
|
||||||
"remixer": "",
|
"remixer": "Remixer",
|
||||||
"djmixer": "",
|
"djmixer": "DJ Mixer",
|
||||||
"performer": ""
|
"performer": "Intérprete"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
@ -141,7 +141,7 @@
|
|||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"created": "Usuario creado",
|
"created": "Usuario creado",
|
||||||
"updated": "Usuario actulalizado",
|
"updated": "Usuario actualizado",
|
||||||
"deleted": "Usuario eliminado"
|
"deleted": "Usuario eliminado"
|
||||||
},
|
},
|
||||||
"message": {
|
"message": {
|
||||||
@ -228,17 +228,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"missing": {
|
"missing": {
|
||||||
"name": "",
|
"name": "Faltante",
|
||||||
"fields": {
|
"fields": {
|
||||||
"path": "",
|
"path": "Ruta",
|
||||||
"size": "",
|
"size": "Tamaño",
|
||||||
"updatedAt": ""
|
"updatedAt": "Actualizado el"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"remove": ""
|
"remove": "Eliminar"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"removed": ""
|
"removed": "Eliminado"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -413,12 +413,12 @@
|
|||||||
"downloadOriginalFormat": "Descargar formato original",
|
"downloadOriginalFormat": "Descargar formato original",
|
||||||
"shareOriginalFormat": "Compartir formato original",
|
"shareOriginalFormat": "Compartir formato original",
|
||||||
"shareDialogTitle": "Compartir %{resource} '%{name}'",
|
"shareDialogTitle": "Compartir %{resource} '%{name}'",
|
||||||
"shareBatchDialogTitle": "Compartir 1 %{resource} |||| Share %{smart_count} %{resource}",
|
"shareBatchDialogTitle": "Compartir 1 %{resource} |||| Compartir %{smart_count} %{resource}",
|
||||||
"shareSuccess": "URL copiada al portapapeles: %{url}",
|
"shareSuccess": "URL copiada al portapapeles: %{url}",
|
||||||
"shareFailure": "Error al copiar la URL %{url} al portapapeles",
|
"shareFailure": "Error al copiar la URL %{url} al portapapeles",
|
||||||
"downloadDialogTitle": "Descargar %{resource} '%{name}' (%{size})",
|
"downloadDialogTitle": "Descargar %{resource} '%{name}' (%{size})",
|
||||||
"shareCopyToClipboard": "Copiar al portapapeles: Ctrl+C, Intro",
|
"shareCopyToClipboard": "Copiar al portapapeles: Ctrl+C, Intro",
|
||||||
"remove_missing_title": "",
|
"remove_missing_title": "Eliminar elemento faltante",
|
||||||
"remove_missing_content": ""
|
"remove_missing_content": ""
|
||||||
},
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
|
@ -14,10 +14,17 @@ export const setTrack = (data) => ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const filterSongs = (data, ids) => {
|
export const filterSongs = (data, ids) => {
|
||||||
if (!ids) {
|
const filteredData = Object.fromEntries(
|
||||||
return data
|
Object.entries(data).filter(([_, song]) => !song.missing),
|
||||||
|
)
|
||||||
|
return !ids
|
||||||
|
? filteredData
|
||||||
|
: ids.reduce((acc, id) => {
|
||||||
|
if (filteredData[id]) {
|
||||||
|
return { ...acc, [id]: filteredData[id] }
|
||||||
}
|
}
|
||||||
return ids.reduce((acc, id) => ({ ...acc, [id]: data[id] }), {})
|
return acc
|
||||||
|
}, {})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addTracks = (data, ids) => {
|
export const addTracks = (data, ids) => {
|
||||||
|
@ -73,8 +73,9 @@ const AlbumActions = ({
|
|||||||
}, [dispatch, data, ids])
|
}, [dispatch, data, ids])
|
||||||
|
|
||||||
const handleAddToPlaylist = React.useCallback(() => {
|
const handleAddToPlaylist = React.useCallback(() => {
|
||||||
dispatch(openAddToPlaylist({ selectedIds: ids }))
|
const selectedIds = ids.filter((id) => !data[id].missing)
|
||||||
}, [dispatch, ids])
|
dispatch(openAddToPlaylist({ selectedIds }))
|
||||||
|
}, [dispatch, data, ids])
|
||||||
|
|
||||||
const handleShare = React.useCallback(() => {
|
const handleShare = React.useCallback(() => {
|
||||||
dispatch(openShareMenu([record.id], 'album', record.name))
|
dispatch(openShareMenu([record.id], 'album', record.name))
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import Table from '@material-ui/core/Table'
|
import Table from '@material-ui/core/Table'
|
||||||
import TableBody from '@material-ui/core/TableBody'
|
import TableBody from '@material-ui/core/TableBody'
|
||||||
import inflection from 'inflection'
|
import { humanize, underscore } from 'inflection'
|
||||||
import TableCell from '@material-ui/core/TableCell'
|
import TableCell from '@material-ui/core/TableCell'
|
||||||
import TableContainer from '@material-ui/core/TableContainer'
|
import TableContainer from '@material-ui/core/TableContainer'
|
||||||
import TableRow from '@material-ui/core/TableRow'
|
import TableRow from '@material-ui/core/TableRow'
|
||||||
@ -112,7 +112,7 @@ const AlbumInfo = (props) => {
|
|||||||
className={classes.tableCell}
|
className={classes.tableCell}
|
||||||
>
|
>
|
||||||
{translate(`resources.album.fields.${key}`, {
|
{translate(`resources.album.fields.${key}`, {
|
||||||
_: inflection.humanize(inflection.underscore(key)),
|
_: humanize(underscore(key)),
|
||||||
})}
|
})}
|
||||||
:
|
:
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
@ -31,7 +31,7 @@ import albumLists, { defaultAlbumList } from './albumLists'
|
|||||||
import config from '../config'
|
import config from '../config'
|
||||||
import AlbumInfo from './AlbumInfo'
|
import AlbumInfo from './AlbumInfo'
|
||||||
import ExpandInfoDialog from '../dialogs/ExpandInfoDialog'
|
import ExpandInfoDialog from '../dialogs/ExpandInfoDialog'
|
||||||
import inflection from 'inflection'
|
import { humanize } from 'inflection'
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
@ -140,9 +140,7 @@ const AlbumFilter = (props) => {
|
|||||||
<AutocompleteInput
|
<AutocompleteInput
|
||||||
emptyText="-- None --"
|
emptyText="-- None --"
|
||||||
optionText={(record) =>
|
optionText={(record) =>
|
||||||
record?.tagValue
|
record?.tagValue ? humanize(record?.tagValue) : '-- None --'
|
||||||
? inflection.humanize(record?.tagValue)
|
|
||||||
: '-- None --'
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</ReferenceInput>
|
</ReferenceInput>
|
||||||
|
@ -233,6 +233,7 @@ export const AlbumContextMenu = (props) =>
|
|||||||
album_id: props.record.id,
|
album_id: props.record.id,
|
||||||
release_date: props.releaseDate,
|
release_date: props.releaseDate,
|
||||||
disc_number: props.discNumber,
|
disc_number: props.discNumber,
|
||||||
|
missing: false,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -262,7 +263,7 @@ export const ArtistContextMenu = (props) =>
|
|||||||
field: 'album',
|
field: 'album',
|
||||||
order: 'ASC',
|
order: 'ASC',
|
||||||
},
|
},
|
||||||
filter: { album_artist_id: props.record.id },
|
filter: { album_artist_id: props.record.id, missing: false },
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : null
|
) : null
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Chip, makeStyles } from '@material-ui/core'
|
import { Chip, makeStyles } from '@material-ui/core'
|
||||||
import { useTranslate } from 'react-admin'
|
import { useTranslate } from 'react-admin'
|
||||||
import inflection from 'inflection'
|
import { humanize, underscore } from 'inflection'
|
||||||
|
|
||||||
const useQuickFilterStyles = makeStyles((theme) => ({
|
const useQuickFilterStyles = makeStyles((theme) => ({
|
||||||
chip: {
|
chip: {
|
||||||
@ -16,11 +16,11 @@ export const QuickFilter = ({ source, resource, label, defaultValue }) => {
|
|||||||
if (typeof lbl === 'string' || lbl instanceof String) {
|
if (typeof lbl === 'string' || lbl instanceof String) {
|
||||||
if (label) {
|
if (label) {
|
||||||
lbl = translate(lbl, {
|
lbl = translate(lbl, {
|
||||||
_: inflection.humanize(inflection.underscore(lbl)),
|
_: humanize(underscore(lbl)),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
lbl = translate(`resources.${resource}.fields.${source}`, {
|
lbl = translate(`resources.${resource}.fields.${source}`, {
|
||||||
_: inflection.humanize(inflection.underscore(source)),
|
_: humanize(underscore(source)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ export const ShuffleAllButton = ({ filters }) => {
|
|||||||
const dataProvider = useDataProvider()
|
const dataProvider = useDataProvider()
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const notify = useNotify()
|
const notify = useNotify()
|
||||||
|
filters = { ...filters, missing: false }
|
||||||
|
|
||||||
const handleOnClick = () => {
|
const handleOnClick = () => {
|
||||||
dataProvider
|
dataProvider
|
||||||
|
@ -13,7 +13,7 @@ import {
|
|||||||
useTranslate,
|
useTranslate,
|
||||||
useRecordContext,
|
useRecordContext,
|
||||||
} from 'react-admin'
|
} from 'react-admin'
|
||||||
import inflection from 'inflection'
|
import { humanize, underscore } from 'inflection'
|
||||||
import {
|
import {
|
||||||
ArtistLinkField,
|
ArtistLinkField,
|
||||||
BitrateField,
|
BitrateField,
|
||||||
@ -140,7 +140,7 @@ export const SongInfo = (props) => {
|
|||||||
<TableRow key={`${record.id}-${key}`}>
|
<TableRow key={`${record.id}-${key}`}>
|
||||||
<TableCell scope="row" className={classes.tableCell}>
|
<TableCell scope="row" className={classes.tableCell}>
|
||||||
{translate(`resources.song.fields.${key}`, {
|
{translate(`resources.song.fields.${key}`, {
|
||||||
_: inflection.humanize(inflection.underscore(key)),
|
_: humanize(underscore(key)),
|
||||||
})}
|
})}
|
||||||
:
|
:
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
@ -10,7 +10,7 @@ import TableRow from '@material-ui/core/TableRow'
|
|||||||
import TableCell from '@material-ui/core/TableCell'
|
import TableCell from '@material-ui/core/TableCell'
|
||||||
import Paper from '@material-ui/core/Paper'
|
import Paper from '@material-ui/core/Paper'
|
||||||
import FavoriteBorderIcon from '@material-ui/icons/FavoriteBorder'
|
import FavoriteBorderIcon from '@material-ui/icons/FavoriteBorder'
|
||||||
import inflection from 'inflection'
|
import { humanize, underscore } from 'inflection'
|
||||||
import { useGetOne, usePermissions, useTranslate } from 'react-admin'
|
import { useGetOne, usePermissions, useTranslate } from 'react-admin'
|
||||||
import config from '../config'
|
import config from '../config'
|
||||||
import { DialogTitle } from './DialogTitle'
|
import { DialogTitle } from './DialogTitle'
|
||||||
@ -136,7 +136,7 @@ const AboutDialog = ({ open, onClose }) => {
|
|||||||
<TableRow key={key}>
|
<TableRow key={key}>
|
||||||
<TableCell align="right" component="th" scope="row">
|
<TableCell align="right" component="th" scope="row">
|
||||||
{translate(`about.links.${key}`, {
|
{translate(`about.links.${key}`, {
|
||||||
_: inflection.humanize(inflection.underscore(key)),
|
_: humanize(underscore(key)),
|
||||||
})}
|
})}
|
||||||
:
|
:
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
@ -9,7 +9,7 @@ import TableBody from '@material-ui/core/TableBody'
|
|||||||
import TableRow from '@material-ui/core/TableRow'
|
import TableRow from '@material-ui/core/TableRow'
|
||||||
import TableCell from '@material-ui/core/TableCell'
|
import TableCell from '@material-ui/core/TableCell'
|
||||||
import { useTranslate } from 'react-admin'
|
import { useTranslate } from 'react-admin'
|
||||||
import inflection from 'inflection'
|
import { humanize } from 'inflection'
|
||||||
import { keyMap } from '../hotkeys'
|
import { keyMap } from '../hotkeys'
|
||||||
import { DialogTitle } from './DialogTitle'
|
import { DialogTitle } from './DialogTitle'
|
||||||
import { DialogContent } from './DialogContent'
|
import { DialogContent } from './DialogContent'
|
||||||
@ -29,7 +29,7 @@ const HelpTable = (props) => {
|
|||||||
{Object.keys(keyMap).map((key) => {
|
{Object.keys(keyMap).map((key) => {
|
||||||
const { sequences, name } = keyMap[key]
|
const { sequences, name } = keyMap[key]
|
||||||
const description = translate(`help.hotkeys.${name}`, {
|
const description = translate(`help.hotkeys.${name}`, {
|
||||||
_: inflection.humanize(name),
|
_: humanize(name),
|
||||||
})
|
})
|
||||||
return (
|
return (
|
||||||
<TableRow key={key}>
|
<TableRow key={key}>
|
||||||
|
@ -6,7 +6,7 @@ import { useTranslate, MenuItemLink, getResources } from 'react-admin'
|
|||||||
import ViewListIcon from '@material-ui/icons/ViewList'
|
import ViewListIcon from '@material-ui/icons/ViewList'
|
||||||
import AlbumIcon from '@material-ui/icons/Album'
|
import AlbumIcon from '@material-ui/icons/Album'
|
||||||
import SubMenu from './SubMenu'
|
import SubMenu from './SubMenu'
|
||||||
import inflection from 'inflection'
|
import { humanize, pluralize } from 'inflection'
|
||||||
import albumLists from '../album/albumLists'
|
import albumLists from '../album/albumLists'
|
||||||
import PlaylistsSubMenu from './PlaylistsSubMenu'
|
import PlaylistsSubMenu from './PlaylistsSubMenu'
|
||||||
import config from '../config'
|
import config from '../config'
|
||||||
@ -42,7 +42,7 @@ const translatedResourceName = (resource, translate) =>
|
|||||||
smart_count: 2,
|
smart_count: 2,
|
||||||
_: resource.options.label,
|
_: resource.options.label,
|
||||||
})
|
})
|
||||||
: inflection.humanize(inflection.pluralize(resource.name)),
|
: humanize(pluralize(resource.name)),
|
||||||
})
|
})
|
||||||
|
|
||||||
const Menu = ({ dense = false }) => {
|
const Menu = ({ dense = false }) => {
|
||||||
|
@ -16,7 +16,6 @@ export default defineConfig({
|
|||||||
filename: 'sw.js',
|
filename: 'sw.js',
|
||||||
devOptions: {
|
devOptions: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
type: 'module',
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
Loading…
x
Reference in New Issue
Block a user