Make web push toggle global
This commit is contained in:
		
							parent
							
								
									a8db08c7d4
								
							
						
					
					
						commit
						46798ac322
					
				
					 10 changed files with 99 additions and 91 deletions
				
			
		|  | @ -69,6 +69,16 @@ const Layout = () => { | |||
|   const [sendDialogOpenMode, setSendDialogOpenMode] = useState(""); | ||||
|   const users = useLiveQuery(() => userManager.all()); | ||||
|   const subscriptions = useLiveQuery(() => subscriptionManager.all()); | ||||
|   const webPushTopics = useLiveQuery(() => subscriptionManager.webPushTopics()); | ||||
| 
 | ||||
|   const websocketSubscriptions = useMemo( | ||||
|     () => (subscriptions && webPushTopics ? subscriptions.filter((s) => !webPushTopics.includes(s.topic)) : []), | ||||
|     // websocketSubscriptions should stay stable unless the list of subscription ids changes. | ||||
|     // without the memoization, the connection listener calls a refresh for no reason. | ||||
|     // this isn't a problem due to the makeConnectionId, but it triggers an | ||||
|     // unnecessary recomputation for every received message. | ||||
|     [JSON.stringify({ subscriptions: subscriptions?.map(({ id }) => id), webPushTopics })] | ||||
|   ); | ||||
|   const subscriptionsWithoutInternal = subscriptions?.filter((s) => !s.internal); | ||||
|   const newNotificationsCount = subscriptionsWithoutInternal?.reduce((prev, cur) => prev + cur.new, 0) || 0; | ||||
|   const [selected] = (subscriptionsWithoutInternal || []).filter( | ||||
|  | @ -77,7 +87,7 @@ const Layout = () => { | |||
|       (config.base_url === s.baseUrl && params.topic === s.topic) | ||||
|   ); | ||||
| 
 | ||||
|   useConnectionListeners(account, subscriptions, users); | ||||
|   useConnectionListeners(account, websocketSubscriptions, users); | ||||
|   useAccountListener(setAccount); | ||||
|   useBackgroundProcesses(); | ||||
|   useEffect(() => updateTitle(newNotificationsCount), [newNotificationsCount]); | ||||
|  |  | |||
|  | @ -48,6 +48,7 @@ import { PermissionDenyAll, PermissionRead, PermissionReadWrite, PermissionWrite | |||
| import { ReserveAddDialog, ReserveDeleteDialog, ReserveEditDialog } from "./ReserveDialogs"; | ||||
| import { UnauthorizedError } from "../app/errors"; | ||||
| import { subscribeTopic } from "./SubscribeDialog"; | ||||
| import notifier from "../app/Notifier"; | ||||
| 
 | ||||
| const maybeUpdateAccountSettings = async (payload) => { | ||||
|   if (!session.exists()) { | ||||
|  | @ -85,6 +86,7 @@ const Notifications = () => { | |||
|         <Sound /> | ||||
|         <MinPriority /> | ||||
|         <DeleteAfter /> | ||||
|         <WebPushEnabled /> | ||||
|       </PrefGroup> | ||||
|     </Card> | ||||
|   ); | ||||
|  | @ -232,6 +234,35 @@ const DeleteAfter = () => { | |||
|   ); | ||||
| }; | ||||
| 
 | ||||
| const WebPushEnabled = () => { | ||||
|   const { t } = useTranslation(); | ||||
|   const labelId = "prefWebPushEnabled"; | ||||
|   const defaultEnabled = useLiveQuery(async () => prefs.webPushEnabled()); | ||||
|   const handleChange = async (ev) => { | ||||
|     await prefs.setWebPushEnabled(ev.target.value); | ||||
|   }; | ||||
| 
 | ||||
|   // while loading | ||||
|   if (defaultEnabled == null) { | ||||
|     return null; | ||||
|   } | ||||
| 
 | ||||
|   if (!notifier.pushPossible()) { | ||||
|     return null; | ||||
|   } | ||||
| 
 | ||||
|   return ( | ||||
|     <Pref labelId={labelId} title={t("prefs_notifications_web_push_title")} description={t("prefs_notifications_web_push_description")}> | ||||
|       <FormControl fullWidth variant="standard" sx={{ m: 1 }}> | ||||
|         <Select value={defaultEnabled} onChange={handleChange} aria-labelledby={labelId}> | ||||
|           <MenuItem value>{t("prefs_notifications_web_push_enabled")}</MenuItem> | ||||
|           <MenuItem value={false}>{t("prefs_notifications_web_push_disabled")}</MenuItem> | ||||
|         </Select> | ||||
|       </FormControl> | ||||
|     </Pref> | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
| const Users = () => { | ||||
|   const { t } = useTranslation(); | ||||
|   const [dialogKey, setDialogKey] = useState(0); | ||||
|  |  | |||
|  | @ -28,7 +28,6 @@ import ReserveTopicSelect from "./ReserveTopicSelect"; | |||
| import { AccountContext } from "./App"; | ||||
| import { TopicReservedError, UnauthorizedError } from "../app/errors"; | ||||
| import { ReserveLimitChip } from "./SubscriptionPopup"; | ||||
| import notifier from "../app/Notifier"; | ||||
| 
 | ||||
| const publicBaseUrl = "https://ntfy.sh"; | ||||
| 
 | ||||
|  | @ -53,12 +52,10 @@ const SubscribeDialog = (props) => { | |||
|   const [showLoginPage, setShowLoginPage] = useState(false); | ||||
|   const fullScreen = useMediaQuery(theme.breakpoints.down("sm")); | ||||
| 
 | ||||
|   const handleSuccess = async (webPushEnabled) => { | ||||
|   const handleSuccess = async () => { | ||||
|     console.log(`[SubscribeDialog] Subscribing to topic ${topic}`); | ||||
|     const actualBaseUrl = baseUrl || config.base_url; | ||||
|     const subscription = await subscribeTopic(actualBaseUrl, topic, { | ||||
|       webPushEnabled, | ||||
|     }); | ||||
|     const subscription = await subscribeTopic(actualBaseUrl, topic, {}); | ||||
|     poller.pollInBackground(subscription); // Dangle! | ||||
|     props.onSuccess(subscription); | ||||
|   }; | ||||
|  | @ -99,12 +96,6 @@ const SubscribePage = (props) => { | |||
|   const reserveTopicEnabled = | ||||
|     session.exists() && (account?.role === Role.ADMIN || (account?.role === Role.USER && (account?.stats.reservations_remaining || 0) > 0)); | ||||
| 
 | ||||
|   const [backgroundNotificationsEnabled, setBackgroundNotificationsEnabled] = useState(false); | ||||
| 
 | ||||
|   const handleBackgroundNotificationsChanged = (e) => { | ||||
|     setBackgroundNotificationsEnabled(e.target.checked); | ||||
|   }; | ||||
| 
 | ||||
|   const handleSubscribe = async () => { | ||||
|     const user = await userManager.get(baseUrl); // May be undefined | ||||
|     const username = user ? user.username : t("subscribe_dialog_error_user_anonymous"); | ||||
|  | @ -142,15 +133,12 @@ const SubscribePage = (props) => { | |||
|     } | ||||
| 
 | ||||
|     console.log(`[SubscribeDialog] Successful login to ${topicUrl(baseUrl, topic)} for user ${username}`); | ||||
|     props.onSuccess(backgroundNotificationsEnabled); | ||||
|     props.onSuccess(); | ||||
|   }; | ||||
| 
 | ||||
|   const handleUseAnotherChanged = (e) => { | ||||
|     props.setBaseUrl(""); | ||||
|     setAnotherServerVisible(e.target.checked); | ||||
|     if (e.target.checked) { | ||||
|       setBackgroundNotificationsEnabled(false); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   const subscribeButtonEnabled = (() => { | ||||
|  | @ -256,22 +244,6 @@ const SubscribePage = (props) => { | |||
|             )} | ||||
|           </FormGroup> | ||||
|         )} | ||||
|         {notifier.pushPossible() && !anotherServerVisible && ( | ||||
|           <FormGroup> | ||||
|             <FormControlLabel | ||||
|               control={ | ||||
|                 <Switch | ||||
|                   onChange={handleBackgroundNotificationsChanged} | ||||
|                   checked={backgroundNotificationsEnabled} | ||||
|                   inputProps={{ | ||||
|                     "aria-label": t("subscribe_dialog_subscribe_enable_background_notifications_label"), | ||||
|                   }} | ||||
|                 /> | ||||
|               } | ||||
|               label={t("subscribe_dialog_subscribe_enable_background_notifications_label")} | ||||
|             /> | ||||
|           </FormGroup> | ||||
|         )} | ||||
|       </DialogContent> | ||||
|       <DialogFooter status={error}> | ||||
|         <Button onClick={props.onCancel}>{t("subscribe_dialog_subscribe_button_cancel")}</Button> | ||||
|  |  | |||
|  | @ -15,19 +15,17 @@ import { | |||
|   MenuItem, | ||||
|   IconButton, | ||||
|   ListItemIcon, | ||||
|   ListItemText, | ||||
|   Divider, | ||||
| } from "@mui/material"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import { useNavigate } from "react-router-dom"; | ||||
| import { | ||||
|   Check, | ||||
|   Clear, | ||||
|   ClearAll, | ||||
|   Edit, | ||||
|   EnhancedEncryption, | ||||
|   Lock, | ||||
|   LockOpen, | ||||
|   Notifications, | ||||
|   NotificationsOff, | ||||
|   RemoveCircle, | ||||
|   Send, | ||||
|  | @ -44,7 +42,6 @@ import api from "../app/Api"; | |||
| import { AccountContext } from "./App"; | ||||
| import { ReserveAddDialog, ReserveDeleteDialog, ReserveEditDialog } from "./ReserveDialogs"; | ||||
| import { UnauthorizedError } from "../app/errors"; | ||||
| import notifier from "../app/Notifier"; | ||||
| 
 | ||||
| export const SubscriptionPopup = (props) => { | ||||
|   const { t } = useTranslation(); | ||||
|  | @ -169,8 +166,8 @@ export const SubscriptionPopup = (props) => { | |||
|   return ( | ||||
|     <> | ||||
|       <PopupMenu horizontal={placement} anchorEl={props.anchor} open={!!props.anchor} onClose={props.onClose}> | ||||
|         {notifier.pushPossible() && <NotificationToggle subscription={subscription} />} | ||||
|         <Divider /> | ||||
|         <NotificationToggle subscription={subscription} /> | ||||
| 
 | ||||
|         <MenuItem onClick={handleChangeDisplayName}> | ||||
|           <ListItemIcon> | ||||
|             <Edit fontSize="small" /> | ||||
|  | @ -334,44 +331,27 @@ const DisplayNameDialog = (props) => { | |||
|   ); | ||||
| }; | ||||
| 
 | ||||
| const checkedItem = ( | ||||
|   <ListItemIcon> | ||||
|     <Check /> | ||||
|   </ListItemIcon> | ||||
| ); | ||||
| 
 | ||||
| const NotificationToggle = ({ subscription }) => { | ||||
|   const { t } = useTranslation(); | ||||
| 
 | ||||
|   const handleToggleBackground = async () => { | ||||
|     try { | ||||
|       await subscriptionManager.toggleBackgroundNotifications(subscription); | ||||
|     } catch (e) { | ||||
|       console.error("[NotificationToggle] Error setting notification type", e); | ||||
|     } | ||||
|   const handleToggleMute = async () => { | ||||
|     const mutedUntil = subscription.mutedUntil ? 0 : 1; // Make this a timestamp in the future | ||||
|     await subscriptionManager.setMutedUntil(subscription.id, mutedUntil); | ||||
|   }; | ||||
| 
 | ||||
|   const unmute = async () => { | ||||
|     await subscriptionManager.setMutedUntil(subscription.id, 0); | ||||
|   }; | ||||
| 
 | ||||
|   if (subscription.mutedUntil === 1) { | ||||
|     return ( | ||||
|       <MenuItem onClick={unmute}> | ||||
|         <ListItemIcon> | ||||
|           <NotificationsOff /> | ||||
|         </ListItemIcon> | ||||
|         {t("notification_toggle_unmute")} | ||||
|       </MenuItem> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   return ( | ||||
|     <MenuItem> | ||||
|       {subscription.webPushEnabled === 1 && checkedItem} | ||||
|       <ListItemText inset={subscription.webPushEnabled !== 1} onClick={handleToggleBackground}> | ||||
|         {t("notification_toggle_background")} | ||||
|       </ListItemText> | ||||
|   return subscription.mutedUntil ? ( | ||||
|     <MenuItem onClick={handleToggleMute}> | ||||
|       <ListItemIcon> | ||||
|         <Notifications /> | ||||
|       </ListItemIcon> | ||||
|       {t("notification_toggle_unmute")} | ||||
|     </MenuItem> | ||||
|   ) : ( | ||||
|     <MenuItem onClick={handleToggleMute}> | ||||
|       <ListItemIcon> | ||||
|         <NotificationsOff /> | ||||
|       </ListItemIcon> | ||||
|       {t("notification_toggle_mute")} | ||||
|     </MenuItem> | ||||
|   ); | ||||
| }; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue