2022-02-25 17:46:22 +00:00
import Drawer from "@mui/material/Drawer" ;
import * as React from "react" ;
2023-01-10 01:37:13 +00:00
import { useContext , useState } from "react" ;
2022-02-25 17:46:22 +00:00
import ListItemButton from "@mui/material/ListItemButton" ;
import ListItemIcon from "@mui/material/ListItemIcon" ;
import ChatBubbleOutlineIcon from "@mui/icons-material/ChatBubbleOutline" ;
2022-12-16 03:07:04 +00:00
import Person from "@mui/icons-material/Person" ;
2022-02-25 17:46:22 +00: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-05 03:47:12 +00:00
import { Alert , AlertTitle , Badge , CircularProgress , Link , ListSubheader , Tooltip } from "@mui/material" ;
2022-02-26 15:14:43 +00:00
import Button from "@mui/material/Button" ;
import Typography from "@mui/material/Typography" ;
2022-06-29 19:57:56 +00:00
import { openUrl , topicDisplayName , topicUrl } from "../app/utils" ;
2022-03-10 04:28:55 +00:00
import routes from "./routes" ;
2022-03-04 16:08:32 +00:00
import { ConnectionState } from "../app/Connection" ;
2023-01-05 03:47:12 +00:00
import { useLocation , useNavigate } from "react-router-dom" ;
2022-03-07 03:37:13 +00:00
import subscriptionManager from "../app/SubscriptionManager" ;
2023-01-05 03:47:12 +00:00
import { ChatBubble , Lock , NotificationsOffOutlined , Public , PublicOff , Send } from "@mui/icons-material" ;
2022-03-08 16:33:17 +00:00
import Box from "@mui/material/Box" ;
import notifier from "../app/Notifier" ;
2022-03-10 04:28:55 +00:00
import config from "../app/config" ;
2022-03-11 03:58:24 +00:00
import ArticleIcon from '@mui/icons-material/Article' ;
2022-06-12 20:38:33 +00:00
import { Trans , useTranslation } from "react-i18next" ;
2022-12-16 03:07:04 +00:00
import session from "../app/Session" ;
2023-01-03 03:21:11 +00:00
import accountApi from "../app/AccountApi" ;
2023-01-05 03:47:12 +00:00
import CelebrationIcon from '@mui/icons-material/Celebration' ;
2023-01-09 20:40:46 +00:00
import UpgradeDialog from "./UpgradeDialog" ;
2023-01-10 01:37:13 +00:00
import { AccountContext } from "./App" ;
2022-02-25 17:46:22 +00:00
2022-03-08 21:56:41 +00:00
const navWidth = 280 ;
2022-02-25 17:46:22 +00:00
const Navigation = ( props ) => {
2022-02-26 15:14:43 +00:00
const navigationList = < NavList { ... props } / > ;
2022-02-25 17:46:22 +00:00
return (
2022-05-02 23:30:29 +00:00
< Box
component = "nav"
2022-05-03 19:09:20 +00:00
role = "navigation"
2022-05-02 23:30:29 +00:00
sx = { { width : { sm : Navigation . width } , flexShrink : { sm : 0 } } }
>
2022-02-25 17:46:22 +00:00
{ /* Mobile drawer; only shown if menu icon clicked (mobile open) and display is small */ }
< Drawer
variant = "temporary"
2022-05-03 19:09:20 +00:00
role = "menubar"
2022-02-25 17:46:22 +00: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 19:09:20 +00:00
role = "menubar"
2022-02-25 17:46:22 +00:00
sx = { {
display : { xs : 'none' , sm : 'block' } ,
'& .MuiDrawer-paper' : { boxSizing : 'border-box' , width : navWidth } ,
} }
>
{ navigationList }
< / D r a w e r >
2022-03-08 16:33:17 +00:00
< / B o x >
2022-02-25 17:46:22 +00:00
) ;
} ;
Navigation . width = navWidth ;
const NavList = ( props ) => {
2022-04-07 23:11:51 +00:00
const { t } = useTranslation ( ) ;
2022-03-04 21:10:04 +00:00
const navigate = useNavigate ( ) ;
const location = useLocation ( ) ;
2023-01-10 01:37:13 +00:00
const { account } = useContext ( AccountContext ) ;
2022-02-26 04:25:04 +00:00
const [ subscribeDialogKey , setSubscribeDialogKey ] = useState ( 0 ) ;
2022-02-25 17:46:22 +00:00
const [ subscribeDialogOpen , setSubscribeDialogOpen ] = useState ( false ) ;
2022-03-06 03:33:34 +00:00
2022-02-26 04:25:04 +00:00
const handleSubscribeReset = ( ) => {
2022-02-25 17:46:22 +00:00
setSubscribeDialogOpen ( false ) ;
2022-02-26 04:25:04 +00:00
setSubscribeDialogKey ( prev => prev + 1 ) ;
}
2022-03-06 03:33:34 +00:00
2022-03-02 02:23:12 +00:00
const handleSubscribeSubmit = ( subscription ) => {
2022-03-07 02:39:20 +00:00
console . log ( ` [Navigation] New subscription: ${ subscription . id } ` , subscription ) ;
2022-02-26 04:25:04 +00:00
handleSubscribeReset ( ) ;
2022-03-10 04:28:55 +00:00
navigate ( routes . forSubscription ( subscription ) ) ;
2022-03-08 16:33:17 +00:00
handleRequestNotificationPermission ( ) ;
2022-02-25 17:46:22 +00:00
}
2022-03-06 03:33:34 +00:00
2022-03-08 16:33:17 +00:00
const handleRequestNotificationPermission = ( ) => {
notifier . maybeRequestPermission ( granted => props . onNotificationGranted ( granted ) )
} ;
2023-01-03 03:21:11 +00:00
const handleAccountClick = ( ) => {
accountApi . sync ( ) ; // Dangle!
navigate ( routes . account ) ;
} ;
2023-01-10 01:37:13 +00:00
const isAdmin = account ? . role === "admin" ;
const isPaid = account ? . tier ? . paid ;
2023-01-09 20:40:46 +00:00
const showUpgradeBanner = config . enable _payments && ! isAdmin && ! isPaid ; // && (!props.account || !props.account.tier || !props.account.tier.paid || props.account);
2022-03-02 21:16:30 +00:00
const showSubscriptionsList = props . subscriptions ? . length > 0 ;
2022-06-12 20:38:33 +00:00
const showNotificationBrowserNotSupportedBox = ! notifier . browserSupported ( ) ;
const showNotificationContextNotSupportedBox = notifier . browserSupported ( ) && ! notifier . contextSupported ( ) ; // Only show if notifications are generally supported in the browser
2022-03-10 23:11:12 +00:00
const showNotificationGrantBox = notifier . supported ( ) && props . subscriptions ? . length > 0 && ! props . notificationsGranted ;
2022-06-12 20:38:33 +00:00
const navListPadding = ( showNotificationGrantBox || showNotificationBrowserNotSupportedBox || showNotificationContextNotSupportedBox ) ? '0' : '' ;
2022-03-06 03:33:34 +00:00
2022-02-25 17:46:22 +00:00
return (
< >
2022-03-04 21:10:04 +00:00
< Toolbar sx = { { display : { xs : 'none' , sm : 'block' } } } / >
2022-06-12 20:38:33 +00:00
< List component = "nav" sx = { { paddingTop : navListPadding } } >
{ showNotificationBrowserNotSupportedBox && < NotificationBrowserNotSupportedAlert / > }
{ showNotificationContextNotSupportedBox && < NotificationContextNotSupportedAlert / > }
2022-03-10 23:11:12 +00:00
{ showNotificationGrantBox && < NotificationGrantAlert onRequestPermissionClick = { handleRequestNotificationPermission } / > }
2022-03-08 01:11:58 +00:00
{ ! showSubscriptionsList &&
2023-01-05 03:47:12 +00:00
< ListItemButton onClick = { ( ) => navigate ( routes . app ) } selected = { location . pathname === config . app _root } >
2022-03-08 01:11:58 +00:00
< ListItemIcon > < ChatBubble / > < / L i s t I t e m I c o n >
2022-04-08 01:46:33 +00:00
< ListItemText primary = { t ( "nav_button_all_notifications" ) } / >
2022-03-08 01:11:58 +00:00
< / L i s t I t e m B u t t o n > }
2022-02-26 15:14:43 +00:00
{ showSubscriptionsList &&
< >
2022-04-08 01:46:33 +00:00
< ListSubheader > { t ( "nav_topics_title" ) } < / L i s t S u b h e a d e r >
2023-01-05 03:47:12 +00:00
< ListItemButton onClick = { ( ) => navigate ( routes . app ) } selected = { location . pathname === config . app _root } >
2022-03-08 01:11:58 +00:00
< ListItemIcon > < ChatBubble / > < / L i s t I t e m I c o n >
2022-04-08 01:46:33 +00:00
< ListItemText primary = { t ( "nav_button_all_notifications" ) } / >
2022-03-08 01:11:58 +00:00
< / L i s t I t e m B u t t o n >
2022-02-26 15:14:43 +00:00
< SubscriptionList
subscriptions = { props . subscriptions }
selectedSubscription = { props . selectedSubscription }
/ >
2022-02-26 19:22:21 +00:00
< Divider sx = { { my : 1 } } / >
2022-02-26 15:14:43 +00:00
< / > }
2022-12-16 03:07:04 +00:00
{ session . exists ( ) &&
2023-01-03 03:21:11 +00:00
< ListItemButton onClick = { handleAccountClick } selected = { location . pathname === routes . account } >
2022-12-16 03:07:04 +00:00
< ListItemIcon > < Person / > < / L i s t I t e m I c o n >
< ListItemText primary = { t ( "nav_button_account" ) } / >
2022-12-29 07:32:05 +00:00
< / L i s t I t e m B u t t o n >
}
2022-03-10 04:28:55 +00:00
< ListItemButton onClick = { ( ) => navigate ( routes . settings ) } selected = { location . pathname === routes . settings } >
2022-03-04 21:10:04 +00:00
< ListItemIcon > < SettingsIcon / > < / L i s t I t e m I c o n >
2022-04-08 01:46:33 +00:00
< ListItemText primary = { t ( "nav_button_settings" ) } / >
2022-02-25 17:46:22 +00:00
< / L i s t I t e m B u t t o n >
2022-03-11 03:58:24 +00:00
< ListItemButton onClick = { ( ) => openUrl ( "/docs" ) } >
< ListItemIcon > < ArticleIcon / > < / L i s t I t e m I c o n >
2022-04-08 01:46:33 +00:00
< ListItemText primary = { t ( "nav_button_documentation" ) } / >
2022-03-11 03:58:24 +00:00
< / L i s t I t e m B u t t o n >
2022-04-04 02:58:44 +00:00
< ListItemButton onClick = { ( ) => props . onPublishMessageClick ( ) } >
2022-04-04 02:11:26 +00:00
< ListItemIcon > < Send / > < / L i s t I t e m I c o n >
2022-04-08 01:46:33 +00:00
< ListItemText primary = { t ( "nav_button_publish_message" ) } / >
2022-04-04 02:11:26 +00:00
< / L i s t I t e m B u t t o n >
2022-02-25 17:46:22 +00:00
< ListItemButton onClick = { ( ) => setSubscribeDialogOpen ( true ) } >
2022-03-04 21:10:04 +00:00
< ListItemIcon > < AddIcon / > < / L i s t I t e m I c o n >
2022-04-07 23:11:51 +00:00
< ListItemText primary = { t ( "nav_button_subscribe" ) } / >
2022-02-25 17:46:22 +00:00
< / L i s t I t e m B u t t o n >
2023-01-05 03:47:12 +00:00
{ showUpgradeBanner &&
2023-01-09 20:40:46 +00:00
< UpgradeBanner / >
2023-01-05 03:47:12 +00:00
}
2022-02-25 17:46:22 +00:00
< / L i s t >
< SubscribeDialog
2022-02-28 21:56:38 +00:00
key = { ` subscribeDialog ${ subscribeDialogKey } ` } // Resets dialog when canceled/closed
2022-02-25 17:46:22 +00:00
open = { subscribeDialogOpen }
2022-02-26 16:45:39 +00:00
subscriptions = { props . subscriptions }
2022-02-26 04:25:04 +00:00
onCancel = { handleSubscribeReset }
onSuccess = { handleSubscribeSubmit }
2022-02-25 17:46:22 +00:00
/ >
< / >
) ;
} ;
2022-02-26 04:25:04 +00:00
2023-01-09 20:40:46 +00:00
const UpgradeBanner = ( ) => {
const [ dialogOpen , setDialogOpen ] = useState ( false ) ;
return (
< Box sx = { {
position : "fixed" ,
width : ` ${ Navigation . width - 1 } px ` ,
bottom : 0 ,
mt : 'auto' ,
background : "linear-gradient(150deg, rgba(196, 228, 221, 0.46) 0%, rgb(255, 255, 255) 100%)" ,
} } >
< Divider / >
2023-01-09 22:56:51 +00:00
< ListItemButton onClick = { ( ) => setDialogOpen ( true ) } sx = { { pt : 2 , pb : 2 } } >
2023-01-09 20:40:46 +00:00
< ListItemIcon > < CelebrationIcon sx = { { color : "#55b86e" } } fontSize = "large" / > < / L i s t I t e m I c o n >
< ListItemText
sx = { { ml : 1 } }
primary = { "Upgrade to ntfy Pro" }
2023-01-09 22:56:51 +00:00
secondary = { "Reserve topics, more messages & emails, and larger attachments" }
2023-01-09 20:40:46 +00:00
primaryTypographyProps = { {
style : {
fontWeight : 500 ,
2023-01-09 22:56:51 +00:00
fontSize : "1.1rem" ,
2023-01-09 20:40:46 +00:00
background : "-webkit-linear-gradient(45deg, #09009f, #00ff95 80%)" ,
WebkitBackgroundClip : "text" ,
WebkitTextFillColor : "transparent"
}
} }
2023-01-09 22:56:51 +00:00
secondaryTypographyProps = { {
style : {
fontSize : "1rem"
}
} }
2023-01-09 20:40:46 +00:00
/ >
< / L i s t I t e m B u t t o n >
< UpgradeDialog
open = { dialogOpen }
onCancel = { ( ) => setDialogOpen ( false ) }
/ >
< / B o x >
) ;
} ;
2022-02-26 15:14:43 +00:00
const SubscriptionList = ( props ) => {
2023-01-12 02:38:10 +00:00
const sortedSubscriptions = props . subscriptions
. filter ( s => ! s . internal )
. sort ( ( a , b ) => {
return ( topicUrl ( a . baseUrl , a . topic ) < topicUrl ( b . baseUrl , b . topic ) ) ? - 1 : 1 ;
} ) ;
2022-02-25 17:46:22 +00:00
return (
< >
2022-03-05 13:52:52 +00:00
{ sortedSubscriptions . map ( subscription =>
2022-03-04 16:08:32 +00:00
< SubscriptionItem
2022-03-02 21:16:30 +00:00
key = { subscription . id }
2022-03-04 16:08:32 +00:00
subscription = { subscription }
2022-03-04 21:10:04 +00:00
selected = { props . selectedSubscription && props . selectedSubscription . id === subscription . id }
2022-03-04 16:08:32 +00:00
/ > ) }
2022-02-25 17:46:22 +00:00
< / >
) ;
}
2022-03-04 16:08:32 +00:00
const SubscriptionItem = ( props ) => {
2022-05-03 19:09:20 +00:00
const { t } = useTranslation ( ) ;
2022-03-04 21:10:04 +00:00
const navigate = useNavigate ( ) ;
2022-03-04 16:08:32 +00:00
const subscription = props . subscription ;
2022-03-07 21:36:49 +00:00
const iconBadge = ( subscription . new <= 99 ) ? subscription . new : "99+" ;
2022-03-04 16:08:32 +00:00
const icon = ( subscription . state === ConnectionState . Connecting )
? < CircularProgress size = "24px" / >
2022-03-07 21:36:49 +00:00
: < Badge badgeContent = { iconBadge } invisible = { subscription . new === 0 } color = "primary" > < ChatBubbleOutlineIcon / > < / B a d g e > ;
2022-06-29 19:57:56 +00:00
const displayName = topicDisplayName ( subscription ) ;
2022-05-03 19:09:20 +00:00
const ariaLabel = ( subscription . state === ConnectionState . Connecting )
2022-06-29 19:57:56 +00:00
? ` ${ displayName } ( ${ t ( "nav_button_connecting" ) } ) `
: displayName ;
2022-03-07 03:37:13 +00:00
const handleClick = async ( ) => {
2022-03-10 04:28:55 +00:00
navigate ( routes . forSubscription ( subscription ) ) ;
2022-03-07 03:37:13 +00:00
await subscriptionManager . markNotificationsRead ( subscription . id ) ;
} ;
2022-03-04 16:08:32 +00:00
return (
2022-05-03 19:09:20 +00:00
< ListItemButton onClick = { handleClick } selected = { props . selected } aria - label = { ariaLabel } aria - live = "polite" >
2022-03-04 16:08:32 +00:00
< ListItemIcon > { icon } < / L i s t I t e m I c o n >
2023-01-03 16:28:04 +00: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 21:56:41 +00:00
{ subscription . mutedUntil > 0 &&
2023-01-03 16:28:04 +00: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 16:08:32 +00:00
< / L i s t I t e m B u t t o n >
) ;
} ;
2022-03-10 23:11:12 +00:00
const NotificationGrantAlert = ( props ) => {
2022-04-08 01:46:33 +00:00
const { t } = useTranslation ( ) ;
2022-03-01 21:22:47 +00:00
return (
< >
< Alert severity = "warning" sx = { { paddingTop : 2 } } >
2022-04-08 01:46:33 +00: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 21:22:47 +00:00
< Button
sx = { { float : 'right' } }
color = "inherit"
size = "small"
onClick = { props . onRequestPermissionClick }
>
2022-04-08 01:46:33 +00:00
{ t ( "alert_grant_button" ) }
2022-03-01 21:22:47 +00:00
< / B u t t o n >
< / A l e r t >
< Divider / >
< / >
) ;
} ;
2022-06-12 20:38:33 +00:00
const NotificationBrowserNotSupportedAlert = ( ) => {
2022-04-08 01:46:33 +00:00
const { t } = useTranslation ( ) ;
2022-03-10 23:11:12 +00:00
return (
< >
< Alert severity = "warning" sx = { { paddingTop : 2 } } >
2022-04-08 01:46:33 +00: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-10 23:11:12 +00:00
< / A l e r t >
< Divider / >
< / >
) ;
} ;
2022-06-12 20:38:33 +00: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 17:46:22 +00:00
export default Navigation ;