Philipp Heckel пре 4 година
родитељ
комит
4ba23390b5
3 измењених фајлова са 143 додато и 54 уклоњено
  1. 9 54
      web/src/components/App.js
  2. 98 0
      web/src/components/DetailSettingsIcon.js
  3. 36 0
      web/src/components/NotificationList.js

+ 9 - 54
web/src/components/App.js

@@ -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}>
+            <CssBaseline />
             <Box sx={{ display: 'flex' }}>
             <Box sx={{ display: 'flex' }}>
-                <CssBaseline />
                 <AppBar position="absolute" open={drawerOpen}>
                 <AppBar position="absolute" open={drawerOpen}>
-                    <Toolbar
-                        sx={{
-                            pr: '24px', // keep right padding when drawer closed
-                        }}
-                        color="primary"
-                    >
+                    <Toolbar sx={{pr: '24px'}} 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">
-                            <Badge badgeContent={4} color="secondary">
-                                <NotificationsIcon />
-                            </Badge>
-                        </IconButton>
+                        <DetailSettingsIcon/>
                     </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}/>
-                        </Grid>
-                    </Container>
+                    <NotificationList notifications={notifications}/>
                 </Box>
                 </Box>
             </Box>
             </Box>
             <AddDialog
             <AddDialog

+ 98 - 0
web/src/components/DetailSettingsIcon.js

@@ -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;

+ 36 - 0
web/src/components/NotificationList.js

@@ -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;