[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
0

Configure Feed

Select the types of activity you want to include in your feed.

more imrovements

FoxxMD (Mar 18, 2026, 7:04 PM UTC) 53feae0f 0941ccc0

+53 -17
+14
src/client/components/DateDisplay.tsx
··· 1 + import { Text } from "@chakra-ui/react" 2 + import dayjs, {Dayjs} from 'dayjs'; 3 + import { shortTodayAwareFormat } from "../../core/TimeUtils"; 4 + 5 + export interface DateDisplayProps { 6 + date?: string | Dayjs 7 + prefix?: string 8 + } 9 + export const ShortDateDisplay = (props: DateDisplayProps) => { 10 + if(props.date === undefined) { 11 + return <Text textStyle="xs" color="fg.muted">(No Date)</Text> 12 + } 13 + return <Text textStyle="xs" color="fg.muted">{`${props.prefix !== undefined ? `${props.prefix} ` : ''}${shortTodayAwareFormat(typeof props.date === 'string' ? dayjs(props.date) : props.date)}`}</Text> 14 + }
+23 -15
src/client/components/List.tsx
··· 1 - import { Accordion, For, Span, Stack, Text, Box, AbsoluteCenter, Button } from '@chakra-ui/react'; 1 + import { Accordion, For, Span, Stack, Text, Box, AbsoluteCenter, Button, Separator, HStack } from '@chakra-ui/react'; 2 + import { JsonPlayObject } from '../../core/Atomic'; 3 + import { ShortDateDisplay } from './DateDisplay'; 4 + import { truncateStringToLength } from '../../core/StringUtils'; 5 + import { TextMuted } from './TextMuted'; 6 + 7 + const shortArtist = truncateStringToLength(20); 2 8 3 9 const items = [ 4 10 { value: 'a', title: 'First Item', text: 'Some value 1..' }, ··· 6 12 { value: 'c', title: 'Third Item', text: 'Some value 3...' }, 7 13 ]; 8 14 15 + export interface ActivityLogProps { 16 + plays: JsonPlayObject[] 17 + } 9 18 10 - export const CList = (props) => 11 - ( 12 - <Stack gap="8"> 19 + // shortArtist(item.data.artists.join(' / ')) 20 + 21 + export const CList = (props: ActivityLogProps) => { 22 + return ( 13 23 <Stack gap="2"> 14 24 <Text fontWeight="semibold">Today</Text> 15 - <Accordion.Root variant="enclosed" collapsible defaultValue={['b']}> 16 - {items.map((item, index) => ( 17 - <Accordion.Item key={index} value={item.value}> 25 + <Accordion.Root variant="enclosed" collapsible> 26 + {props.plays.map((item, index) => ( 27 + <Accordion.Item key={index} value={index.toString()}> 18 28 <Box position="relative"> 19 29 <Accordion.ItemTrigger> 20 30 <Accordion.ItemIndicator /> 21 31 <Stack gap="1"> 22 - <Span flex="1">{item.title}</Span> 23 - <Text fontSize="sm" color="fg.muted"> 24 - Click to expand 25 - </Text> 32 + <Span flex="1">{item.data.track}</Span> 33 + <TextMuted overflow="hidden" textOverflow="ellipsis">{item.data.artists.join(' / ')}</TextMuted> 34 + <HStack gap="1"><ShortDateDisplay date={item.data.playDate} prefix="Played"/><Separator orientation="vertical" height="4" /><TextMuted>{item.meta?.source}</TextMuted></HStack> 26 35 </Stack> 27 36 </Accordion.ItemTrigger> 28 37 <AbsoluteCenter axis="vertical" insetEnd="0" padding="1em"> ··· 32 41 </AbsoluteCenter> 33 42 </Box> 34 43 <Accordion.ItemContent> 35 - <Accordion.ItemBody borderTopColor="gray.border" borderTopWidth="1px">{item.text}</Accordion.ItemBody> 44 + <Accordion.ItemBody borderTopColor="gray.border" borderTopWidth="1px">{item.data.track}</Accordion.ItemBody> 36 45 </Accordion.ItemContent> 37 46 </Accordion.Item> 38 47 ))} 39 48 </Accordion.Root> 40 49 </Stack> 41 - </Stack> 42 - 43 - ); 50 + ); 51 + }
+11
src/client/components/TextMuted.tsx
··· 1 + import { Text as ChakraText } from "@chakra-ui/react" 2 + import { ComponentProps } from "react" 3 + 4 + export const TextMuted = (props: ComponentProps<typeof ChakraText>) => { 5 + const { 6 + children, 7 + ...rest 8 + } = props; 9 + return <ChakraText textStyle="xs" color="fg.muted" {...rest}>{props.children} 10 + </ChakraText> 11 + }
+5 -2
src/stories/List.stories.tsx
··· 4 4 import { fn } from 'storybook/test'; 5 5 import { CList } from "../client/components/List"; 6 6 import {Provider} from "../client/components/Provider"; 7 + import { generateJsonPlays } from "../backend/tests/utils/PlayTestUtils.js"; 8 + 9 + 7 10 8 11 // More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export 9 12 const meta = preview.meta({ ··· 16 19 // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs 17 20 tags: ['autodocs'], 18 21 // More on argTypes: https://storybook.js.org/docs/api/argtypes 19 - argTypes: { 20 - backgroundColor: { control: 'color' }, 22 + args: { 23 + plays: generateJsonPlays(2, undefined, {source: 'Spotify'}), 21 24 }, 22 25 decorators: [ 23 26 (Story) => (<Provider><Story/></Provider>),