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.
5.9 kB · 137 lines
TSX
at strict
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137import React, { type ComponentProps, Fragment, useEffect } from "react"import { Stack, Heading, Separator, HStack, Flex, Badge, Card, LinkOverlay, LinkBox } from '@chakra-ui/react';import { type ComponentCommonApiJson, isComponentClientApiJson, isComponentSourceApiJson, type MsSseEvent, type MsSseEventPayload } from "../../../core/Api.js";import { Link } from "react-router";import { TextMuted } from "../TextMuted.js";import { capitalize } from "../../../core/StringUtils.js";import { ChevronRightButton, IdleIcon } from "../icons/ChakraIcons.js";import { PlayersContainer, PlayersContainerFetchable } from "../chakraPlayer/Player.js";import { type QueryFunctionContext, useQuery, useQueryClient } from '@tanstack/react-query';import { ErrorAlert } from "../ErrorAlert";import { useSSEContext, useSSEAnyEvent} from "@flamefrontend/sse-runtime-react";import { CountIndicatorStreamable, DeadLetterIndicatorStreamable, QueuedIndicatorStreamable } from "./Stats.js";import { ComponentStateBadge } from "../Badges.js";import { MSErrorBoundary } from "../ErrorBoundary.js";
const presentPlayersContainerProps: ComponentProps<typeof Stack> = {//paddingTop: '2',//borderTopWidth: '1px'};
export const MSComponentSummary = (props: { data: ComponentCommonApiJson, fetchable?: boolean }) => { const { data, fetchable } = props; let sleepingRender: React.JSX.Element = null;
const cardHeaderProps: Card.HeaderProps = {}; const isClient = isComponentClientApiJson(data); if(isComponentSourceApiJson(data)) { const { sleeping } = data; if(sleeping) { sleepingRender = <IdleIcon animated/>; } } const body = (<Card.Body px="3" py="2" paddingTop="3"> {fetchable ? <PlayersContainerFetchable data={data} nowPlaying={isClient} stack={presentPlayersContainerProps}/> : <PlayersContainer data={data} nowPlaying={isClient} live={fetchable} stack={presentPlayersContainerProps}/>} </Card.Body>);
return ( <MSErrorBoundary> <Card.Root variant="subtle"> <LinkBox> <Card.Header {...cardHeaderProps}> <Flex justify="space-between"> <Heading>{data.name}</Heading> <Stack justify="flex-start" alignItems="flex-end"> <HStack gap="2"> {sleepingRender} <ComponentStateBadge maxWidth="fit-content" componentId={props.data.id} live data={props.data} /> <Separator orientation="vertical" height="4" /> <LinkOverlay asChild> <Link to={`components/${props.data.id}`}> <ChevronRightButton variant="ghost" size="xs"/> </Link> </LinkOverlay> </HStack> {/* <Text textStyle="sm" textAlign="end">{props.data.status}</Text> */} </Stack> </Flex> <TextMuted textStyle="md">{capitalize(data.type)} <Badge color={data.mode === 'client' ? 'var(--chakra-colors-purple-fg)' : 'var(--chakra-colors-pink-fg)'} size="sm" variant="outline">{capitalize(data.mode)}</Badge></TextMuted> <QuickStatsSource data={data} streamable={props.fetchable} /> </Card.Header> </LinkBox> <MSErrorBoundary>{body}</MSErrorBoundary> </Card.Root> </MSErrorBoundary>)}
//colorPalette={data.mode === 'client' ? 'purple' : 'pink'}// color={data.mode === 'client' ? 'purple' : 'pink'}
const QuickStatsSource = (props: { data: ComponentCommonApiJson, streamable?: boolean }) => ( <Fragment> <HStack gap="2"> <QueuedIndicatorStreamable data={props.data} as="text"/> <Separator orientation="vertical" height="4" /> <DeadLetterIndicatorStreamable data={props.data} as="text"/> <HStack gap="2" hideBelow="sm"> <Separator orientation="vertical" height="4" /> <CountIndicatorStreamable data={props.data} as="text"/> </HStack> </HStack> </Fragment> )
export const MSComponentSummaryFetchable = (props: {componentId: number, data: ComponentCommonApiJson}) => { const { componentId, data: initData } = props; const queryClient = useQueryClient(); const qKey = ['components', componentId, 'summary']; useEffect(() => { if (initData !== undefined && queryClient.getQueryData(qKey) === undefined) { queryClient.setQueryData(['components', componentId, 'summary'], initData); } }, [initData]);
const client = useSSEContext<MsSseEvent>(); useSSEAnyEvent(client, (payload) => { if('componentId' in (payload.data as object) && (payload.data as Record<string, any>).componentId === componentId) { switch(payload.type) { case 'componentUpdate': queryClient.setQueryData(['components', componentId, 'summary'], (old: ComponentCommonApiJson) => { const componentData = payload.data as MsSseEventPayload<Partial<ComponentCommonApiJson>>; return {...old, ...componentData.data}; }); } } });
const { isPending, isError, data, error } = useQuery({ queryKey: ['components', componentId, 'summary'], queryFn: queryFn, structuralSharing: false, staleTime: Infinity, });
if (isError) { return <ErrorAlert error={error} /> }
if(!isPending) { return <MSComponentSummary data={data} fetchable/> }}
type ComponentSummaryQueryKey = ['components', number, 'summary'];const queryFn = async (context: QueryFunctionContext<ComponentSummaryQueryKey>) => { return {} as ComponentCommonApiJson;}