2022-02-25 18:46:22 +01:00
import Drawer from "@mui/material/Drawer" ;
import * as React from "react" ;
2022-02-26 16:14:43 +01:00
import { useState } from "react" ;
2022-02-25 18:46:22 +01:00
import ListItemButton from "@mui/material/ListItemButton" ;
import ListItemIcon from "@mui/material/ListItemIcon" ;
import ChatBubbleOutlineIcon from "@mui/icons-material/ChatBubbleOutline" ;
2022-12-16 04:07:04 +01:00
import Person from "@mui/icons-material/Person" ;
2022-02-25 18:46:22 +01:00
import ListItemText from "@mui/material/ListItemText" ;
import Toolbar from "@mui/material/Toolbar" ;
import Divider from "@mui/material/Divider" ;
import List from "@mui/material/List" ;
import SettingsIcon from "@mui/icons-material/Settings" ;
import AddIcon from "@mui/icons-material/Add" ;
import SubscribeDialog from "./SubscribeDialog" ;
2023-01-03 17:28:04 +01:00
import {
Alert ,
AlertTitle ,
Badge ,
CircularProgress ,
Link ,
ListItem ,
ListItemSecondaryAction ,
ListSubheader , Tooltip
} from "@mui/material" ;
2022-02-26 16:14:43 +01:00
import Button from "@mui/material/Button" ;
import Typography from "@mui/material/Typography" ;
2022-06-29 21:57:56 +02:00
import { openUrl , topicDisplayName , topicUrl } from "../app/utils" ;
2022-03-10 05:28:55 +01:00
import routes from "./routes" ;
2022-03-04 17:08:32 +01:00
import { ConnectionState } from "../app/Connection" ;
2023-01-03 17:28:04 +01:00
import { useLocation , useNavigate , useOutletContext } from "react-router-dom" ;
2022-03-07 04:37:13 +01:00
import subscriptionManager from "../app/SubscriptionManager" ;
2023-01-03 17:28:04 +01:00
import { ChatBubble , Lock , MoreVert , NotificationsOffOutlined , Public , PublicOff , Send } from "@mui/icons-material" ;
2022-03-08 17:33:17 +01:00
import Box from "@mui/material/Box" ;
import notifier from "../app/Notifier" ;
2022-03-10 05:28:55 +01:00
import config from "../app/config" ;
2022-03-11 04:58:24 +01:00
import ArticleIcon from '@mui/icons-material/Article' ;
2022-06-12 22:38:33 +02:00
import { Trans , useTranslation } from "react-i18next" ;
2022-12-16 04:07:04 +01:00
import session from "../app/Session" ;
2023-01-03 04:21:11 +01:00
import accountApi from "../app/AccountApi" ;
2023-01-03 17:28:04 +01:00
import IconButton from "@mui/material/IconButton" ;
import CloseIcon from "@mui/icons-material/Close" ;
2022-02-25 18:46:22 +01:00
2022-03-08 22:56:41 +01:00
const navWidth = 280 ;
2022-02-25 18:46:22 +01:00
const Navigation = ( props ) => {
2022-02-26 16:14:43 +01:00
const navigationList = < NavList { ... props } / > ;
2022-02-25 18:46:22 +01:00
return (
2022-05-03 01:30:29 +02:00
< Box
component = "nav"
2022-05-03 21:09:20 +02:00
role = "navigation"
2022-05-03 01:30:29 +02:00
sx = { { width : { sm : Navigation . width } , flexShrink : { sm : 0 } } }
>
2022-02-25 18:46:22 +01:00
{ /* Mobile drawer; only shown if menu icon clicked (mobile open) and display is small */ }
< Drawer
variant = "temporary"
2022-05-03 21:09:20 +02:00
role = "menubar"
2022-02-25 18:46:22 +01:00
open = { props . mobileDrawerOpen }
onClose = { props . onMobileDrawerToggle }
ModalProps = { { keepMounted : true } } // Better open performance on mobile.
sx = { {
display : { xs : 'block' , sm : 'none' } ,
'& .MuiDrawer-paper' : { boxSizing : 'border-box' , width : navWidth } ,
} }
>
{ navigationList }
< / D r a w e r >
{ /* Big screen drawer; persistent, shown if screen is big */ }
< Drawer
open
variant = "permanent"
2022-05-03 21:09:20 +02:00
role = "menubar"
2022-02-25 18:46:22 +01:00
sx = { {
display : { xs : 'none' , sm : 'block' } ,
'& .MuiDrawer-paper' : { boxSizing : 'border-box' , width : navWidth } ,
} }
>
{ navigationList }
< / D r a w e r >
2022-03-08 17:33:17 +01:00
< / B o x >
2022-02-25 18:46:22 +01:00
) ;
} ;
Navigation . width = navWidth ;
const NavList = ( props ) => {
2022-04-08 01:11:51 +02:00
const { t } = useTranslation ( ) ;
2022-03-04 22:10:04 +01:00
const navigate = useNavigate ( ) ;
const location = useLocation ( ) ;
2022-02-26 05:25:04 +01:00
const [ subscribeDialogKey , setSubscribeDialogKey ] = useState ( 0 ) ;
2022-02-25 18:46:22 +01:00
const [ subscribeDialogOpen , setSubscribeDialogOpen ] = useState ( false ) ;
2022-03-06 04:33:34 +01:00
2022-02-26 05:25:04 +01:00
const handleSubscribeReset = ( ) => {
2022-02-25 18:46:22 +01:00
setSubscribeDialogOpen ( false ) ;
2022-02-26 05:25:04 +01:00
setSubscribeDialogKey ( prev => prev + 1 ) ;
}
2022-03-06 04:33:34 +01:00
2022-03-02 03:23:12 +01:00
const handleSubscribeSubmit = ( subscription ) => {
2022-03-07 03:39:20 +01:00
console . log ( ` [Navigation] New subscription: ${ subscription . id } ` , subscription ) ;
2022-02-26 05:25:04 +01:00
handleSubscribeReset ( ) ;
2022-03-10 05:28:55 +01:00
navigate ( routes . forSubscription ( subscription ) ) ;
2022-03-08 17:33:17 +01:00
handleRequestNotificationPermission ( ) ;
2022-02-25 18:46:22 +01:00
}
2022-03-06 04:33:34 +01:00
2022-03-08 17:33:17 +01:00
const handleRequestNotificationPermission = ( ) => {
notifier . maybeRequestPermission ( granted => props . onNotificationGranted ( granted ) )
} ;
2023-01-03 04:21:11 +01:00
const handleAccountClick = ( ) => {
accountApi . sync ( ) ; // Dangle!
navigate ( routes . account ) ;
} ;
2022-03-02 22:16:30 +01:00
const showSubscriptionsList = props . subscriptions ? . length > 0 ;
2022-06-12 22:38:33 +02:00
const showNotificationBrowserNotSupportedBox = ! notifier . browserSupported ( ) ;
const showNotificationContextNotSupportedBox = notifier . browserSupported ( ) && ! notifier . contextSupported ( ) ; // Only show if notifications are generally supported in the browser
2022-03-11 00:11:12 +01:00
const showNotificationGrantBox = notifier . supported ( ) && props . subscriptions ? . length > 0 && ! props . notificationsGranted ;
2022-06-12 22:38:33 +02:00
const navListPadding = ( showNotificationGrantBox || showNotificationBrowserNotSupportedBox || showNotificationContextNotSupportedBox ) ? '0' : '' ;
2022-03-06 04:33:34 +01:00
2022-02-25 18:46:22 +01:00
return (
< >
2022-03-04 22:10:04 +01:00
< Toolbar sx = { { display : { xs : 'none' , sm : 'block' } } } / >
2022-06-12 22:38:33 +02:00
< List component = "nav" sx = { { paddingTop : navListPadding } } >
{ showNotificationBrowserNotSupportedBox && < NotificationBrowserNotSupportedAlert / > }
{ showNotificationContextNotSupportedBox && < NotificationContextNotSupportedAlert / > }
2022-03-11 00:11:12 +01:00
{ showNotificationGrantBox && < NotificationGrantAlert onRequestPermissionClick = { handleRequestNotificationPermission } / > }
2022-03-08 02:11:58 +01:00
{ ! showSubscriptionsList &&
2022-12-02 21:37:48 +01:00
< ListItemButton onClick = { ( ) => navigate ( routes . app ) } selected = { location . pathname === config . appRoot } >
2022-03-08 02:11:58 +01:00
< ListItemIcon > < ChatBubble / > < / L i s t I t e m I c o n >
2022-04-08 03:46:33 +02:00
< ListItemText primary = { t ( "nav_button_all_notifications" ) } / >
2022-03-08 02:11:58 +01:00
< / L i s t I t e m B u t t o n > }
2022-02-26 16:14:43 +01:00
{ showSubscriptionsList &&
< >
2022-04-08 03:46:33 +02:00
< ListSubheader > { t ( "nav_topics_title" ) } < / L i s t S u b h e a d e r >
2022-12-02 21:37:48 +01:00
< ListItemButton onClick = { ( ) => navigate ( routes . app ) } selected = { location . pathname === config . appRoot } >
2022-03-08 02:11:58 +01:00
< ListItemIcon > < ChatBubble / > < / L i s t I t e m I c o n >
2022-04-08 03:46:33 +02:00
< ListItemText primary = { t ( "nav_button_all_notifications" ) } / >
2022-03-08 02:11:58 +01:00
< / L i s t I t e m B u t t o n >
2022-02-26 16:14:43 +01:00
< SubscriptionList
subscriptions = { props . subscriptions }
selectedSubscription = { props . selectedSubscription }
/ >
2022-02-26 20:22:21 +01:00
< Divider sx = { { my : 1 } } / >
2022-02-26 16:14:43 +01:00
< / > }
2022-12-16 04:07:04 +01:00
{ session . exists ( ) &&
2023-01-03 04:21:11 +01:00
< ListItemButton onClick = { handleAccountClick } selected = { location . pathname === routes . account } >
2022-12-16 04:07:04 +01:00
< ListItemIcon > < Person / > < / L i s t I t e m I c o n >
< ListItemText primary = { t ( "nav_button_account" ) } / >
2022-12-29 08:32:05 +01:00
< / L i s t I t e m B u t t o n >
}
2022-03-10 05:28:55 +01:00
< ListItemButton onClick = { ( ) => navigate ( routes . settings ) } selected = { location . pathname === routes . settings } >
2022-03-04 22:10:04 +01:00
< ListItemIcon > < SettingsIcon / > < / L i s t I t e m I c o n >
2022-04-08 03:46:33 +02:00
< ListItemText primary = { t ( "nav_button_settings" ) } / >
2022-02-25 18:46:22 +01:00
< / L i s t I t e m B u t t o n >
2022-03-11 04:58:24 +01:00
< ListItemButton onClick = { ( ) => openUrl ( "/docs" ) } >
< ListItemIcon > < ArticleIcon / > < / L i s t I t e m I c o n >
2022-04-08 03:46:33 +02:00
< ListItemText primary = { t ( "nav_button_documentation" ) } / >
2022-03-11 04:58:24 +01:00
< / L i s t I t e m B u t t o n >
2022-04-04 04:58:44 +02:00
< ListItemButton onClick = { ( ) => props . onPublishMessageClick ( ) } >
2022-04-04 04:11:26 +02:00
< ListItemIcon > < Send / > < / L i s t I t e m I c o n >
2022-04-08 03:46:33 +02:00
< ListItemText primary = { t ( "nav_button_publish_message" ) } / >
2022-04-04 04:11:26 +02:00
< / L i s t I t e m B u t t o n >
2022-02-25 18:46:22 +01:00
< ListItemButton onClick = { ( ) => setSubscribeDialogOpen ( true ) } >
2022-03-04 22:10:04 +01:00
< ListItemIcon > < AddIcon / > < / L i s t I t e m I c o n >
2022-04-08 01:11:51 +02:00
< ListItemText primary = { t ( "nav_button_subscribe" ) } / >
2022-02-25 18:46:22 +01:00
< / L i s t I t e m B u t t o n >
< / L i s t >
< SubscribeDialog
2022-02-28 22:56:38 +01:00
key = { ` subscribeDialog ${ subscribeDialogKey } ` } // Resets dialog when canceled/closed
2022-02-25 18:46:22 +01:00
open = { subscribeDialogOpen }
2022-02-26 17:45:39 +01:00
subscriptions = { props . subscriptions }
2022-02-26 05:25:04 +01:00
onCancel = { handleSubscribeReset }
onSuccess = { handleSubscribeSubmit }
2022-02-25 18:46:22 +01:00
/ >
< / >
) ;
} ;
2022-02-26 05:25:04 +01:00
2022-02-26 16:14:43 +01:00
const SubscriptionList = ( props ) => {
2022-03-05 14:52:52 +01:00
const sortedSubscriptions = props . subscriptions . sort ( ( a , b ) => {
return ( topicUrl ( a . baseUrl , a . topic ) < topicUrl ( b . baseUrl , b . topic ) ) ? - 1 : 1 ;
} ) ;
2022-02-25 18:46:22 +01:00
return (
< >
2022-03-05 14:52:52 +01:00
{ sortedSubscriptions . map ( subscription =>
2022-03-04 17:08:32 +01:00
< SubscriptionItem
2022-03-02 22:16:30 +01:00
key = { subscription . id }
2022-03-04 17:08:32 +01:00
subscription = { subscription }
2022-03-04 22:10:04 +01:00
selected = { props . selectedSubscription && props . selectedSubscription . id === subscription . id }
2022-03-04 17:08:32 +01:00
/ > ) }
2022-02-25 18:46:22 +01:00
< / >
) ;
}
2022-03-04 17:08:32 +01:00
const SubscriptionItem = ( props ) => {
2022-05-03 21:09:20 +02:00
const { t } = useTranslation ( ) ;
2022-03-04 22:10:04 +01:00
const navigate = useNavigate ( ) ;
2022-03-04 17:08:32 +01:00
const subscription = props . subscription ;
2022-03-07 22:36:49 +01:00
const iconBadge = ( subscription . new <= 99 ) ? subscription . new : "99+" ;
2022-03-04 17:08:32 +01:00
const icon = ( subscription . state === ConnectionState . Connecting )
? < CircularProgress size = "24px" / >
2022-03-07 22:36:49 +01:00
: < Badge badgeContent = { iconBadge } invisible = { subscription . new === 0 } color = "primary" > < ChatBubbleOutlineIcon / > < / B a d g e > ;
2022-06-29 21:57:56 +02:00
const displayName = topicDisplayName ( subscription ) ;
2022-05-03 21:09:20 +02:00
const ariaLabel = ( subscription . state === ConnectionState . Connecting )
2022-06-29 21:57:56 +02:00
? ` ${ displayName } ( ${ t ( "nav_button_connecting" ) } ) `
: displayName ;
2022-03-07 04:37:13 +01:00
const handleClick = async ( ) => {
2022-03-10 05:28:55 +01:00
navigate ( routes . forSubscription ( subscription ) ) ;
2022-03-07 04:37:13 +01:00
await subscriptionManager . markNotificationsRead ( subscription . id ) ;
} ;
2022-03-04 17:08:32 +01:00
return (
2022-05-03 21:09:20 +02:00
< ListItemButton onClick = { handleClick } selected = { props . selected } aria - label = { ariaLabel } aria - live = "polite" >
2022-03-04 17:08:32 +01:00
< ListItemIcon > { icon } < / L i s t I t e m I c o n >
2023-01-03 17:28:04 +01:00
< ListItemText primary = { displayName } primaryTypographyProps = { { style : { overflow : "hidden" , textOverflow : "ellipsis" } } } / >
{ subscription . reservation ? . everyone &&
< ListItemIcon edge = "end" sx = { { minWidth : "26px" } } >
{ subscription . reservation ? . everyone === "read-write" &&
< Tooltip title = { t ( "prefs_reservations_table_everyone_read_write" ) } > < Public fontSize = "small" / > < / T o o l t i p >
}
{ subscription . reservation ? . everyone === "read-only" &&
< Tooltip title = { t ( "prefs_reservations_table_everyone_read_only" ) } > < PublicOff fontSize = "small" / > < / T o o l t i p >
}
{ subscription . reservation ? . everyone === "write-only" &&
< Tooltip title = { t ( "prefs_reservations_table_everyone_write_only" ) } > < PublicOff fontSize = "small" / > < / T o o l t i p >
}
{ subscription . reservation ? . everyone === "deny-all" &&
< Tooltip title = { t ( "prefs_reservations_table_everyone_deny_all" ) } > < Lock fontSize = "small" / > < / T o o l t i p >
}
< / L i s t I t e m I c o n >
}
2022-03-08 22:56:41 +01:00
{ subscription . mutedUntil > 0 &&
2023-01-03 17:28:04 +01:00
< ListItemIcon edge = "end" sx = { { minWidth : "26px" } } aria - label = { t ( "nav_button_muted" ) } >
< Tooltip title = { t ( "nav_button_muted" ) } > < NotificationsOffOutlined / > < / T o o l t i p >
< / L i s t I t e m I c o n >
}
2022-03-04 17:08:32 +01:00
< / L i s t I t e m B u t t o n >
) ;
} ;
2022-03-11 00:11:12 +01:00
const NotificationGrantAlert = ( props ) => {
2022-04-08 03:46:33 +02:00
const { t } = useTranslation ( ) ;
2022-03-01 22:22:47 +01:00
return (
< >
< Alert severity = "warning" sx = { { paddingTop : 2 } } >
2022-04-08 03:46:33 +02:00
< AlertTitle > { t ( "alert_grant_title" ) } < / A l e r t T i t l e >
< Typography gutterBottom > { t ( "alert_grant_description" ) } < / T y p o g r a p h y >
2022-03-01 22:22:47 +01:00
< Button
sx = { { float : 'right' } }
color = "inherit"
size = "small"
onClick = { props . onRequestPermissionClick }
>
2022-04-08 03:46:33 +02:00
{ t ( "alert_grant_button" ) }
2022-03-01 22:22:47 +01:00
< / B u t t o n >
< / A l e r t >
< Divider / >
< / >
) ;
} ;
2022-06-12 22:38:33 +02:00
const NotificationBrowserNotSupportedAlert = ( ) => {
2022-04-08 03:46:33 +02:00
const { t } = useTranslation ( ) ;
2022-03-11 00:11:12 +01:00
return (
< >
< Alert severity = "warning" sx = { { paddingTop : 2 } } >
2022-04-08 03:46:33 +02:00
< AlertTitle > { t ( "alert_not_supported_title" ) } < / A l e r t T i t l e >
< Typography gutterBottom > { t ( "alert_not_supported_description" ) } < / T y p o g r a p h y >
2022-03-11 00:11:12 +01:00
< / A l e r t >
< Divider / >
< / >
) ;
} ;
2022-06-12 22:38:33 +02:00
const NotificationContextNotSupportedAlert = ( ) => {
const { t } = useTranslation ( ) ;
return (
< >
< Alert severity = "warning" sx = { { paddingTop : 2 } } >
< AlertTitle > { t ( "alert_not_supported_title" ) } < / A l e r t T i t l e >
< Typography gutterBottom >
< Trans
i18nKey = "alert_not_supported_context_description"
components = { {
mdnLink : < Link href = "https://developer.mozilla.org/en-US/docs/Web/API/notification" target = "_blank" rel = "noopener" / >
} }
/ >
< / T y p o g r a p h y >
< / A l e r t >
< Divider / >
< / >
) ;
} ;
2022-02-25 18:46:22 +01:00
export default Navigation ;