Merge branch 'master' into dlna-spike

This commit is contained in:
Rob Emery 2025-03-12 20:15:58 +00:00
commit 6dcabcc35d
14 changed files with 73 additions and 61 deletions

View File

@ -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 {

View File

@ -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": {

View File

@ -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) => {

View File

@ -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))

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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)),
}) })
} }
} }

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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}>

View File

@ -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 }) => {

View File

@ -16,7 +16,6 @@ export default defineConfig({
filename: 'sw.js', filename: 'sw.js',
devOptions: { devOptions: {
enabled: true, enabled: true,
type: 'module',
}, },
}), }),
], ],