diff --git a/db/migrations/20241026183640_support_new_scanner.go b/db/migrations/20241026183640_support_new_scanner.go index a9c48cc7d..bdf68c7cc 100644 --- a/db/migrations/20241026183640_support_new_scanner.go +++ b/db/migrations/20241026183640_support_new_scanner.go @@ -178,7 +178,7 @@ join library on media_file.library_id = library.id`, string(os.PathSeparator))) f := model.NewFolder(lib, path) _, err = stmt.ExecContext(ctx, f.ID, lib.ID, f.Path, f.Name, f.ParentID) 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 @@ -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) } - 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(` update media_file set path = replace(substr(path, %d), '\', '/');`, libPathLen+2)) if err != nil { diff --git a/resources/i18n/es.json b/resources/i18n/es.json index 83c7d4b1f..4c811b447 100644 --- a/resources/i18n/es.json +++ b/resources/i18n/es.json @@ -27,12 +27,12 @@ "playDate": "Últimas reproducciones", "channels": "Canales", "createdAt": "Creado el", - "grouping": "", + "grouping": "Agrupación", "mood": "", - "participants": "", - "tags": "", - "mappedTags": "", - "rawTags": "" + "participants": "Participantes", + "tags": "Etiquetas", + "mappedTags": "Etiquetas asignadas", + "rawTags": "Etiquetas sin procesar" }, "actions": { "addToQueue": "Reproducir después", @@ -65,10 +65,10 @@ "releaseDate": "Publicado", "releases": "Lanzamiento |||| Lanzamientos", "released": "Publicado", - "recordLabel": "", - "catalogNum": "", - "releaseType": "", - "grouping": "", + "recordLabel": "Discográfica", + "catalogNum": "Número de catálogo", + "releaseType": "Tipo de lanzamiento", + "grouping": "Agrupación", "media": "", "mood": "" }, @@ -76,7 +76,7 @@ "playAll": "Reproducir", "playNext": "Reproducir siguiente", "addToQueue": "Reproducir después", - "shuffle": "Aletorio", + "shuffle": "Aleatorio", "addToPlaylist": "Agregar a la lista", "download": "Descargar", "info": "Obtener información", @@ -102,22 +102,22 @@ "rating": "Calificación", "genre": "Género", "size": "Tamaño", - "role": "" + "role": "Rol" }, "roles": { - "albumartist": "", - "artist": "", - "composer": "", - "conductor": "", - "lyricist": "", - "arranger": "", - "producer": "", - "director": "", - "engineer": "", - "mixer": "", - "remixer": "", - "djmixer": "", - "performer": "" + "albumartist": "Artista del álbum", + "artist": "Artista", + "composer": "Compositor", + "conductor": "Director de orquesta", + "lyricist": "Letrista", + "arranger": "Arreglista", + "producer": "Productor", + "director": "Director", + "engineer": "Ingeniero de sonido", + "mixer": "Mezclador", + "remixer": "Remixer", + "djmixer": "DJ Mixer", + "performer": "Intérprete" } }, "user": { @@ -141,7 +141,7 @@ }, "notifications": { "created": "Usuario creado", - "updated": "Usuario actulalizado", + "updated": "Usuario actualizado", "deleted": "Usuario eliminado" }, "message": { @@ -228,17 +228,17 @@ } }, "missing": { - "name": "", + "name": "Faltante", "fields": { - "path": "", - "size": "", - "updatedAt": "" + "path": "Ruta", + "size": "Tamaño", + "updatedAt": "Actualizado el" }, "actions": { - "remove": "" + "remove": "Eliminar" }, "notifications": { - "removed": "" + "removed": "Eliminado" } } }, @@ -413,12 +413,12 @@ "downloadOriginalFormat": "Descargar formato original", "shareOriginalFormat": "Compartir formato original", "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}", "shareFailure": "Error al copiar la URL %{url} al portapapeles", "downloadDialogTitle": "Descargar %{resource} '%{name}' (%{size})", "shareCopyToClipboard": "Copiar al portapapeles: Ctrl+C, Intro", - "remove_missing_title": "", + "remove_missing_title": "Eliminar elemento faltante", "remove_missing_content": "" }, "menu": { @@ -509,4 +509,4 @@ "current_song": "Canción actual" } } -} \ No newline at end of file +} diff --git a/ui/src/actions/player.js b/ui/src/actions/player.js index a9e2577f4..acef2e9b2 100644 --- a/ui/src/actions/player.js +++ b/ui/src/actions/player.js @@ -14,10 +14,17 @@ export const setTrack = (data) => ({ }) export const filterSongs = (data, ids) => { - if (!ids) { - return data - } - return ids.reduce((acc, id) => ({ ...acc, [id]: data[id] }), {}) + const filteredData = Object.fromEntries( + Object.entries(data).filter(([_, song]) => !song.missing), + ) + return !ids + ? filteredData + : ids.reduce((acc, id) => { + if (filteredData[id]) { + return { ...acc, [id]: filteredData[id] } + } + return acc + }, {}) } export const addTracks = (data, ids) => { diff --git a/ui/src/album/AlbumActions.jsx b/ui/src/album/AlbumActions.jsx index 65d6fe64c..96cfab09a 100644 --- a/ui/src/album/AlbumActions.jsx +++ b/ui/src/album/AlbumActions.jsx @@ -73,8 +73,9 @@ const AlbumActions = ({ }, [dispatch, data, ids]) const handleAddToPlaylist = React.useCallback(() => { - dispatch(openAddToPlaylist({ selectedIds: ids })) - }, [dispatch, ids]) + const selectedIds = ids.filter((id) => !data[id].missing) + dispatch(openAddToPlaylist({ selectedIds })) + }, [dispatch, data, ids]) const handleShare = React.useCallback(() => { dispatch(openShareMenu([record.id], 'album', record.name)) diff --git a/ui/src/album/AlbumInfo.jsx b/ui/src/album/AlbumInfo.jsx index 6ddfda96f..d6d123895 100644 --- a/ui/src/album/AlbumInfo.jsx +++ b/ui/src/album/AlbumInfo.jsx @@ -1,6 +1,6 @@ import Table from '@material-ui/core/Table' import TableBody from '@material-ui/core/TableBody' -import inflection from 'inflection' +import { humanize, underscore } from 'inflection' import TableCell from '@material-ui/core/TableCell' import TableContainer from '@material-ui/core/TableContainer' import TableRow from '@material-ui/core/TableRow' @@ -112,7 +112,7 @@ const AlbumInfo = (props) => { className={classes.tableCell} > {translate(`resources.album.fields.${key}`, { - _: inflection.humanize(inflection.underscore(key)), + _: humanize(underscore(key)), })} : diff --git a/ui/src/album/AlbumList.jsx b/ui/src/album/AlbumList.jsx index 336c605ba..142457f12 100644 --- a/ui/src/album/AlbumList.jsx +++ b/ui/src/album/AlbumList.jsx @@ -31,7 +31,7 @@ import albumLists, { defaultAlbumList } from './albumLists' import config from '../config' import AlbumInfo from './AlbumInfo' import ExpandInfoDialog from '../dialogs/ExpandInfoDialog' -import inflection from 'inflection' +import { humanize } from 'inflection' import { makeStyles } from '@material-ui/core/styles' const useStyles = makeStyles({ @@ -140,9 +140,7 @@ const AlbumFilter = (props) => { - record?.tagValue - ? inflection.humanize(record?.tagValue) - : '-- None --' + record?.tagValue ? humanize(record?.tagValue) : '-- None --' } /> diff --git a/ui/src/common/ContextMenus.jsx b/ui/src/common/ContextMenus.jsx index 623b01a24..855825496 100644 --- a/ui/src/common/ContextMenus.jsx +++ b/ui/src/common/ContextMenus.jsx @@ -233,6 +233,7 @@ export const AlbumContextMenu = (props) => album_id: props.record.id, release_date: props.releaseDate, disc_number: props.discNumber, + missing: false, }, }} /> @@ -262,7 +263,7 @@ export const ArtistContextMenu = (props) => field: 'album', order: 'ASC', }, - filter: { album_artist_id: props.record.id }, + filter: { album_artist_id: props.record.id, missing: false }, }} /> ) : null diff --git a/ui/src/common/QuickFilter.jsx b/ui/src/common/QuickFilter.jsx index 62263ffc5..79b09b333 100644 --- a/ui/src/common/QuickFilter.jsx +++ b/ui/src/common/QuickFilter.jsx @@ -1,7 +1,7 @@ import React from 'react' import { Chip, makeStyles } from '@material-ui/core' import { useTranslate } from 'react-admin' -import inflection from 'inflection' +import { humanize, underscore } from 'inflection' const useQuickFilterStyles = makeStyles((theme) => ({ chip: { @@ -16,11 +16,11 @@ export const QuickFilter = ({ source, resource, label, defaultValue }) => { if (typeof lbl === 'string' || lbl instanceof String) { if (label) { lbl = translate(lbl, { - _: inflection.humanize(inflection.underscore(lbl)), + _: humanize(underscore(lbl)), }) } else { lbl = translate(`resources.${resource}.fields.${source}`, { - _: inflection.humanize(inflection.underscore(source)), + _: humanize(underscore(source)), }) } } diff --git a/ui/src/common/ShuffleAllButton.jsx b/ui/src/common/ShuffleAllButton.jsx index bc455b615..1631e2cf5 100644 --- a/ui/src/common/ShuffleAllButton.jsx +++ b/ui/src/common/ShuffleAllButton.jsx @@ -10,6 +10,7 @@ export const ShuffleAllButton = ({ filters }) => { const dataProvider = useDataProvider() const dispatch = useDispatch() const notify = useNotify() + filters = { ...filters, missing: false } const handleOnClick = () => { dataProvider diff --git a/ui/src/common/SongInfo.jsx b/ui/src/common/SongInfo.jsx index bce0e750f..d94685633 100644 --- a/ui/src/common/SongInfo.jsx +++ b/ui/src/common/SongInfo.jsx @@ -13,7 +13,7 @@ import { useTranslate, useRecordContext, } from 'react-admin' -import inflection from 'inflection' +import { humanize, underscore } from 'inflection' import { ArtistLinkField, BitrateField, @@ -140,7 +140,7 @@ export const SongInfo = (props) => { {translate(`resources.song.fields.${key}`, { - _: inflection.humanize(inflection.underscore(key)), + _: humanize(underscore(key)), })} : diff --git a/ui/src/dialogs/AboutDialog.jsx b/ui/src/dialogs/AboutDialog.jsx index facc056e0..4f074002b 100644 --- a/ui/src/dialogs/AboutDialog.jsx +++ b/ui/src/dialogs/AboutDialog.jsx @@ -10,7 +10,7 @@ import TableRow from '@material-ui/core/TableRow' import TableCell from '@material-ui/core/TableCell' import Paper from '@material-ui/core/Paper' import FavoriteBorderIcon from '@material-ui/icons/FavoriteBorder' -import inflection from 'inflection' +import { humanize, underscore } from 'inflection' import { useGetOne, usePermissions, useTranslate } from 'react-admin' import config from '../config' import { DialogTitle } from './DialogTitle' @@ -136,7 +136,7 @@ const AboutDialog = ({ open, onClose }) => { {translate(`about.links.${key}`, { - _: inflection.humanize(inflection.underscore(key)), + _: humanize(underscore(key)), })} : diff --git a/ui/src/dialogs/HelpDialog.jsx b/ui/src/dialogs/HelpDialog.jsx index adbce99b2..1aa9db60e 100644 --- a/ui/src/dialogs/HelpDialog.jsx +++ b/ui/src/dialogs/HelpDialog.jsx @@ -9,7 +9,7 @@ import TableBody from '@material-ui/core/TableBody' import TableRow from '@material-ui/core/TableRow' import TableCell from '@material-ui/core/TableCell' import { useTranslate } from 'react-admin' -import inflection from 'inflection' +import { humanize } from 'inflection' import { keyMap } from '../hotkeys' import { DialogTitle } from './DialogTitle' import { DialogContent } from './DialogContent' @@ -29,7 +29,7 @@ const HelpTable = (props) => { {Object.keys(keyMap).map((key) => { const { sequences, name } = keyMap[key] const description = translate(`help.hotkeys.${name}`, { - _: inflection.humanize(name), + _: humanize(name), }) return ( diff --git a/ui/src/layout/Menu.jsx b/ui/src/layout/Menu.jsx index 2cb8a9824..bd1e37ee0 100644 --- a/ui/src/layout/Menu.jsx +++ b/ui/src/layout/Menu.jsx @@ -6,7 +6,7 @@ import { useTranslate, MenuItemLink, getResources } from 'react-admin' import ViewListIcon from '@material-ui/icons/ViewList' import AlbumIcon from '@material-ui/icons/Album' import SubMenu from './SubMenu' -import inflection from 'inflection' +import { humanize, pluralize } from 'inflection' import albumLists from '../album/albumLists' import PlaylistsSubMenu from './PlaylistsSubMenu' import config from '../config' @@ -42,7 +42,7 @@ const translatedResourceName = (resource, translate) => smart_count: 2, _: resource.options.label, }) - : inflection.humanize(inflection.pluralize(resource.name)), + : humanize(pluralize(resource.name)), }) const Menu = ({ dense = false }) => { diff --git a/ui/vite.config.js b/ui/vite.config.js index 590313ffc..dee9d3939 100644 --- a/ui/vite.config.js +++ b/ui/vite.config.js @@ -16,7 +16,6 @@ export default defineConfig({ filename: 'sw.js', devOptions: { enabled: true, - type: 'module', }, }), ],