Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/FoxxMD/multi-scrobbler. Scrobble plays from multiple sources to multiple clients docs.multi-scrobbler.app
deezer docker jellyfin koito lastfm listenbrainz maloja mopidy mpris music music-assistant plex scrobble self-hosted spotify subsonic tautulli youtube-music
Something went wrong. Try again.
6.1 kB · 149 lines
TSX
at master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149import { Box, useListCollection, Stack, HStack, Badge, Span } from "@chakra-ui/react"import { useQuery } from '@tanstack/react-query';import { tanQueries } from "../../queries/index.ts";import { type AlbumSearchResult, type AlbumSearchSimpleRequestQuery, type TrackDataCreditBase } from "../../../core/Api.ts";import React, { useCallback, useEffect, useState } from "react";import { ArtistCreditTags } from "../ArtistCreditDisplay.tsx";import { LeftSideMetadataResultContent, MetadataPickMenu, MetadataSearchCombobox, MetadataServiceScore, type MetadataPartials } from "./MetadataResults.tsx";import { albumSearchToMusicService, creditSchema, type Credit } from "../../../core/Atomic.ts";import { MusicServiceIndicators } from "../musicServices/MusicServiceIndicators.tsx";import { CountryFlag } from "../Country.tsx";import dayjs from "dayjs";
const albumPartials: MetadataPartials<AlbumSearchResult> = { album: { label: 'Album only', pick: ({ artists, ...rest }) => rest },};
export const AlbumSearchResultItem = (props: { data: AlbumSearchResult, onPick?: (val: AlbumSearchResult) => void }) => {
const { name, albumType, albumTypeHint, metadata = [], artists = [], country, date, } = props.data; const albumHints: string[] = []; if(albumType !== undefined) { albumHints.push(albumType); } if(albumTypeHint !== undefined) { albumHints.push(albumTypeHint); }
let artistTags: React.JSX.Element | undefined = undefined; if(artists.length > 0) { artistTags = <ArtistCreditTags data={artists} /> }
const countryElm: React.JSX.Element | undefined = country !== undefined ? <CountryFlag iso={country} tooltip={(val) => `Released in ${val}`}/> : undefined; let locationDateInfo: React.JSX.Element | undefined = undefined; if(countryElm !== undefined && date === undefined) { locationDateInfo = countryElm; } else if(countryElm !== undefined && date !== undefined) { locationDateInfo = <Badge variant="outline">{countryElm}<Span>{dayjs(date).format('YYYY')}</Span></Badge> }
const smallMetadataServiceScore = <Badge hideFrom="sm" variant="subtle" size="sm"><MetadataServiceScore {...props.data}/></Badge>
return ( <HStack gap="4" flexGrow="1"> <Box hideBelow="sm"><LeftSideMetadataResultContent {...props.data}/></Box> <Stack gap="1" flexGrow="1"> {smallMetadataServiceScore} <Box display="flow-root"> <Box float="right" marginStart="2"> <MetadataPickMenu data={props.data} partials={albumPartials} onPick={props.onPick} /> </Box> <HStack wrap="wrap"> {name}{albumHints.length > 0? <Box>({albumHints.join(' -- ')})</Box> : undefined}{locationDateInfo}<MusicServiceIndicators services={metadata}/> </HStack> </Box> {artistTags} </Stack> </HStack> )}
/** Play data to change when an album is selected. Album artists are always replaced, even when the selected album has none, so artists from a previous album are not kept. */export interface AlbumOnChange { album: Credit albumArtists: Credit[] | undefined}
export interface AlbumSearchProps { initial?: Credit onChange: (val: AlbumOnChange) => void onInputSwitch?: () => void contextData?: TrackDataCreditBase}
export const AlbumSearch = (props: AlbumSearchProps) => {
const { initial, onChange = (val) => console.log(val, 'Selected value for prop') } = props;
const [selectedItem, setSelectedItem] = useState<Credit>(initial ?? {name: ''}); const [searchQuery, setSearchQuery] = useState<AlbumSearchSimpleRequestQuery | TrackDataCreditBase>({album: ''}); const search = (query: string, context: boolean) => { const musicService = albumSearchToMusicService(query); if(musicService !== undefined) { setSearchQuery({album: query}); } else if(context) { setSearchQuery({...(props.contextData), album: {name: query}}) } else { setSearchQuery({album: query}); } };
const query = useQuery({ enabled: (`album` in searchQuery && searchQuery.album !== '') || `album` in searchQuery && typeof searchQuery.album === 'object' && (searchQuery.album?.name ?? '') !== '', ...tanQueries.metadata.album(searchQuery) });
const { collection, set } = useListCollection<AlbumSearchResult>({ initialItems: query.data?.data ?? [], itemToString: (item) => item.name, itemToValue: (item) => item.id, });
useEffect(() => { if (query.isSuccess) { set(query.data.data); } }, [query, set])
const doChange = useCallback((val: Credit & { artists?: Credit[] }) => { // drops the search-only properties of a result const album = creditSchema.parse(val); setSelectedItem(album); onChange({ album, albumArtists: creditSchema.array().optional().parse(val.artists) }); },[setSelectedItem, onChange]);
const services = selectedItem.metadata ?? []; let groupContent: React.JSX.Element | undefined = undefined; if(services.length > 0) { groupContent = <MusicServiceIndicators services={services} link={false}/> }
return ( <MetadataSearchCombobox placeholder="Type to search for albums" collection={collection} inputGroupContent={groupContent} isLoading={query.isLoading} isFetching={query.isFetching} key={selectedItem?.name} initialInput={selectedItem?.name} isError={query.isError} onChange={doChange} onInputSwitch={props.onInputSwitch} onQueryChange={search} renderItem={(item, onPick) => <AlbumSearchResultItem data={item} onPick={onPick} />} /> );}