import { Drawer, ListItemButton, ListItemIcon, ListItemText, Toolbar, Divider, List, Alert, AlertTitle, Badge, CircularProgress, Link, ListSubheader, Portal, Tooltip, Typography, Box, IconButton, Button, } from "@mui/material"; import * as React from "react"; import { useContext, useState } from "react"; import ChatBubbleOutlineIcon from "@mui/icons-material/ChatBubbleOutline"; import Person from "@mui/icons-material/Person"; import SettingsIcon from "@mui/icons-material/Settings"; import AddIcon from "@mui/icons-material/Add"; import { useLocation, useNavigate } from "react-router-dom"; import { ChatBubble, MoreVert, NotificationsOffOutlined, Send } from "@mui/icons-material"; import ArticleIcon from "@mui/icons-material/Article"; import { Trans, useTranslation } from "react-i18next"; import CelebrationIcon from "@mui/icons-material/Celebration"; import SubscribeDialog from "./SubscribeDialog"; import { openUrl, topicDisplayName, topicUrl } from "../app/utils"; import routes from "./routes"; import { ConnectionState } from "../app/Connection"; import subscriptionManager from "../app/SubscriptionManager"; import notifier from "../app/Notifier"; import config from "../app/config"; import session from "../app/Session"; import accountApi, { Permission, Role } from "../app/AccountApi"; import UpgradeDialog from "./UpgradeDialog"; import { AccountContext } from "./App"; import { PermissionDenyAll, PermissionRead, PermissionReadWrite, PermissionWrite } from "./ReserveIcons"; import { SubscriptionPopup } from "./SubscriptionPopup"; const navWidth = 280; const Navigation = (props) => { const navigationList = ; return ( {/* Mobile drawer; only shown if menu icon clicked (mobile open) and display is small */} {navigationList} {/* Big screen drawer; persistent, shown if screen is big */} {navigationList} ); }; Navigation.width = navWidth; const NavList = (props) => { const { t } = useTranslation(); const navigate = useNavigate(); const location = useLocation(); const { account } = useContext(AccountContext); const [subscribeDialogKey, setSubscribeDialogKey] = useState(0); const [subscribeDialogOpen, setSubscribeDialogOpen] = useState(false); const handleSubscribeReset = () => { setSubscribeDialogOpen(false); setSubscribeDialogKey((prev) => prev + 1); }; const handleSubscribeSubmit = (subscription) => { console.log(`[Navigation] New subscription: ${subscription.id}`, subscription); handleSubscribeReset(); navigate(routes.forSubscription(subscription)); }; const handleAccountClick = () => { accountApi.sync(); // Dangle! navigate(routes.account); }; const isAdmin = account?.role === Role.ADMIN; const isPaid = account?.billing?.subscription; const showUpgradeBanner = config.enable_payments && !isAdmin && !isPaid; const showSubscriptionsList = props.subscriptions?.length > 0; const [showNotificationPermissionRequired, setShowNotificationPermissionRequired] = useState(notifier.notRequested()); const [showNotificationPermissionDenied, setShowNotificationPermissionDenied] = useState(notifier.denied()); const showNotificationIOSInstallRequired = notifier.iosSupportedButInstallRequired(); const showNotificationBrowserNotSupportedBox = !showNotificationIOSInstallRequired && !notifier.browserSupported(); const showNotificationContextNotSupportedBox = notifier.browserSupported() && !notifier.contextSupported(); // Only show if notifications are generally supported in the browser const refreshPermissions = () => { setShowNotificationPermissionRequired(notifier.notRequested()); setShowNotificationPermissionDenied(notifier.denied()); }; const alertVisible = showNotificationPermissionRequired || showNotificationPermissionDenied || showNotificationIOSInstallRequired || showNotificationBrowserNotSupportedBox || showNotificationContextNotSupportedBox; return ( <> {showNotificationPermissionRequired && } {showNotificationPermissionDenied && } {showNotificationBrowserNotSupportedBox && } {showNotificationContextNotSupportedBox && } {showNotificationIOSInstallRequired && } {alertVisible && } {!showSubscriptionsList && ( navigate(routes.app)} selected={location.pathname === config.app_root}> )} {showSubscriptionsList && ( <> {t("nav_topics_title")} navigate(routes.app)} selected={location.pathname === config.app_root}> )} {session.exists() && ( )} navigate(routes.settings)} selected={location.pathname === routes.settings}> openUrl("/docs")}> props.onPublishMessageClick()}> setSubscribeDialogOpen(true)}> {showUpgradeBanner && } ); }; const UpgradeBanner = () => { const { t } = useTranslation(); const [dialogKey, setDialogKey] = useState(0); const [dialogOpen, setDialogOpen] = useState(false); const handleClick = () => { setDialogKey((k) => k + 1); setDialogOpen(true); }; return ( setDialogOpen(false)} /> ); }; const SubscriptionList = (props) => { const sortedSubscriptions = props.subscriptions .filter((s) => !s.internal) .sort((a, b) => (topicUrl(a.baseUrl, a.topic) < topicUrl(b.baseUrl, b.topic) ? -1 : 1)); return ( <> {sortedSubscriptions.map((subscription) => ( ))} ); }; const SubscriptionItem = (props) => { const { t } = useTranslation(); const navigate = useNavigate(); const [menuAnchorEl, setMenuAnchorEl] = useState(null); const { subscription } = props; const iconBadge = subscription.new <= 99 ? subscription.new : "99+"; const displayName = topicDisplayName(subscription); const ariaLabel = subscription.state === ConnectionState.Connecting ? `${displayName} (${t("nav_button_connecting")})` : displayName; const icon = subscription.state === ConnectionState.Connecting ? ( ) : ( ); const handleClick = async () => { navigate(routes.forSubscription(subscription)); await subscriptionManager.markNotificationsRead(subscription.id); }; return ( <> {icon} {subscription.reservation?.everyone && ( {subscription.reservation?.everyone === Permission.READ_WRITE && ( )} {subscription.reservation?.everyone === Permission.READ_ONLY && ( )} {subscription.reservation?.everyone === Permission.WRITE_ONLY && ( )} {subscription.reservation?.everyone === Permission.DENY_ALL && ( )} )} {subscription.mutedUntil > 0 && ( )} e.stopPropagation()} onClick={(e) => { e.stopPropagation(); setMenuAnchorEl(e.currentTarget); }} > setMenuAnchorEl(null)} /> ); }; const NotificationPermissionRequired = ({ refreshPermissions }) => { const { t } = useTranslation(); const requestPermission = async () => { await notifier.maybeRequestPermission(); refreshPermissions(); }; return ( {t("alert_notification_permission_required_title")} {t("alert_notification_permission_required_description")} ); }; const NotificationPermissionDeniedAlert = () => { const { t } = useTranslation(); return ( {t("alert_notification_permission_denied_title")} {t("alert_notification_permission_denied_description")} ); }; const NotificationIOSInstallRequiredAlert = () => { const { t } = useTranslation(); return ( {t("alert_notification_ios_install_required_title")} {t("alert_notification_ios_install_required_description")} ); }; const NotificationBrowserNotSupportedAlert = () => { const { t } = useTranslation(); return ( {t("alert_not_supported_title")} {t("alert_not_supported_description")} ); }; const NotificationContextNotSupportedAlert = () => { const { t } = useTranslation(); return ( {t("alert_not_supported_title")} , }} /> ); }; export default Navigation;