/** * Embeddable public dashboard, served as /embed (embed.html) for iframes on other sites. * * Its own entry on purpose: it is the only document that may be framed (see embedFramingMiddleware) so it must never * be able to render the logged in dashboard. No router, no auth client, no cookies and nothing stored. * * Query params: * * * type -- recent (default, Now Playing then Recent) | now (only Now Playing) | stats (counts and components) * * n -- show at most this many recent plays * * theme -- light | dark, default follows the visitor's system * * transparent -- 1 for no background * * cta -- 0 to hide the multi-scrobbler logo and link at the bottom * * Posts {type: 'multi-scrobbler-embed', height} to the parent whenever its height changes so the host page can resize the iframe. * */ import React, { useLayoutEffect, useRef } from 'react'; import ReactDOM from 'react-dom/client'; import dayjs from 'dayjs'; import utc from 'dayjs/plugin/utc.js'; import relativeTime from 'dayjs/plugin/relativeTime.js'; import { Box, Flex, Spinner, Stack, Text } from '@chakra-ui/react'; import { Provider } from './components/Provider'; import { CallToAction, ComponentStats, CountStats, isPublicDashboardEnabled, RecentPublicPlaysList, usePublicDashboard } from './components/PublicDashboard'; dayjs.extend(utc); dayjs.extend(relativeTime); const params = new URLSearchParams(window.location.search); const typeParam = params.get('type'); const type = typeParam === 'now' || typeParam === 'stats' ? typeParam : 'recent'; const themeParam = params.get('theme'); const theme = themeParam === 'light' || themeParam === 'dark' ? themeParam : undefined; const transparent = params.get('transparent') === '1'; const limitParam = Number.parseInt(params.get('n') ?? '', 10); const limit = Number.isInteger(limitParam) && limitParam > 0 ? limitParam : undefined; const cta = params.get('cta') !== '0'; const Message = (props: {children: string}) => {props.children}; const EmbedContent = () => { const {isPending, isError, data} = usePublicDashboard(); if (isPending) { return ; } if (isError || !isPublicDashboardEnabled(data.data)) { return Not available; } const {recent, nowPlaying} = data.data; switch (type) { case 'now': if (nowPlaying === undefined) { return Not available; } return nowPlaying === null ? Not playing anything right now : ; case 'stats': return ; default: if (recent === undefined && nowPlaying === undefined) { return Not available; } return ; } } const Embed = () => { const ref = useRef(null); useLayoutEffect(() => { const elm = ref.current; if (elm === null || window.parent === window) { return; } // only a number is sent, so any parent origin is fine const observer = new ResizeObserver(() => window.parent.postMessage({type: 'multi-scrobbler-embed', height: Math.ceil(elm.getBoundingClientRect().height)}, '*')); observer.observe(elm); return () => observer.disconnect(); }, []); return ( {transparent ? : undefined} {/* a bit smaller than the embed's text */} {cta ? : undefined} ); } ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( {/* own storage key so the dashboard's theme override is never used here */} );