diff --git a/web/src/components/ActionBar.js b/web/src/components/ActionBar.js index 73cf5db..994cd4e 100644 --- a/web/src/components/ActionBar.js +++ b/web/src/components/ActionBar.js @@ -4,11 +4,20 @@ import Toolbar from "@mui/material/Toolbar"; import IconButton from "@mui/material/IconButton"; import MenuIcon from "@mui/icons-material/Menu"; import Typography from "@mui/material/Typography"; -import SubscribeSettings from "./SubscribeSettings"; import * as React from "react"; +import {useEffect, useRef, useState} from "react"; import Box from "@mui/material/Box"; -import {topicShortUrl} from "../app/utils"; -import {useLocation} from "react-router-dom"; +import {subscriptionRoute, topicShortUrl} from "../app/utils"; +import {useLocation, useNavigate} from "react-router-dom"; +import ClickAwayListener from '@mui/material/ClickAwayListener'; +import Grow from '@mui/material/Grow'; +import Paper from '@mui/material/Paper'; +import Popper from '@mui/material/Popper'; +import MenuItem from '@mui/material/MenuItem'; +import MenuList from '@mui/material/MenuList'; +import MoreVertIcon from "@mui/icons-material/MoreVert"; +import api from "../app/Api"; +import subscriptionManager from "../app/SubscriptionManager"; const ActionBar = (props) => { const location = useLocation(); @@ -41,7 +50,7 @@ const ActionBar = (props) => { {title} - {props.selectedSubscription && } @@ -50,4 +59,111 @@ const ActionBar = (props) => { ); }; +// Originally from https://mui.com/components/menus/#MenuListComposition.js +const SettingsIcon = (props) => { + const navigate = useNavigate(); + const [open, setOpen] = useState(false); + const anchorRef = useRef(null); + + const handleToggle = () => { + setOpen((prevOpen) => !prevOpen); + }; + + const handleClose = (event) => { + if (anchorRef.current && anchorRef.current.contains(event.target)) { + return; + } + setOpen(false); + }; + + const handleClearAll = async (event) => { + handleClose(event); + console.log(`[ActionBar] Deleting all notifications from ${props.subscription.id}`); + await subscriptionManager.deleteNotifications(props.subscription.id); + }; + + const handleUnsubscribe = async (event) => { + console.log(`[ActionBar] Unsubscribing from ${props.subscription.id}`); + handleClose(event); + await subscriptionManager.remove(props.subscription.id); + const newSelected = await subscriptionManager.first(); // May be undefined + if (newSelected) { + navigate(subscriptionRoute(newSelected)); + } + }; + + const handleSendTestMessage = () => { + const baseUrl = props.subscription.baseUrl; + const topic = props.subscription.topic; + api.publish(baseUrl, topic, + `This is a test notification sent by the ntfy Web UI at ${new Date().toString()}.`); // FIXME result ignored + setOpen(false); + } + + const handleListKeyDown = (event) => { + if (event.key === 'Tab') { + event.preventDefault(); + setOpen(false); + } else if (event.key === 'Escape') { + setOpen(false); + } + } + + // return focus to the button when we transitioned from !open -> open + const prevOpen = useRef(open); + useEffect(() => { + if (prevOpen.current === true && open === false) { + anchorRef.current.focus(); + } + prevOpen.current = open; + }, [open]); + + return ( + <> + + + + + {({TransitionProps, placement}) => ( + + + + + Send test notification + Clear all notifications + Unsubscribe + + + + + )} + + + ); +}; + export default ActionBar; diff --git a/web/src/components/App.js b/web/src/components/App.js index 119deca..182df34 100644 --- a/web/src/components/App.js +++ b/web/src/components/App.js @@ -47,31 +47,21 @@ const Root = () => { const subscriptions = useLiveQuery(() => subscriptionManager.all()); const selectedSubscription = findSelected(location, subscriptions); - const handleSubscriptionClick = async (subscriptionId) => { - const subscription = await subscriptionManager.get(subscriptionId); - navigate(subscriptionRoute(subscription)); - } const handleSubscribeSubmit = async (subscription) => { console.log(`[App] New subscription: ${subscription.id}`, subscription); navigate(subscriptionRoute(subscription)); handleRequestPermission(); }; - const handleUnsubscribe = async (subscriptionId) => { - console.log(`[App] Unsubscribing from ${subscriptionId}`); - const newSelected = await subscriptionManager.first(); // May be undefined - if (newSelected) { - navigate(subscriptionRoute(newSelected)); - } - }; + const handleRequestPermission = () => { notificationManager.maybeRequestPermission(granted => setNotificationsGranted(granted)); }; - // Define hooks: Note that the order of the hooks is important. The "loading" hooks - // must be before the "saving" hooks. + useEffect(() => { poller.startWorker(); pruner.startWorker(); }, [/* initial render */]); + useEffect(() => { const handleNotification = async (subscriptionId, notification) => { try { @@ -93,14 +83,17 @@ const Root = () => { // This is for the use of 'navigate' // FIXME //eslint-disable-next-line }, [/* initial render */]); - useEffect(() => { connectionManager.refresh(subscriptions, users) }, [subscriptions, users]); // Dangle! + + useEffect(() => { + connectionManager.refresh(subscriptions, users); + }, [subscriptions, users]); // Dangle! + return ( setMobileDrawerOpen(!mobileDrawerOpen)} /> @@ -110,7 +103,6 @@ const Root = () => { mobileDrawerOpen={mobileDrawerOpen} notificationsGranted={notificationsGranted} onMobileDrawerToggle={() => setMobileDrawerOpen(!mobileDrawerOpen)} - onSubscriptionClick={handleSubscriptionClick} onSubscribeSubmit={handleSubscribeSubmit} onRequestPermissionClick={handleRequestPermission} /> diff --git a/web/src/components/Navigation.js b/web/src/components/Navigation.js index eb98914..c8f9fd5 100644 --- a/web/src/components/Navigation.js +++ b/web/src/components/Navigation.js @@ -58,16 +58,20 @@ const NavList = (props) => { const location = useLocation(); const [subscribeDialogKey, setSubscribeDialogKey] = useState(0); const [subscribeDialogOpen, setSubscribeDialogOpen] = useState(false); + const handleSubscribeReset = () => { setSubscribeDialogOpen(false); setSubscribeDialogKey(prev => prev+1); } + const handleSubscribeSubmit = (subscription) => { handleSubscribeReset(); props.onSubscribeSubmit(subscription); } + const showSubscriptionsList = props.subscriptions?.length > 0; const showGrantPermissionsBox = props.subscriptions?.length > 0 && !props.notificationsGranted; + return ( <> diff --git a/web/src/components/SubscribeSettings.js b/web/src/components/SubscribeSettings.js deleted file mode 100644 index 1c1a4e3..0000000 --- a/web/src/components/SubscribeSettings.js +++ /dev/null @@ -1,116 +0,0 @@ -import * as React from 'react'; -import {useEffect, useRef, useState} from 'react'; -import ClickAwayListener from '@mui/material/ClickAwayListener'; -import Grow from '@mui/material/Grow'; -import Paper from '@mui/material/Paper'; -import Popper from '@mui/material/Popper'; -import MenuItem from '@mui/material/MenuItem'; -import MenuList from '@mui/material/MenuList'; -import IconButton from "@mui/material/IconButton"; -import MoreVertIcon from "@mui/icons-material/MoreVert"; -import api from "../app/Api"; -import subscriptionManager from "../app/SubscriptionManager"; - -// Originally from https://mui.com/components/menus/#MenuListComposition.js -const SubscribeSettings = (props) => { - const [open, setOpen] = useState(false); - const anchorRef = useRef(null); - - const handleToggle = () => { - setOpen((prevOpen) => !prevOpen); - }; - - const handleClose = (event) => { - if (anchorRef.current && anchorRef.current.contains(event.target)) { - return; - } - setOpen(false); - }; - - const handleClearAll = async (event) => { - handleClose(event); - console.log(`[IconSubscribeSettings] Deleting all notifications from ${props.subscription.id}`); - await subscriptionManager.deleteNotifications(props.subscription.id); - }; - - const handleUnsubscribe = async (event) => { - handleClose(event); - await subscriptionManager.remove(props.subscription.id); - props.onUnsubscribe(props.subscription.id); - }; - - const handleSendTestMessage = () => { - const baseUrl = props.subscription.baseUrl; - const topic = props.subscription.topic; - api.publish(baseUrl, topic, - `This is a test notification sent by the ntfy Web UI at ${new Date().toString()}.`); // FIXME result ignored - setOpen(false); - } - - const handleListKeyDown = (event) => { - if (event.key === 'Tab') { - event.preventDefault(); - setOpen(false); - } else if (event.key === 'Escape') { - setOpen(false); - } - } - - // return focus to the button when we transitioned from !open -> open - const prevOpen = useRef(open); - useEffect(() => { - if (prevOpen.current === true && open === false) { - anchorRef.current.focus(); - } - prevOpen.current = open; - }, [open]); - - return ( - <> - - - - - {({TransitionProps, placement}) => ( - - - - - Send test notification - Clear all notifications - Unsubscribe - - - - - )} - - - ); -} - -export default SubscribeSettings;