@u-wave/react-server-list
Version:
Show a list of üWave servers.
410 lines (390 loc) • 12.2 kB
JavaScript
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