Move more stuff out of App.js

This commit is contained in:
Philipp Heckel 2022-03-05 22:33:34 -05:00
parent acde2e5b6e
commit 09b128f27a
4 changed files with 132 additions and 136 deletions

View File

@ -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) => {
<Typography variant="h6" noWrap component="div" sx={{ flexGrow: 1 }}>
{title}
</Typography>
{props.selectedSubscription && <SubscribeSettings
{props.selectedSubscription && <SettingsIcon
subscription={props.selectedSubscription}
onUnsubscribe={props.onUnsubscribe}
/>}
@ -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 (
<>
<IconButton
color="inherit"
size="large"
edge="end"
ref={anchorRef}
id="composition-button"
onClick={handleToggle}
>
<MoreVertIcon/>
</IconButton>
<Popper
open={open}
anchorEl={anchorRef.current}
role={undefined}
placement="bottom-start"
transition
disablePortal
>
{({TransitionProps, placement}) => (
<Grow
{...TransitionProps}
style={{
transformOrigin:
placement === 'bottom-start' ? 'left top' : 'left bottom',
}}
>
<Paper>
<ClickAwayListener onClickAway={handleClose}>
<MenuList
autoFocusItem={open}
id="composition-menu"
onKeyDown={handleListKeyDown}
>
<MenuItem onClick={handleSendTestMessage}>Send test notification</MenuItem>
<MenuItem onClick={handleClearAll}>Clear all notifications</MenuItem>
<MenuItem onClick={handleUnsubscribe}>Unsubscribe</MenuItem>
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</>
);
};
export default ActionBar;

View File

@ -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 (
<Box sx={{display: 'flex'}}>
<CssBaseline/>
<ActionBar
subscriptions={subscriptions}
selectedSubscription={selectedSubscription}
onUnsubscribe={handleUnsubscribe}
onMobileDrawerToggle={() => setMobileDrawerOpen(!mobileDrawerOpen)}
/>
<Box component="nav" sx={{width: {sm: Navigation.width}, flexShrink: {sm: 0}}}>
@ -110,7 +103,6 @@ const Root = () => {
mobileDrawerOpen={mobileDrawerOpen}
notificationsGranted={notificationsGranted}
onMobileDrawerToggle={() => setMobileDrawerOpen(!mobileDrawerOpen)}
onSubscriptionClick={handleSubscriptionClick}
onSubscribeSubmit={handleSubscribeSubmit}
onRequestPermissionClick={handleRequestPermission}
/>

View File

@ -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 (
<>
<Toolbar sx={{ display: { xs: 'none', sm: 'block' } }}/>

View File

@ -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 (
<>
<IconButton
color="inherit"
size="large"
edge="end"
ref={anchorRef}
id="composition-button"
onClick={handleToggle}
>
<MoreVertIcon/>
</IconButton>
<Popper
open={open}
anchorEl={anchorRef.current}
role={undefined}
placement="bottom-start"
transition
disablePortal
>
{({TransitionProps, placement}) => (
<Grow
{...TransitionProps}
style={{
transformOrigin:
placement === 'bottom-start' ? 'left top' : 'left bottom',
}}
>
<Paper>
<ClickAwayListener onClickAway={handleClose}>
<MenuList
autoFocusItem={open}
id="composition-menu"
onKeyDown={handleListKeyDown}
>
<MenuItem onClick={handleSendTestMessage}>Send test notification</MenuItem>
<MenuItem onClick={handleClearAll}>Clear all notifications</MenuItem>
<MenuItem onClick={handleUnsubscribe}>Unsubscribe</MenuItem>
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</>
);
}
export default SubscribeSettings;