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

feat(ui): Global sse status indicator

FoxxMD (Jun 23, 2026, 7:51 PM UTC) 37bb5073 1237056d

+68 -4
+1 -1
src/client/AppNext.tsx
··· 59 59 //const [logsEnabled, setLogsEnabled] = useState(true); 60 60 const location = useLocation(); 61 61 return (<> 62 - <Box px="4" py="2" mb="4" pb="4" position="sticky" top="0" zIndex="1" bg="bg" borderBottomWidth="1px"><AppHeader fetchable><RightHeaderFloatingLogs/></AppHeader></Box> 62 + <Box px="4" py="2" mb="4" pb="4" position="sticky" top="0" zIndex="1" bg="bg" borderBottomWidth="1px"><AppHeader fetchable><RightHeaderFloatingLogs streamable/></AppHeader></Box> 63 63 <Container display="flex"> 64 64 <Box hideBelow="md" display="flex" flexDir="column" pr="2" gap="6" flexShrink="1"><SideNavItems items={NAV_LINKS} currentUrl={location.pathname}/></Box> 65 65 <Outlet/>
+12
src/client/index-next.css
··· 22 22 } 23 23 } 24 24 25 + @keyframes icon-fade-half { 26 + 0% { 27 + opacity: 1; 28 + } 29 + 60% { 30 + opacity: 0.5; 31 + } 32 + 100% { 33 + opacity: 1; 34 + } 35 + } 36 + 25 37 .chakra-stack .MuiLinearProgress-dashed { 26 38 /* animation: dashed-player 3s infinite linear !important; 27 39 background-image: radial-gradient(var(--chakra-colors-color-palette-solid) 0%, var(--chakra-colors-color-palette-solid) 16%, transparent 42%);
+54 -2
src/client/components/AppHeader.tsx
··· 1 - import React, { ComponentProps, useMemo, forwardRef, Fragment, useEffect, PropsWithChildren, useCallback, useRef } from "react" 2 - import { Image, Heading, HStack, Link, LinkOverlay, LinkBox, Span, Flex, Box, Separator, Switch, FloatingPanel, Portal, Text, IconButton } from '@chakra-ui/react'; 1 + import React, { ComponentProps, useMemo, forwardRef, Fragment, useEffect, PropsWithChildren, useCallback, useRef, useState } from "react" 2 + import { Image, Heading, HStack, Link, LinkOverlay, LinkBox, Span, Flex, Box, Separator, Switch, FloatingPanel, Portal, Text, IconButton, Status, Stack } from '@chakra-ui/react'; 3 3 import { VersionNext } from "../Version"; 4 4 import { TextMuted } from "./TextMuted"; 5 5 import { TerminalButton, TerminalIcon, XButton } from "./icons/ChakraIcons"; ··· 12 12 13 13 import { LogsFetchable } from "./LogsNext"; 14 14 import { Ripple } from "./icons/AnimatedIcons"; 15 + import { useSSEContext, useSSEStatus } from "@flamefrontend/sse-runtime-react"; 16 + import { ToggleTip } from "./ToggleTip"; 17 + import { ErrorLike } from "../../core/Atomic"; 18 + import { ErrorAlert } from "./ErrorAlert"; 15 19 16 20 export const AppTitle = (props: { fetchable?: boolean } = {}) => { 17 21 const { ··· 66 70 </HStack> 67 71 } 68 72 73 + export const SSEStatus = (props: {live?: boolean, status?: ReturnType<typeof useSSEStatus>['status'], error?: ErrorLike}) => { 74 + 75 + let status = props.status ?? 'closed' 76 + let error: ErrorLike = props.error; 77 + 78 + if(props.live) { 79 + const client = useSSEContext(); 80 + const { status: sseStatus, error: sseError } = useSSEStatus(client); 81 + status = sseStatus; 82 + error = sseError as ErrorLike; 83 + } 84 + let content: string | React.JSX.Element; 85 + let color: ComponentProps<typeof Status.Indicator>['colorPalette']; 86 + switch(status) { 87 + case 'error': 88 + case 'closed': 89 + color = 'red'; 90 + content = ( 91 + <Stack> 92 + <Text>Live events connection is <strong>{status}</strong></Text> 93 + {error !== undefined ? <ErrorAlert error={error}/> : null} 94 + </Stack> 95 + ); 96 + break; 97 + case 'idle': 98 + case 'open': 99 + color = 'green'; 100 + content = 'Currently receiving live events'; 101 + break; 102 + case 'connecting': 103 + case 'reconnecting': 104 + color = 'orange'; 105 + content = 'Reconnecting to live events...'; 106 + break; 107 + } 108 + 109 + return ( 110 + <ToggleTip content={content}> 111 + <IconButton variant="ghost" size="xs"> 112 + <Status.Root> 113 + <Status.Indicator colorPalette={color} style={color === 'green' ? {animation: 'icon-fade-half 3s infinite linear'} : undefined}/> 114 + </Status.Root> 115 + </IconButton> 116 + </ToggleTip> 117 + ) 118 + } 119 + 69 120 export const RightHeaderFloatingLogs = (props: {streamable?: boolean}) => { 70 121 const [width, height] = useWindowSize(); 71 122 72 123 return <HStack gap="2"> 124 + <Box marginRight="2"><SSEStatus live={props.streamable}/></Box> 73 125 <FloatingPanel.Root 74 126 defaultPosition={{x: width * 0.03, y: height * 0.65}} 75 127 defaultSize={{ width: width * 0.95, height: height * 0.3 }}
+1 -1
src/client/components/msComponent/MSComponentDetailed.tsx
··· 121 121 <Box marginEnd="auto"> 122 122 <MSComponentType data={props.data}/> 123 123 </Box> 124 - <HStack truncate>{props.data.status}{sleepingRender}</HStack> 124 + <HStack truncate>{sleepingRender}{props.data.status}</HStack> 125 125 </Wrap> 126 126 <Flex justifyContent="flex-end" rowGap="6" flexDirection="row-reverse" wrap="wrap"> 127 127 <Card.Root bgColor="bg.subtle" size="sm">