Settings icon

This commit is contained in:
Philipp Heckel 2022-02-21 16:24:13 -05:00
parent dd1a85e733
commit 4ba23390b5
3 changed files with 143 additions and 54 deletions

View file

@ -1,6 +1,5 @@
import * as React from 'react'; import * as React from 'react';
import {useEffect, useState} from 'react'; import {useEffect, useState} from 'react';
import Container from '@mui/material/Container';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import WsConnection from '../app/WsConnection'; import WsConnection from '../app/WsConnection';
@ -13,19 +12,16 @@ import ChatBubbleOutlineIcon from '@mui/icons-material/ChatBubbleOutline';
import List from '@mui/material/List'; import List from '@mui/material/List';
import Divider from '@mui/material/Divider'; import Divider from '@mui/material/Divider';
import IconButton from '@mui/material/IconButton'; import IconButton from '@mui/material/IconButton';
import Badge from '@mui/material/Badge';
import Grid from '@mui/material/Grid';
import MenuIcon from '@mui/icons-material/Menu'; import MenuIcon from '@mui/icons-material/Menu';
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'; import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
import NotificationsIcon from '@mui/icons-material/Notifications';
import ListItemIcon from "@mui/material/ListItemIcon"; import ListItemIcon from "@mui/material/ListItemIcon";
import ListItemText from "@mui/material/ListItemText"; import ListItemText from "@mui/material/ListItemText";
import ListItemButton from "@mui/material/ListItemButton"; import ListItemButton from "@mui/material/ListItemButton";
import SettingsIcon from "@mui/icons-material/Settings"; import SettingsIcon from "@mui/icons-material/Settings";
import AddIcon from "@mui/icons-material/Add"; import AddIcon from "@mui/icons-material/Add";
import Card from "@mui/material/Card";
import {CardContent, Stack} from "@mui/material";
import AddDialog from "./AddDialog"; import AddDialog from "./AddDialog";
import NotificationList from "./NotificationList";
import DetailSettingsIcon from "./DetailSettingsIcon";
import theme from "./theme"; import theme from "./theme";
import LocalStorage from "../app/Storage"; import LocalStorage from "../app/Storage";
@ -102,34 +98,6 @@ const SubscriptionNavItem = (props) => {
); );
} }
const NotificationList = (props) => {
return (
<Stack spacing={3} className="notificationList">
{props.notifications.map(notification =>
<NotificationItem key={notification.id} notification={notification}/>)}
</Stack>
);
}
const NotificationItem = (props) => {
const notification = props.notification;
return (
<Card sx={{ minWidth: 275 }}>
<CardContent>
<Typography sx={{ fontSize: 14 }} color="text.secondary" gutterBottom>
{notification.time}
</Typography>
{notification.title && <Typography variant="h5" component="div">
{notification.title}
</Typography>}
<Typography variant="body1">
{notification.message}
</Typography>
</CardContent>
</Card>
);
}
const App = () => { const App = () => {
console.log("Launching App component"); console.log("Launching App component");
@ -149,11 +117,11 @@ const App = () => {
connection.start(); connection.start();
}; };
const handleAddCancel = () => { const handleAddCancel = () => {
console.log(`Cancel clicked`) console.log(`Cancel clicked`);
setAddDialogOpen(false); setAddDialogOpen(false);
} }
const handleSubscriptionClick = (subscriptionId) => { const handleSubscriptionClick = (subscriptionId) => {
console.log(`Selected subscription ${subscriptionId}`) console.log(`Selected subscription ${subscriptionId}`);
setSelectedSubscription(subscriptions[subscriptionId]); setSelectedSubscription(subscriptions[subscriptionId]);
}; };
const notifications = (selectedSubscription !== null) ? selectedSubscription.notifications : []; const notifications = (selectedSubscription !== null) ? selectedSubscription.notifications : [];
@ -183,15 +151,10 @@ const App = () => {
}, [subscriptions]); }, [subscriptions]);
return ( return (
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<Box sx={{ display: 'flex' }}>
<CssBaseline /> <CssBaseline />
<Box sx={{ display: 'flex' }}>
<AppBar position="absolute" open={drawerOpen}> <AppBar position="absolute" open={drawerOpen}>
<Toolbar <Toolbar sx={{pr: '24px'}} color="primary">
sx={{
pr: '24px', // keep right padding when drawer closed
}}
color="primary"
>
<IconButton <IconButton
edge="start" edge="start"
color="inherit" color="inherit"
@ -211,13 +174,9 @@ const App = () => {
noWrap noWrap
sx={{ flexGrow: 1 }} sx={{ flexGrow: 1 }}
> >
ntfy {(selectedSubscription != null) ? selectedSubscription.shortUrl() : "ntfy.sh"}
</Typography> </Typography>
<IconButton color="inherit"> <DetailSettingsIcon/>
<Badge badgeContent={4} color="secondary">
<NotificationsIcon />
</Badge>
</IconButton>
</Toolbar> </Toolbar>
</AppBar> </AppBar>
<Drawer variant="permanent" open={drawerOpen}> <Drawer variant="permanent" open={drawerOpen}>
@ -268,11 +227,7 @@ const App = () => {
}} }}
> >
<Toolbar /> <Toolbar />
<Container maxWidth="lg" sx={{ mt: 4, mb: 4 }}>
<Grid container spacing={3}>
<NotificationList notifications={notifications}/> <NotificationList notifications={notifications}/>
</Grid>
</Container>
</Box> </Box>
</Box> </Box>
<AddDialog <AddDialog

View file

@ -0,0 +1,98 @@
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";
// Originally from https://mui.com/components/menus/#MenuListComposition.js
const DetailSettingsIcon = () => {
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);
};
function 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"
aria-controls={open ? 'composition-menu' : undefined}
aria-expanded={open ? 'true' : undefined}
aria-haspopup="true"
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"
aria-labelledby="composition-button"
onKeyDown={handleListKeyDown}
>
<MenuItem onClick={handleClose}>Send test notification</MenuItem>
<MenuItem onClick={handleClose}>Unsubscribe</MenuItem>
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</>
);
}
export default DetailSettingsIcon;

View file

@ -0,0 +1,36 @@
import Container from "@mui/material/Container";
import {CardContent, Stack} from "@mui/material";
import Card from "@mui/material/Card";
import Typography from "@mui/material/Typography";
import * as React from "react";
const NotificationList = (props) => {
const sortedNotifications = props.notifications.sort((a, b) => a.time < b.time);
return (
<Container maxWidth="lg" sx={{ marginTop: 3 }}>
<Stack container spacing={3}>
{sortedNotifications.map(notification =>
<NotificationItem key={notification.id} notification={notification}/>)}
</Stack>
</Container>
);
}
const NotificationItem = (props) => {
const notification = props.notification;
const date = new Intl.DateTimeFormat('default', {dateStyle: 'short', timeStyle: 'short'})
.format(new Date(notification.time * 1000));
const tags = (notification.tags && notification.tags.length > 0) ? notification.tags.join(', ') : null;
return (
<Card sx={{ minWidth: 275 }}>
<CardContent>
<Typography sx={{ fontSize: 14 }} color="text.secondary">{date}</Typography>
{notification.title && <Typography variant="h5" component="div">{notification.title}</Typography>}
<Typography variant="body1" gutterBottom>{notification.message}</Typography>
{tags && <Typography sx={{ fontSize: 14 }} color="text.secondary">Tags: {tags}</Typography>}
</CardContent>
</Card>
);
}
export default NotificationList;