UNPKG

@u-wave/react-server-list

Version:
410 lines (390 loc) 12.2 kB
import React from 'react'; import PropTypes from 'prop-types'; import CircularProgress from '@mui/material/CircularProgress'; import Typography from '@mui/material/Typography'; import { jsxs, jsx, Fragment } from 'react/jsx-runtime'; import _objectSpread from '@babel/runtime/helpers/objectSpread2'; import Button from '@mui/material/Button'; import Card from '@mui/material/Card'; import CardContent from '@mui/material/CardContent'; import CardActions from '@mui/material/CardActions'; import IconButton from '@mui/material/IconButton'; import SvgIcon from '@mui/material/SvgIcon'; import ms from 'ms'; import stripIndent from 'strip-indent'; import Dialog from '@mui/material/Dialog'; import DialogTitle from '@mui/material/DialogTitle'; import DialogContent from '@mui/material/DialogContent'; import DialogActions from '@mui/material/DialogActions'; import { useTheme } from '@mui/material/styles'; import useMediaQuery from '@mui/material/useMediaQuery'; import useSWR from 'swr'; import nodeFetch from 'node-fetch'; var _CircularProgress; function Loading(_ref) { var { message } = _ref; return /*#__PURE__*/jsxs("div", { className: "usl-Loading", children: [_CircularProgress || (_CircularProgress = /*#__PURE__*/jsx(CircularProgress, { size: 300, variant: "indeterminate" })), /*#__PURE__*/jsx(Typography, { children: message })] }); } Loading.propTypes = process.env.NODE_ENV !== "production" ? { message: PropTypes.string } : {}; var _Loading$1; var Markdown = /*#__PURE__*/React.lazy(() => import('react-markdown')); /** * @typedef {object} DescriptionDialogProps * @prop {import('./hub').Server & { description: string }} server * @prop {boolean} isOpen * @prop {() => void} onCloseDescription * * @param {DescriptionDialogProps} props */ function DescriptionDialog(_ref) { var { server, isOpen, onCloseDescription } = _ref; var theme = useTheme(); var isFullScreen = useMediaQuery(theme.breakpoints.down('sm')); var contentStyle = { width: "".concat(theme.breakpoints.values.sm, "px") }; var loading = /*#__PURE__*/jsx("div", { className: "usl-DescriptionDialog-loading", style: contentStyle, children: _Loading$1 || (_Loading$1 = /*#__PURE__*/jsx(Loading, { message: "Loading description..." })) }); return /*#__PURE__*/jsxs(Dialog, { className: "usl-DescriptionDialog", open: isOpen, fullScreen: isFullScreen, onClose: onCloseDescription, children: [/*#__PURE__*/jsx(DialogTitle, { children: server.name }), /*#__PURE__*/jsx(DialogContent, { children: /*#__PURE__*/jsx(React.Suspense, { fallback: loading, children: /*#__PURE__*/jsx("div", { className: "usl-DescriptionDialog-markdown", style: contentStyle, children: /*#__PURE__*/jsx(Markdown, { children: stripIndent(server.description) }) }) }) }), /*#__PURE__*/jsxs(DialogActions, { children: [/*#__PURE__*/jsx(Button, { color: "inherit", onClick: onCloseDescription, children: "Close" }), /*#__PURE__*/jsx(Button, { color: "primary", variant: "contained", href: server.url, children: "Join" })] })] }); } DescriptionDialog.propTypes = process.env.NODE_ENV !== "production" ? { server: PropTypes.shape({ name: PropTypes.string, description: PropTypes.string, url: PropTypes.string }).isRequired, isOpen: PropTypes.bool, onCloseDescription: PropTypes.func.isRequired } : {}; function CurrentMedia(_ref) { var { media } = _ref; return /*#__PURE__*/jsxs("div", { className: "usl-CurrentMedia", children: [/*#__PURE__*/jsx("div", { className: "usl-CurrentMedia-image", style: { backgroundImage: "url(".concat(JSON.stringify(media.thumbnail), ")") } }), /*#__PURE__*/jsxs("div", { className: "usl-CurrentMedia-nowPlaying", children: [/*#__PURE__*/jsx("p", { className: "usl-CurrentMedia-title", children: media.title }), /*#__PURE__*/jsx("p", { className: "usl-CurrentMedia-artist", children: media.artist })] })] }); } CurrentMedia.propTypes = process.env.NODE_ENV !== "production" ? { media: PropTypes.shape({ thumbnail: PropTypes.string.isRequired, title: PropTypes.string.isRequired, artist: PropTypes.string.isRequired }).isRequired } : {}; var _path, _SvgIcon, _CardContent, _WarningIcon; var mdiAlert = 'M13 14H11V9H13M13 18H11V16H13M1 21H23L12 2L1 21Z'; var mdiMenu = 'M3,6H21V8H3V6M3,11H21V13H3V11M3,16H21V18H3V16Z'; var { useCallback, useState } = React; var downTimeout$1 = ms('10 minutes'); /** * @param {import('@mui/material/SvgIcon').SvgIconProps} props */ function WarningIcon(props) { return /*#__PURE__*/jsx(SvgIcon, _objectSpread(_objectSpread({}, props), {}, { // eslint-disable-line react/jsx-props-no-spreading style: { height: 16, width: 16, verticalAlign: 'sub' }, children: _path || (_path = /*#__PURE__*/jsx("path", { d: mdiAlert })) })); } /** * @typedef {object} WarningTextProps * @prop {import('react').ReactNode} children * * @param {WarningTextProps} props */ function WarningText(_ref) { var { children } = _ref; return /*#__PURE__*/jsx(Typography, { variant: "body1", style: { color: '#ed404f' }, children: children }); } WarningText.propTypes = process.env.NODE_ENV !== "production" ? { children: PropTypes.node.isRequired } : {}; /** @param {string} since */ function timedOutMessage(since) { return " This server may be down. It has not responded for ".concat(since, "."); } /** * @param {import('./hub').Server} server * @return {server is { description: string }} */ function hasDescription(server) { return typeof server.description === 'string'; } /** * @typedef {object} ServerThumbnailProps * @prop {import('./hub').Server} server * @prop {import('./hub').Media} [media] * * @param {ServerThumbnailProps} props */ function ServerThumbnail(_ref2) { var { server, media } = _ref2; var [isOpen, setDescriptionOpen] = useState(false); var onOpenDescription = useCallback( /** @param {React.MouseEvent<HTMLButtonElement>} event */ event => { event.preventDefault(); event.stopPropagation(); setDescriptionOpen(true); }, []); var onCloseDescription = useCallback(() => { setDescriptionOpen(false); }, []); return /*#__PURE__*/jsx("div", { className: "usl-ServerThumbnail", children: /*#__PURE__*/jsxs(Card, { children: [/*#__PURE__*/jsx(CardContent, { children: /*#__PURE__*/jsxs("div", { className: "usl-ServerThumbnail-header", children: [/*#__PURE__*/jsxs("div", { children: [/*#__PURE__*/jsx(Typography, { variant: "h5", children: server.name }), /*#__PURE__*/jsx(Typography, { variant: "body2", children: server.subtitle })] }), server.description && /*#__PURE__*/jsx(IconButton, { "aria-label": "View description for ".concat(server.name), onClick: onOpenDescription, children: _SvgIcon || (_SvgIcon = /*#__PURE__*/jsx(SvgIcon, { children: /*#__PURE__*/jsx("path", { d: mdiMenu }) })) })] }) }), media ? /*#__PURE__*/jsx("a", { href: server.url, className: "usl-ServerThumbnail-link", children: /*#__PURE__*/jsx(CurrentMedia, { media: media }) }) : /*#__PURE__*/jsxs(Fragment, { children: [/*#__PURE__*/jsx("a", { href: server.url, className: "usl-ServerThumbnail-link", children: _CardContent || (_CardContent = /*#__PURE__*/jsx(CardContent, { className: "usl-ServerThumbnail-nobodyPlaying", children: /*#__PURE__*/jsx(Typography, { children: "Nobody is playing!" }) })) }), /*#__PURE__*/jsx(CardActions, { className: "usl-ServerThumbnail-actions", children: /*#__PURE__*/jsx(Button, { variant: "contained", color: "primary", href: server.url, children: "Join" }) })] }), server.timeSincePing >= downTimeout$1 ? /*#__PURE__*/jsx(CardContent, { children: /*#__PURE__*/jsxs(WarningText, { children: [_WarningIcon || (_WarningIcon = /*#__PURE__*/jsx(WarningIcon, {})), timedOutMessage(ms(server.timeSincePing, { long: true }))] }) }) : null, hasDescription(server) ? /*#__PURE__*/jsx(DescriptionDialog, { server: server, isOpen: isOpen, onCloseDescription: onCloseDescription }) : null] }) }); } ServerThumbnail.propTypes = process.env.NODE_ENV !== "production" ? { server: PropTypes.shape({ name: PropTypes.string, subtitle: PropTypes.string, description: PropTypes.string, timeSincePing: PropTypes.number, url: PropTypes.string }).isRequired, media: PropTypes.object // eslint-disable-line react/forbid-prop-types } : {}; var _Typography; function ServerList(_ref) { var { servers } = _ref; return /*#__PURE__*/jsx("div", { className: "usl-ServerList", children: servers.length === 0 ? _Typography || (_Typography = /*#__PURE__*/jsx(Typography, { children: "No servers are currently available." })) : servers.map(server => /*#__PURE__*/jsx(ServerThumbnail, { server: server, media: server.booth && server.booth.media }, server.url)) }); } ServerList.propTypes = process.env.NODE_ENV !== "production" ? { servers: PropTypes.arrayOf(PropTypes.object).isRequired } : {}; var fetch = global.fetch || nodeFetch; var downTimeout = ms('10 minutes'); /** * * * @typedef {object} Media * @prop {string} title - Title of the media. * @prop {string} artist - Artist, creator, or uploader of the media. * @prop {string} thumbnail - A full HTTP(S) URL to a thumbnail for the media. */ /** * @typedef {object} Booth * @prop {string} dj - Username of the current DJ. * @prop {Media} media - The currently playing media. */ /** * @typedef {object} Server * @prop {string} name - Name of the server. * @prop {string} subtitle - A short description for the server. * @prop {string} [description] - Long-form description for the server. May contain Markdown. * @prop {string} url - Web-accessible URL to this server, * hosting eg. the web client or a home page. * @prop {number} timeSincePing - Time in milliseconds since the most recent update from this * server. * @prop {Booth} [booth] */ /** * @param {string} hubServer - URL of the announce server. * @return {Promise<Server[]>} */ function loadServers(hubServer) { // eslint-disable-line import/prefer-default-export return fetch(hubServer).then(response => /** @type {Promise<{ servers: Server[] }>} */response.json()).then(state => state.servers.sort((a, b) => { if (a.timeSincePing >= downTimeout) { return 1; } if (b.timeSincePing >= downTimeout) { return -1; } return 0; })); } /** * @param {string} hub * @return {{ data?: import('./hub').Server[], error?: Error }} */ function useServers(hub) { return useSWR(hub, loadServers, { refreshInterval: 30000 }); } var _Loading; function Container(_ref) { var { hub = 'https://announce.u-wave.net/' } = _ref; var { data, error } = useServers(hub); if (error) { return /*#__PURE__*/jsx(Loading, { message: error.message }); } if (data) { return /*#__PURE__*/jsx(ServerList, { servers: data }); } return _Loading || (_Loading = /*#__PURE__*/jsx(Loading, { message: "Loading available servers..." })); } Container.propTypes = process.env.NODE_ENV !== "production" ? { /** * URL of the announce server to use to discover üWave servers. */ hub: PropTypes.string } : {}; export { Container, ServerList, loadServers, useServers }; //# sourceMappingURL=u-wave-react-server-list.js.map