| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244 |
- import * as React from 'react';
- import {useEffect, useState} from 'react';
- import Typography from '@mui/material/Typography';
- import Box from '@mui/material/Box';
- import {styled, ThemeProvider} from '@mui/material/styles';
- import CssBaseline from '@mui/material/CssBaseline';
- import MuiDrawer from '@mui/material/Drawer';
- import MuiAppBar from '@mui/material/AppBar';
- import Toolbar from '@mui/material/Toolbar';
- import ChatBubbleOutlineIcon from '@mui/icons-material/ChatBubbleOutline';
- import List from '@mui/material/List';
- import Divider from '@mui/material/Divider';
- import IconButton from '@mui/material/IconButton';
- import MenuIcon from '@mui/icons-material/Menu';
- import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
- import ListItemIcon from "@mui/material/ListItemIcon";
- import ListItemText from "@mui/material/ListItemText";
- import ListItemButton from "@mui/material/ListItemButton";
- import SettingsIcon from "@mui/icons-material/Settings";
- import AddIcon from "@mui/icons-material/Add";
- import AddDialog from "./AddDialog";
- import NotificationList from "./NotificationList";
- import DetailSettingsIcon from "./DetailSettingsIcon";
- import theme from "./theme";
- import api from "../app/Api";
- import repository from "../app/Repository";
- import connectionManager from "../app/ConnectionManager";
- import Subscriptions from "../app/Subscriptions";
- const drawerWidth = 240;
- const AppBar = styled(MuiAppBar, {
- shouldForwardProp: (prop) => prop !== 'open',
- })(({ theme, open }) => ({
- zIndex: theme.zIndex.drawer + 1,
- transition: theme.transitions.create(['width', 'margin'], {
- easing: theme.transitions.easing.sharp,
- duration: theme.transitions.duration.leavingScreen,
- }),
- ...(open && {
- marginLeft: drawerWidth,
- width: `calc(100% - ${drawerWidth}px)`,
- transition: theme.transitions.create(['width', 'margin'], {
- easing: theme.transitions.easing.sharp,
- duration: theme.transitions.duration.enteringScreen,
- }),
- }),
- }));
- const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open' })(
- ({ theme, open }) => ({
- '& .MuiDrawer-paper': {
- position: 'relative',
- whiteSpace: 'nowrap',
- width: drawerWidth,
- transition: theme.transitions.create('width', {
- easing: theme.transitions.easing.sharp,
- duration: theme.transitions.duration.enteringScreen,
- }),
- boxSizing: 'border-box',
- ...(!open && {
- overflowX: 'hidden',
- transition: theme.transitions.create('width', {
- easing: theme.transitions.easing.sharp,
- duration: theme.transitions.duration.leavingScreen,
- }),
- width: theme.spacing(7),
- [theme.breakpoints.up('sm')]: {
- width: theme.spacing(9),
- },
- }),
- },
- }),
- );
- const SubscriptionNav = (props) => {
- const subscriptions = props.subscriptions;
- return (
- <>
- {subscriptions.map((id, subscription) =>
- <SubscriptionNavItem
- key={id}
- subscription={subscription}
- selected={props.selectedSubscription && props.selectedSubscription.id === id}
- onClick={() => props.handleSubscriptionClick(id)}
- />)
- }
- </>
- );
- }
- const SubscriptionNavItem = (props) => {
- const subscription = props.subscription;
- return (
- <ListItemButton onClick={props.onClick} selected={props.selected}>
- <ListItemIcon><ChatBubbleOutlineIcon /></ListItemIcon>
- <ListItemText primary={subscription.shortUrl()}/>
- </ListItemButton>
- );
- }
- const App = () => {
- console.log("Launching App component");
- const [drawerOpen, setDrawerOpen] = useState(true);
- const [subscriptions, setSubscriptions] = useState(new Subscriptions());
- const [selectedSubscription, setSelectedSubscription] = useState(null);
- const [subscribeDialogOpen, setSubscribeDialogOpen] = useState(false);
- const handleNotification = (subscriptionId, notification) => {
- setSubscriptions(prev => {
- const newSubscription = prev.get(subscriptionId).addNotification(notification);
- return prev.update(newSubscription).clone();
- });
- };
- const handleSubscribeSubmit = (subscription) => {
- setSubscribeDialogOpen(false);
- setSubscriptions(prev => prev.add(subscription).clone());
- setSelectedSubscription(subscription);
- api.poll(subscription.baseUrl, subscription.topic)
- .then(messages => {
- setSubscriptions(prev => {
- const newSubscription = prev.get(subscription.id).addNotifications(messages);
- return prev.update(newSubscription).clone();
- });
- });
- };
- const handleSubscribeCancel = () => {
- console.log(`Cancel clicked`);
- setSubscribeDialogOpen(false);
- };
- const handleUnsubscribe = (subscriptionId) => {
- setSubscriptions(prev => {
- const newSubscriptions = prev.remove(subscriptionId).clone();
- setSelectedSubscription(newSubscriptions.firstOrNull());
- return newSubscriptions;
- });
- };
- const handleSubscriptionClick = (subscriptionId) => {
- console.log(`Selected subscription ${subscriptionId}`);
- setSelectedSubscription(subscriptions.get(subscriptionId));
- };
- const notifications = (selectedSubscription !== null) ? selectedSubscription.notifications : [];
- const toggleDrawer = () => {
- setDrawerOpen(!drawerOpen);
- };
- useEffect(() => {
- connectionManager.refresh(subscriptions, handleNotification);
- repository.saveSubscriptions(subscriptions);
- }, [subscriptions]);
- return (
- <ThemeProvider theme={theme}>
- <CssBaseline />
- <Box sx={{ display: 'flex' }}>
- <AppBar position="absolute" open={drawerOpen}>
- <Toolbar sx={{pr: '24px'}} color="primary">
- <IconButton
- edge="start"
- color="inherit"
- aria-label="open drawer"
- onClick={toggleDrawer}
- sx={{
- marginRight: '36px',
- ...(drawerOpen && { display: 'none' }),
- }}
- >
- <MenuIcon />
- </IconButton>
- <Typography
- component="h1"
- variant="h6"
- color="inherit"
- noWrap
- sx={{ flexGrow: 1 }}
- >
- {(selectedSubscription !== null) ? selectedSubscription.shortUrl() : "ntfy"}
- </Typography>
- {selectedSubscription !== null && <DetailSettingsIcon
- subscription={selectedSubscription}
- onUnsubscribe={handleUnsubscribe}
- />}
- </Toolbar>
- </AppBar>
- <Drawer variant="permanent" open={drawerOpen}>
- <Toolbar
- sx={{
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'flex-end',
- px: [1],
- }}
- >
- <IconButton onClick={toggleDrawer}>
- <ChevronLeftIcon />
- </IconButton>
- </Toolbar>
- <Divider />
- <List component="nav">
- <SubscriptionNav
- subscriptions={subscriptions}
- selectedSubscription={selectedSubscription}
- handleSubscriptionClick={handleSubscriptionClick}
- />
- <Divider sx={{ my: 1 }} />
- <ListItemButton>
- <ListItemIcon>
- <SettingsIcon />
- </ListItemIcon>
- <ListItemText primary="Settings" />
- </ListItemButton>
- <ListItemButton onClick={() => setSubscribeDialogOpen(true)}>
- <ListItemIcon>
- <AddIcon />
- </ListItemIcon>
- <ListItemText primary="Add subscription" />
- </ListItemButton>
- </List>
- </Drawer>
- <Box
- component="main"
- sx={{
- backgroundColor: (theme) =>
- theme.palette.mode === 'light'
- ? theme.palette.grey[100]
- : theme.palette.grey[900],
- flexGrow: 1,
- height: '100vh',
- overflow: 'auto',
- }}
- >
- <Toolbar />
- <NotificationList notifications={notifications}/>
- </Box>
- </Box>
- <AddDialog
- open={subscribeDialogOpen}
- onCancel={handleSubscribeCancel}
- onSubmit={handleSubscribeSubmit}
- />
- </ThemeProvider>
- );
- }
- export default App;
|