quintodrome/ui/src/radio/RadioList.jsx

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

168 lines
4.1 KiB
React
Raw Normal View History

feat(ui): add cover art support for internet radio stations (#5229) * feat(artwork): add KindRadioArtwork and EntityRadio constant * feat(model): add UploadedImage field and artwork methods to Radio * feat(model): add Radio to GetEntityByID lookup chain * feat(db): add uploaded_image column to radio table * feat(artwork): add radio artwork reader with uploaded image fallback * feat(api): add radio image upload/delete endpoints * feat(ui): add radio artwork ID prefix to getCoverArtUrl * feat(ui): add cover art display and upload to RadioEdit * feat(ui): add cover art thumbnails to radio list * feat(ui): prefer artwork URL in radio player helper * refactor: remove redundant code in radio artwork - Remove duplicate Avatar rendering in RadioList by reusing CoverArtField - Remove redundant UpdatedAt assignment in radio image handlers (already set by repository Put) * refactor(ui): extract shared useImageLoadingState hook Move image loading/error/lightbox state management into a shared useImageLoadingState hook in common/. Consolidates duplicated logic from AlbumDetails, PlaylistDetails, RadioEdit, and artist detail views. * feat(ui): use radio placeholder icon when no uploaded image Remove album placeholder fallback from radio artwork reader so radios without an uploaded image return ErrUnavailable. On the frontend, show the internet-radio-icon.svg placeholder instead of requesting server artwork when no image is uploaded, allowing favicon fallback in the player. * refactor(ui): update defaultOff fields in useSelectedFields for RadioList Signed-off-by: Deluan <deluan@navidrome.org> * fix: address code review feedback - Add missing alt attribute to CardMedia in RadioEdit for accessibility - Fix UpdateInternetRadio to preserve UploadedImage field by fetching existing radio before updating (prevents Subsonic API from clearing custom artwork) - Add Reader() level tests to verify ErrUnavailable is returned when radio has no uploaded image * refactor: add colsToUpdate to RadioRepository.Put Use the base sqlRepository.put with column filtering instead of hand-rolled SQL. UpdateInternetRadio now specifies only the Subsonic API fields, preventing UploadedImage from being cleared. Image upload/delete handlers specify only UploadedImage. * fix: ensure UpdatedAt is included in colsToUpdate for radio Put --------- Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-18 14:57:33 -08:00
import { Avatar, makeStyles, useMediaQuery } from '@material-ui/core'
import React, { cloneElement } from 'react'
import {
CreateButton,
Datagrid,
DateField,
2023-01-17 10:22:10 -09:00
EditButton,
Filter,
sanitizeListRestProps,
SearchInput,
SimpleList,
TextField,
TopToolbar,
UrlField,
useTranslate,
} from 'react-admin'
fix(ui): cancel in-flight image requests on pagination, cache across remounts (#5249) * feat(ui): cancel in-flight image requests on pagination and cache across remounts When paginating quickly through list/grid views, image requests for previous pages were never canceled, queuing on the server and blocking new images. This adds a useImageUrl hook that loads images via fetch() with AbortController, so requests are canceled when components unmount. A module-level cache (URL → blob URL) with reference counting ensures React Admin refreshes display images instantly without re-fetching. * feat(ui): update AlbumListPagination to conditionally render based on albumListType Signed-off-by: Deluan <deluan@navidrome.org> * feat(ui): abort all in-flight image fetches on pagination change Pagination component now watches page/perPage via useListContext and calls abortAllInFlight() when either changes, freeing the browser connection pool immediately for the next page's data request. Also adds empty placeholder style to CoverArtAvatar so it renders as a clean transparent area while loading instead of the default person icon. * Revert "feat(ui): abort all in-flight image fetches on pagination change" This reverts commit 3bc09f9d0374aa63572a381e38a30e2f2cec4da8. * fix(ui): limit concurrent image fetches to prevent connection pool saturation With <img src>, the browser prioritizes API requests over image loads. With fetch(), all requests compete equally for the HTTP/1.1 connection pool (6 per origin), causing API requests to queue behind images and making pagination feel unresponsive. Caps concurrent image fetches at 4 with a pending queue, leaving connections free for API requests. Queued fetches for unmounted components are removed without ever hitting the network. * fix(ui): fix queued fetch not aborted on unmount Set queued=false when doFetch executes from the pending queue, so cleanup correctly calls controller.abort() instead of searching an already-drained queue. --------- Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-25 17:30:40 -08:00
import {
List,
useImageUrl,
ToggleFieldsMenu,
useSelectedFields,
} from '../common'
feat(ui): add cover art support for internet radio stations (#5229) * feat(artwork): add KindRadioArtwork and EntityRadio constant * feat(model): add UploadedImage field and artwork methods to Radio * feat(model): add Radio to GetEntityByID lookup chain * feat(db): add uploaded_image column to radio table * feat(artwork): add radio artwork reader with uploaded image fallback * feat(api): add radio image upload/delete endpoints * feat(ui): add radio artwork ID prefix to getCoverArtUrl * feat(ui): add cover art display and upload to RadioEdit * feat(ui): add cover art thumbnails to radio list * feat(ui): prefer artwork URL in radio player helper * refactor: remove redundant code in radio artwork - Remove duplicate Avatar rendering in RadioList by reusing CoverArtField - Remove redundant UpdatedAt assignment in radio image handlers (already set by repository Put) * refactor(ui): extract shared useImageLoadingState hook Move image loading/error/lightbox state management into a shared useImageLoadingState hook in common/. Consolidates duplicated logic from AlbumDetails, PlaylistDetails, RadioEdit, and artist detail views. * feat(ui): use radio placeholder icon when no uploaded image Remove album placeholder fallback from radio artwork reader so radios without an uploaded image return ErrUnavailable. On the frontend, show the internet-radio-icon.svg placeholder instead of requesting server artwork when no image is uploaded, allowing favicon fallback in the player. * refactor(ui): update defaultOff fields in useSelectedFields for RadioList Signed-off-by: Deluan <deluan@navidrome.org> * fix: address code review feedback - Add missing alt attribute to CardMedia in RadioEdit for accessibility - Fix UpdateInternetRadio to preserve UploadedImage field by fetching existing radio before updating (prevents Subsonic API from clearing custom artwork) - Add Reader() level tests to verify ErrUnavailable is returned when radio has no uploaded image * refactor: add colsToUpdate to RadioRepository.Put Use the base sqlRepository.put with column filtering instead of hand-rolled SQL. UpdateInternetRadio now specifies only the Subsonic API fields, preventing UploadedImage from being cleared. Image upload/delete handlers specify only UploadedImage. * fix: ensure UpdatedAt is included in colsToUpdate for radio Put --------- Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-18 14:57:33 -08:00
import subsonic from '../subsonic'
import { StreamField } from './StreamField'
2023-01-17 10:22:10 -09:00
import { setTrack } from '../actions'
import { songFromRadio } from './helper'
feat(ui): add cover art support for internet radio stations (#5229) * feat(artwork): add KindRadioArtwork and EntityRadio constant * feat(model): add UploadedImage field and artwork methods to Radio * feat(model): add Radio to GetEntityByID lookup chain * feat(db): add uploaded_image column to radio table * feat(artwork): add radio artwork reader with uploaded image fallback * feat(api): add radio image upload/delete endpoints * feat(ui): add radio artwork ID prefix to getCoverArtUrl * feat(ui): add cover art display and upload to RadioEdit * feat(ui): add cover art thumbnails to radio list * feat(ui): prefer artwork URL in radio player helper * refactor: remove redundant code in radio artwork - Remove duplicate Avatar rendering in RadioList by reusing CoverArtField - Remove redundant UpdatedAt assignment in radio image handlers (already set by repository Put) * refactor(ui): extract shared useImageLoadingState hook Move image loading/error/lightbox state management into a shared useImageLoadingState hook in common/. Consolidates duplicated logic from AlbumDetails, PlaylistDetails, RadioEdit, and artist detail views. * feat(ui): use radio placeholder icon when no uploaded image Remove album placeholder fallback from radio artwork reader so radios without an uploaded image return ErrUnavailable. On the frontend, show the internet-radio-icon.svg placeholder instead of requesting server artwork when no image is uploaded, allowing favicon fallback in the player. * refactor(ui): update defaultOff fields in useSelectedFields for RadioList Signed-off-by: Deluan <deluan@navidrome.org> * fix: address code review feedback - Add missing alt attribute to CardMedia in RadioEdit for accessibility - Fix UpdateInternetRadio to preserve UploadedImage field by fetching existing radio before updating (prevents Subsonic API from clearing custom artwork) - Add Reader() level tests to verify ErrUnavailable is returned when radio has no uploaded image * refactor: add colsToUpdate to RadioRepository.Put Use the base sqlRepository.put with column filtering instead of hand-rolled SQL. UpdateInternetRadio now specifies only the Subsonic API fields, preventing UploadedImage from being cleared. Image upload/delete handlers specify only UploadedImage. * fix: ensure UpdatedAt is included in colsToUpdate for radio Put --------- Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-18 14:57:33 -08:00
import { RADIO_PLACEHOLDER_IMAGE } from '../consts'
2023-01-17 10:22:10 -09:00
import { useDispatch } from 'react-redux'
const useStyles = makeStyles({
row: {
'&:hover': {
'& $contextMenu': {
visibility: 'visible',
},
},
},
contextMenu: {
visibility: 'hidden',
},
})
const RadioFilter = (props) => (
<Filter {...props} variant={'outlined'}>
<SearchInput id="search" source="name" alwaysOn />
</Filter>
)
const RadioListActions = ({
className,
filters,
resource,
showFilter,
displayedFilters,
filterValues,
isAdmin,
...rest
}) => {
const isNotSmall = useMediaQuery((theme) => theme.breakpoints.up('sm'))
const translate = useTranslate()
return (
<TopToolbar className={className} {...sanitizeListRestProps(rest)}>
{isAdmin && (
<CreateButton basePath="/radio">
{translate('ra.action.create')}
</CreateButton>
)}
{filters &&
cloneElement(filters, {
resource,
showFilter,
displayedFilters,
filterValues,
context: 'button',
})}
{isNotSmall && <ToggleFieldsMenu resource="radio" />}
</TopToolbar>
)
}
feat(ui): add cover art support for internet radio stations (#5229) * feat(artwork): add KindRadioArtwork and EntityRadio constant * feat(model): add UploadedImage field and artwork methods to Radio * feat(model): add Radio to GetEntityByID lookup chain * feat(db): add uploaded_image column to radio table * feat(artwork): add radio artwork reader with uploaded image fallback * feat(api): add radio image upload/delete endpoints * feat(ui): add radio artwork ID prefix to getCoverArtUrl * feat(ui): add cover art display and upload to RadioEdit * feat(ui): add cover art thumbnails to radio list * feat(ui): prefer artwork URL in radio player helper * refactor: remove redundant code in radio artwork - Remove duplicate Avatar rendering in RadioList by reusing CoverArtField - Remove redundant UpdatedAt assignment in radio image handlers (already set by repository Put) * refactor(ui): extract shared useImageLoadingState hook Move image loading/error/lightbox state management into a shared useImageLoadingState hook in common/. Consolidates duplicated logic from AlbumDetails, PlaylistDetails, RadioEdit, and artist detail views. * feat(ui): use radio placeholder icon when no uploaded image Remove album placeholder fallback from radio artwork reader so radios without an uploaded image return ErrUnavailable. On the frontend, show the internet-radio-icon.svg placeholder instead of requesting server artwork when no image is uploaded, allowing favicon fallback in the player. * refactor(ui): update defaultOff fields in useSelectedFields for RadioList Signed-off-by: Deluan <deluan@navidrome.org> * fix: address code review feedback - Add missing alt attribute to CardMedia in RadioEdit for accessibility - Fix UpdateInternetRadio to preserve UploadedImage field by fetching existing radio before updating (prevents Subsonic API from clearing custom artwork) - Add Reader() level tests to verify ErrUnavailable is returned when radio has no uploaded image * refactor: add colsToUpdate to RadioRepository.Put Use the base sqlRepository.put with column filtering instead of hand-rolled SQL. UpdateInternetRadio now specifies only the Subsonic API fields, preventing UploadedImage from being cleared. Image upload/delete handlers specify only UploadedImage. * fix: ensure UpdatedAt is included in colsToUpdate for radio Put --------- Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-18 14:57:33 -08:00
const avatarStyle = { width: 40, height: 40 }
const CoverArtField = ({ record }) => {
fix(ui): cancel in-flight image requests on pagination, cache across remounts (#5249) * feat(ui): cancel in-flight image requests on pagination and cache across remounts When paginating quickly through list/grid views, image requests for previous pages were never canceled, queuing on the server and blocking new images. This adds a useImageUrl hook that loads images via fetch() with AbortController, so requests are canceled when components unmount. A module-level cache (URL → blob URL) with reference counting ensures React Admin refreshes display images instantly without re-fetching. * feat(ui): update AlbumListPagination to conditionally render based on albumListType Signed-off-by: Deluan <deluan@navidrome.org> * feat(ui): abort all in-flight image fetches on pagination change Pagination component now watches page/perPage via useListContext and calls abortAllInFlight() when either changes, freeing the browser connection pool immediately for the next page's data request. Also adds empty placeholder style to CoverArtAvatar so it renders as a clean transparent area while loading instead of the default person icon. * Revert "feat(ui): abort all in-flight image fetches on pagination change" This reverts commit 3bc09f9d0374aa63572a381e38a30e2f2cec4da8. * fix(ui): limit concurrent image fetches to prevent connection pool saturation With <img src>, the browser prioritizes API requests over image loads. With fetch(), all requests compete equally for the HTTP/1.1 connection pool (6 per origin), causing API requests to queue behind images and making pagination feel unresponsive. Caps concurrent image fetches at 4 with a pending queue, leaving connections free for API requests. Queued fetches for unmounted components are removed without ever hitting the network. * fix(ui): fix queued fetch not aborted on unmount Set queued=false when doFetch executes from the pending queue, so cleanup correctly calls controller.abort() instead of searching an already-drained queue. --------- Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-25 17:30:40 -08:00
const directUrl = record?.uploadedImage
feat(ui): add cover art support for internet radio stations (#5229) * feat(artwork): add KindRadioArtwork and EntityRadio constant * feat(model): add UploadedImage field and artwork methods to Radio * feat(model): add Radio to GetEntityByID lookup chain * feat(db): add uploaded_image column to radio table * feat(artwork): add radio artwork reader with uploaded image fallback * feat(api): add radio image upload/delete endpoints * feat(ui): add radio artwork ID prefix to getCoverArtUrl * feat(ui): add cover art display and upload to RadioEdit * feat(ui): add cover art thumbnails to radio list * feat(ui): prefer artwork URL in radio player helper * refactor: remove redundant code in radio artwork - Remove duplicate Avatar rendering in RadioList by reusing CoverArtField - Remove redundant UpdatedAt assignment in radio image handlers (already set by repository Put) * refactor(ui): extract shared useImageLoadingState hook Move image loading/error/lightbox state management into a shared useImageLoadingState hook in common/. Consolidates duplicated logic from AlbumDetails, PlaylistDetails, RadioEdit, and artist detail views. * feat(ui): use radio placeholder icon when no uploaded image Remove album placeholder fallback from radio artwork reader so radios without an uploaded image return ErrUnavailable. On the frontend, show the internet-radio-icon.svg placeholder instead of requesting server artwork when no image is uploaded, allowing favicon fallback in the player. * refactor(ui): update defaultOff fields in useSelectedFields for RadioList Signed-off-by: Deluan <deluan@navidrome.org> * fix: address code review feedback - Add missing alt attribute to CardMedia in RadioEdit for accessibility - Fix UpdateInternetRadio to preserve UploadedImage field by fetching existing radio before updating (prevents Subsonic API from clearing custom artwork) - Add Reader() level tests to verify ErrUnavailable is returned when radio has no uploaded image * refactor: add colsToUpdate to RadioRepository.Put Use the base sqlRepository.put with column filtering instead of hand-rolled SQL. UpdateInternetRadio now specifies only the Subsonic API fields, preventing UploadedImage from being cleared. Image upload/delete handlers specify only UploadedImage. * fix: ensure UpdatedAt is included in colsToUpdate for radio Put --------- Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-18 14:57:33 -08:00
? subsonic.getCoverArtUrl(record, 40, true)
fix(ui): cancel in-flight image requests on pagination, cache across remounts (#5249) * feat(ui): cancel in-flight image requests on pagination and cache across remounts When paginating quickly through list/grid views, image requests for previous pages were never canceled, queuing on the server and blocking new images. This adds a useImageUrl hook that loads images via fetch() with AbortController, so requests are canceled when components unmount. A module-level cache (URL → blob URL) with reference counting ensures React Admin refreshes display images instantly without re-fetching. * feat(ui): update AlbumListPagination to conditionally render based on albumListType Signed-off-by: Deluan <deluan@navidrome.org> * feat(ui): abort all in-flight image fetches on pagination change Pagination component now watches page/perPage via useListContext and calls abortAllInFlight() when either changes, freeing the browser connection pool immediately for the next page's data request. Also adds empty placeholder style to CoverArtAvatar so it renders as a clean transparent area while loading instead of the default person icon. * Revert "feat(ui): abort all in-flight image fetches on pagination change" This reverts commit 3bc09f9d0374aa63572a381e38a30e2f2cec4da8. * fix(ui): limit concurrent image fetches to prevent connection pool saturation With <img src>, the browser prioritizes API requests over image loads. With fetch(), all requests compete equally for the HTTP/1.1 connection pool (6 per origin), causing API requests to queue behind images and making pagination feel unresponsive. Caps concurrent image fetches at 4 with a pending queue, leaving connections free for API requests. Queued fetches for unmounted components are removed without ever hitting the network. * fix(ui): fix queued fetch not aborted on unmount Set queued=false when doFetch executes from the pending queue, so cleanup correctly calls controller.abort() instead of searching an already-drained queue. --------- Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-25 17:30:40 -08:00
: null
const { imgUrl } = useImageUrl(directUrl)
if (!record) return null
const src = imgUrl || RADIO_PLACEHOLDER_IMAGE
feat(ui): add cover art support for internet radio stations (#5229) * feat(artwork): add KindRadioArtwork and EntityRadio constant * feat(model): add UploadedImage field and artwork methods to Radio * feat(model): add Radio to GetEntityByID lookup chain * feat(db): add uploaded_image column to radio table * feat(artwork): add radio artwork reader with uploaded image fallback * feat(api): add radio image upload/delete endpoints * feat(ui): add radio artwork ID prefix to getCoverArtUrl * feat(ui): add cover art display and upload to RadioEdit * feat(ui): add cover art thumbnails to radio list * feat(ui): prefer artwork URL in radio player helper * refactor: remove redundant code in radio artwork - Remove duplicate Avatar rendering in RadioList by reusing CoverArtField - Remove redundant UpdatedAt assignment in radio image handlers (already set by repository Put) * refactor(ui): extract shared useImageLoadingState hook Move image loading/error/lightbox state management into a shared useImageLoadingState hook in common/. Consolidates duplicated logic from AlbumDetails, PlaylistDetails, RadioEdit, and artist detail views. * feat(ui): use radio placeholder icon when no uploaded image Remove album placeholder fallback from radio artwork reader so radios without an uploaded image return ErrUnavailable. On the frontend, show the internet-radio-icon.svg placeholder instead of requesting server artwork when no image is uploaded, allowing favicon fallback in the player. * refactor(ui): update defaultOff fields in useSelectedFields for RadioList Signed-off-by: Deluan <deluan@navidrome.org> * fix: address code review feedback - Add missing alt attribute to CardMedia in RadioEdit for accessibility - Fix UpdateInternetRadio to preserve UploadedImage field by fetching existing radio before updating (prevents Subsonic API from clearing custom artwork) - Add Reader() level tests to verify ErrUnavailable is returned when radio has no uploaded image * refactor: add colsToUpdate to RadioRepository.Put Use the base sqlRepository.put with column filtering instead of hand-rolled SQL. UpdateInternetRadio now specifies only the Subsonic API fields, preventing UploadedImage from being cleared. Image upload/delete handlers specify only UploadedImage. * fix: ensure UpdatedAt is included in colsToUpdate for radio Put --------- Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-18 14:57:33 -08:00
return (
<Avatar src={src} variant="rounded" style={avatarStyle} alt={record.name} />
)
}
CoverArtField.defaultProps = { label: '' }
const RadioList = ({ permissions, ...props }) => {
const classes = useStyles()
2023-01-17 10:22:10 -09:00
const isXsmall = useMediaQuery((theme) => theme.breakpoints.down('xs'))
const dispatch = useDispatch()
const isAdmin = permissions === 'admin'
const toggleableFields = {
feat(ui): add cover art support for internet radio stations (#5229) * feat(artwork): add KindRadioArtwork and EntityRadio constant * feat(model): add UploadedImage field and artwork methods to Radio * feat(model): add Radio to GetEntityByID lookup chain * feat(db): add uploaded_image column to radio table * feat(artwork): add radio artwork reader with uploaded image fallback * feat(api): add radio image upload/delete endpoints * feat(ui): add radio artwork ID prefix to getCoverArtUrl * feat(ui): add cover art display and upload to RadioEdit * feat(ui): add cover art thumbnails to radio list * feat(ui): prefer artwork URL in radio player helper * refactor: remove redundant code in radio artwork - Remove duplicate Avatar rendering in RadioList by reusing CoverArtField - Remove redundant UpdatedAt assignment in radio image handlers (already set by repository Put) * refactor(ui): extract shared useImageLoadingState hook Move image loading/error/lightbox state management into a shared useImageLoadingState hook in common/. Consolidates duplicated logic from AlbumDetails, PlaylistDetails, RadioEdit, and artist detail views. * feat(ui): use radio placeholder icon when no uploaded image Remove album placeholder fallback from radio artwork reader so radios without an uploaded image return ErrUnavailable. On the frontend, show the internet-radio-icon.svg placeholder instead of requesting server artwork when no image is uploaded, allowing favicon fallback in the player. * refactor(ui): update defaultOff fields in useSelectedFields for RadioList Signed-off-by: Deluan <deluan@navidrome.org> * fix: address code review feedback - Add missing alt attribute to CardMedia in RadioEdit for accessibility - Fix UpdateInternetRadio to preserve UploadedImage field by fetching existing radio before updating (prevents Subsonic API from clearing custom artwork) - Add Reader() level tests to verify ErrUnavailable is returned when radio has no uploaded image * refactor: add colsToUpdate to RadioRepository.Put Use the base sqlRepository.put with column filtering instead of hand-rolled SQL. UpdateInternetRadio now specifies only the Subsonic API fields, preventing UploadedImage from being cleared. Image upload/delete handlers specify only UploadedImage. * fix: ensure UpdatedAt is included in colsToUpdate for radio Put --------- Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-18 14:57:33 -08:00
coverArt: <CoverArtField source="id" sortable={false} />,
name: <TextField source="name" />,
homePageUrl: (
<UrlField
source="homePageUrl"
onClick={(e) => e.stopPropagation()}
target="_blank"
rel="noopener noreferrer"
/>
),
2023-01-17 10:31:17 -09:00
streamUrl: <TextField source="streamUrl" />,
updatedAt: <DateField source="updatedAt" showTime />,
2023-01-17 10:22:10 -09:00
createdAt: <DateField source="createdAt" showTime />,
}
const columns = useSelectedFields({
resource: 'radio',
columns: toggleableFields,
feat(ui): add cover art support for internet radio stations (#5229) * feat(artwork): add KindRadioArtwork and EntityRadio constant * feat(model): add UploadedImage field and artwork methods to Radio * feat(model): add Radio to GetEntityByID lookup chain * feat(db): add uploaded_image column to radio table * feat(artwork): add radio artwork reader with uploaded image fallback * feat(api): add radio image upload/delete endpoints * feat(ui): add radio artwork ID prefix to getCoverArtUrl * feat(ui): add cover art display and upload to RadioEdit * feat(ui): add cover art thumbnails to radio list * feat(ui): prefer artwork URL in radio player helper * refactor: remove redundant code in radio artwork - Remove duplicate Avatar rendering in RadioList by reusing CoverArtField - Remove redundant UpdatedAt assignment in radio image handlers (already set by repository Put) * refactor(ui): extract shared useImageLoadingState hook Move image loading/error/lightbox state management into a shared useImageLoadingState hook in common/. Consolidates duplicated logic from AlbumDetails, PlaylistDetails, RadioEdit, and artist detail views. * feat(ui): use radio placeholder icon when no uploaded image Remove album placeholder fallback from radio artwork reader so radios without an uploaded image return ErrUnavailable. On the frontend, show the internet-radio-icon.svg placeholder instead of requesting server artwork when no image is uploaded, allowing favicon fallback in the player. * refactor(ui): update defaultOff fields in useSelectedFields for RadioList Signed-off-by: Deluan <deluan@navidrome.org> * fix: address code review feedback - Add missing alt attribute to CardMedia in RadioEdit for accessibility - Fix UpdateInternetRadio to preserve UploadedImage field by fetching existing radio before updating (prevents Subsonic API from clearing custom artwork) - Add Reader() level tests to verify ErrUnavailable is returned when radio has no uploaded image * refactor: add colsToUpdate to RadioRepository.Put Use the base sqlRepository.put with column filtering instead of hand-rolled SQL. UpdateInternetRadio now specifies only the Subsonic API fields, preventing UploadedImage from being cleared. Image upload/delete handlers specify only UploadedImage. * fix: ensure UpdatedAt is included in colsToUpdate for radio Put --------- Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-18 14:57:33 -08:00
defaultOff: ['streamUrl', 'createdAt'],
})
2023-01-17 10:22:10 -09:00
const handleRowClick = async (id, basePath, record) => {
dispatch(setTrack(await songFromRadio(record)))
}
return (
<List
{...props}
exporter={false}
2023-01-15 12:12:22 -09:00
sort={{ field: 'name', order: 'ASC' }}
bulkActionButtons={isAdmin ? undefined : false}
hasCreate={isAdmin}
actions={<RadioListActions isAdmin={isAdmin} />}
filters={<RadioFilter />}
perPage={isXsmall ? 25 : 10}
>
{isXsmall ? (
<SimpleList
feat(ui): add cover art support for internet radio stations (#5229) * feat(artwork): add KindRadioArtwork and EntityRadio constant * feat(model): add UploadedImage field and artwork methods to Radio * feat(model): add Radio to GetEntityByID lookup chain * feat(db): add uploaded_image column to radio table * feat(artwork): add radio artwork reader with uploaded image fallback * feat(api): add radio image upload/delete endpoints * feat(ui): add radio artwork ID prefix to getCoverArtUrl * feat(ui): add cover art display and upload to RadioEdit * feat(ui): add cover art thumbnails to radio list * feat(ui): prefer artwork URL in radio player helper * refactor: remove redundant code in radio artwork - Remove duplicate Avatar rendering in RadioList by reusing CoverArtField - Remove redundant UpdatedAt assignment in radio image handlers (already set by repository Put) * refactor(ui): extract shared useImageLoadingState hook Move image loading/error/lightbox state management into a shared useImageLoadingState hook in common/. Consolidates duplicated logic from AlbumDetails, PlaylistDetails, RadioEdit, and artist detail views. * feat(ui): use radio placeholder icon when no uploaded image Remove album placeholder fallback from radio artwork reader so radios without an uploaded image return ErrUnavailable. On the frontend, show the internet-radio-icon.svg placeholder instead of requesting server artwork when no image is uploaded, allowing favicon fallback in the player. * refactor(ui): update defaultOff fields in useSelectedFields for RadioList Signed-off-by: Deluan <deluan@navidrome.org> * fix: address code review feedback - Add missing alt attribute to CardMedia in RadioEdit for accessibility - Fix UpdateInternetRadio to preserve UploadedImage field by fetching existing radio before updating (prevents Subsonic API from clearing custom artwork) - Add Reader() level tests to verify ErrUnavailable is returned when radio has no uploaded image * refactor: add colsToUpdate to RadioRepository.Put Use the base sqlRepository.put with column filtering instead of hand-rolled SQL. UpdateInternetRadio now specifies only the Subsonic API fields, preventing UploadedImage from being cleared. Image upload/delete handlers specify only UploadedImage. * fix: ensure UpdatedAt is included in colsToUpdate for radio Put --------- Signed-off-by: Deluan <deluan@navidrome.org>
2026-03-18 14:57:33 -08:00
leftAvatar={(r) => <CoverArtField record={r} />}
leftIcon={(r) => (
<StreamField
record={r}
source={'streamUrl'}
hideUrl
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
}}
/>
)}
primaryText={(r) => r.name}
secondaryText={(r) => r.homePageUrl}
/>
) : (
2023-01-17 10:22:10 -09:00
<Datagrid rowClick={handleRowClick} classes={{ row: classes.row }}>
{columns}
2023-01-17 10:22:10 -09:00
{isAdmin && <EditButton />}
</Datagrid>
)}
</List>
)
}
export default RadioList