[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): Implement float logs

FoxxMD (Jun 16, 2026, 8:15 PM UTC) a19fd42a 206fd62f

+124 -12
+62
package-lock.json
··· 119 119 "@fortawesome/free-solid-svg-icons": "^6.7.2", 120 120 "@fortawesome/react-fontawesome": "^0.2.6", 121 121 "@gr2m/net-interceptor": "^1.0.0", 122 + "@react-hook/window-size": "^3.1.1", 122 123 "@react-nano/use-event-source": "^0.16.0", 123 124 "@reduxjs/toolkit": "^2.11.2", 124 125 "@storybook/addon-a11y": "^10.4.3", ··· 3778 3779 "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", 3779 3780 "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", 3780 3781 "license": "BSD-3-Clause" 3782 + }, 3783 + "node_modules/@react-hook/debounce": { 3784 + "version": "3.0.0", 3785 + "resolved": "https://registry.npmjs.org/@react-hook/debounce/-/debounce-3.0.0.tgz", 3786 + "integrity": "sha512-ir/kPrSfAzY12Gre0sOHkZ2rkEmM4fS5M5zFxCi4BnCeXh2nvx9Ujd+U4IGpKCuPA+EQD0pg1eK2NGLvfWejag==", 3787 + "dev": true, 3788 + "license": "MIT", 3789 + "dependencies": { 3790 + "@react-hook/latest": "^1.0.2" 3791 + }, 3792 + "peerDependencies": { 3793 + "react": ">=16.8" 3794 + } 3795 + }, 3796 + "node_modules/@react-hook/event": { 3797 + "version": "1.2.6", 3798 + "resolved": "https://registry.npmjs.org/@react-hook/event/-/event-1.2.6.tgz", 3799 + "integrity": "sha512-JUL5IluaOdn5w5Afpe/puPa1rj8X6udMlQ9dt4hvMuKmTrBS1Ya6sb4sVgvfe2eU4yDuOfAhik8xhbcCekbg9Q==", 3800 + "dev": true, 3801 + "license": "MIT", 3802 + "peerDependencies": { 3803 + "react": ">=16.8" 3804 + } 3805 + }, 3806 + "node_modules/@react-hook/latest": { 3807 + "version": "1.0.3", 3808 + "resolved": "https://registry.npmjs.org/@react-hook/latest/-/latest-1.0.3.tgz", 3809 + "integrity": "sha512-dy6duzl+JnAZcDbNTfmaP3xHiKtbXYOaz3G51MGVljh548Y8MWzTr+PHLOfvpypEVW9zwvl+VyKjbWKEVbV1Rg==", 3810 + "dev": true, 3811 + "license": "MIT", 3812 + "peerDependencies": { 3813 + "react": ">=16.8" 3814 + } 3815 + }, 3816 + "node_modules/@react-hook/throttle": { 3817 + "version": "2.2.0", 3818 + "resolved": "https://registry.npmjs.org/@react-hook/throttle/-/throttle-2.2.0.tgz", 3819 + "integrity": "sha512-LJ5eg+yMV8lXtqK3lR+OtOZ2WH/EfWvuiEEu0M3bhR7dZRfTyEJKxH1oK9uyBxiXPtWXiQggWbZirMCXam51tg==", 3820 + "dev": true, 3821 + "license": "MIT", 3822 + "dependencies": { 3823 + "@react-hook/latest": "^1.0.2" 3824 + }, 3825 + "peerDependencies": { 3826 + "react": ">=16.8" 3827 + } 3828 + }, 3829 + "node_modules/@react-hook/window-size": { 3830 + "version": "3.1.1", 3831 + "resolved": "https://registry.npmjs.org/@react-hook/window-size/-/window-size-3.1.1.tgz", 3832 + "integrity": "sha512-yWnVS5LKnOUIrEsI44oz3bIIUYqflamPL27n+k/PC//PsX/YeWBky09oPeAoc9As6jSH16Wgo8plI+ECZaHk3g==", 3833 + "dev": true, 3834 + "license": "MIT", 3835 + "dependencies": { 3836 + "@react-hook/debounce": "^3.0.0", 3837 + "@react-hook/event": "^1.2.1", 3838 + "@react-hook/throttle": "^2.2.0" 3839 + }, 3840 + "peerDependencies": { 3841 + "react": ">=16.8" 3842 + } 3781 3843 }, 3782 3844 "node_modules/@react-nano/use-event-source": { 3783 3845 "version": "0.16.0",
+1
package.json
··· 158 158 "@fortawesome/free-solid-svg-icons": "^6.7.2", 159 159 "@fortawesome/react-fontawesome": "^0.2.6", 160 160 "@gr2m/net-interceptor": "^1.0.0", 161 + "@react-hook/window-size": "^3.1.1", 161 162 "@react-nano/use-event-source": "^0.16.0", 162 163 "@reduxjs/toolkit": "^2.11.2", 163 164 "@storybook/addon-a11y": "^10.4.3",
+4 -5
src/client/AppNext.tsx
··· 13 13 import { Container, Box, Center, Splitter, useSplitter } from '@chakra-ui/react'; 14 14 import { MsSseEvent } from '../core/Api'; 15 15 import { SSEProvider } from "@flamefrontend/sse-runtime-react"; 16 - import { AppHeader, RightHeaderSwitchLogs } from './components/AppHeader'; 16 + import { AppHeader, RightHeaderFloatingLogs, RightHeaderSwitchLogs } from './components/AppHeader'; 17 17 import { NAV_LINKS, SideNavItems } from './components/SideNav'; 18 18 import { LogsFetchable } from './components/LogsNext'; 19 19 import { SplitLayout } from './components/layouts/SplitLayout'; ··· 55 55 } 56 56 57 57 const Layout = () => { 58 - const [logsEnabled, setLogsEnabled] = useState(true); 58 + //const [logsEnabled, setLogsEnabled] = useState(true); 59 59 const location = useLocation(); 60 60 return (<> 61 - <Box px="4" py="2" mb="4" pb="4" position="sticky" top="0" zIndex="1" bg="bg" borderBottomWidth="1px"><AppHeader fetchable><RightHeaderSwitchLogs logsEnabled={logsEnabled} setLogsEnabled={setLogsEnabled}/></AppHeader></Box> 61 + <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 62 <Container display="flex"> 63 63 <Box hideBelow="md" display="flex" flexDir="column" pr="2" gap="6" flexShrink="1"><SideNavItems items={NAV_LINKS} currentUrl={location.pathname}/></Box> 64 - {!logsEnabled ? <Outlet/> : <SplitLayout/>} 65 - {/* <Outlet/> */} 64 + <Outlet/> 66 65 </Container> 67 66 </>); 68 67 }
+57 -7
src/client/components/AppHeader.tsx
··· 1 - import React, { ComponentProps, useMemo, forwardRef, Fragment, useEffect, PropsWithChildren } from "react" 2 - import { Image, Heading, HStack, Link, LinkOverlay, LinkBox, Span, Flex, Box, Separator, Switch } from '@chakra-ui/react'; 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'; 3 3 import { VersionNext } from "../Version"; 4 4 import { TextMuted } from "./TextMuted"; 5 - import { DocsButton, GithubButton, HeartbeatButton, HeartbeatIcon, TerminalButton, TerminalIcon } from "./icons/ChakraIcons"; 5 + import { TerminalButton, TerminalIcon, XButton } from "./icons/ChakraIcons"; 6 6 import { MobileSidebarNav } from "./MobileMenu"; 7 + import { LuGripHorizontal, LuMinus } from "react-icons/lu" 8 + import { 9 + useWindowSize, 10 + } from '@react-hook/window-size' 11 + 12 + import { LogsFetchable } from "./LogsNext"; 7 13 8 14 export const AppTitle = (props: { fetchable?: boolean } = {}) => { 9 15 const { ··· 28 34 29 35 interface RightHeaderSwitchLogsProps { 30 36 logsEnabled?: boolean 31 - setLogsEnabled?: (val: boolean ) => void 37 + setLogsEnabled?: (val: boolean) => void 32 38 } 33 39 34 40 export const RightHeaderSwitchLogs = (props: { 35 41 logsEnabled?: boolean 36 - setLogsEnabled?: (val: boolean ) => void 42 + setLogsEnabled?: (val: boolean) => void 37 43 }) => { 38 - const {logsEnabled, setLogsEnabled} = props; 44 + const { logsEnabled, setLogsEnabled } = props; 39 45 40 46 const TerminalSwitch = setLogsEnabled !== undefined ? ( 41 47 <Switch.Root ··· 47 53 <Switch.Control> 48 54 <Switch.Thumb /> 49 55 </Switch.Control> 50 - <Switch.Label><TerminalIcon/></Switch.Label> 56 + <Switch.Label><TerminalIcon /></Switch.Label> 51 57 </Switch.Root> 52 58 ) : null; 53 59 54 60 return <HStack gap="2"> 55 61 {TerminalSwitch} 62 + </HStack> 63 + } 64 + 65 + export const RightHeaderFloatingLogs = () => { 66 + const [width, height] = useWindowSize(); 67 + 68 + return <HStack gap="2"> 69 + <FloatingPanel.Root 70 + defaultPosition={{x: width * 0.03, y: height * 0.65}} 71 + defaultSize={{ width: width * 0.95, height: height * 0.3 }} 72 + persistRect 73 + closeOnEscape 74 + lazyMount 75 + > 76 + <FloatingPanel.Trigger asChild> 77 + <TerminalButton hideBelow="md" /> 78 + </FloatingPanel.Trigger> 79 + <Portal> 80 + <FloatingPanel.Positioner> 81 + <FloatingPanel.Content> 82 + <FloatingPanel.Header> 83 + <FloatingPanel.DragTrigger> 84 + <LuGripHorizontal /> 85 + <FloatingPanel.Title>Logs</FloatingPanel.Title> 86 + </FloatingPanel.DragTrigger> 87 + <FloatingPanel.Control> 88 + <FloatingPanel.StageTrigger stage="minimized" asChild> 89 + <IconButton variant="ghost" size="2xs"> 90 + <LuMinus /> 91 + </IconButton> 92 + </FloatingPanel.StageTrigger> 93 + <FloatingPanel.CloseTrigger asChild> 94 + <XButton variant="ghost" size="2xs" /> 95 + </FloatingPanel.CloseTrigger> 96 + </FloatingPanel.Control> 97 + </FloatingPanel.Header> 98 + <FloatingPanel.Body> 99 + <LogsFetchable /> 100 + </FloatingPanel.Body> 101 + <FloatingPanel.ResizeTriggers /> 102 + </FloatingPanel.Content> 103 + </FloatingPanel.Positioner> 104 + </Portal> 105 + </FloatingPanel.Root> 56 106 </HStack> 57 107 } 58 108