Add dialog
This commit is contained in:
		
							parent
							
								
									c859f866b8
								
							
						
					
					
						commit
						8c0f3b2304
					
				
					 4 changed files with 86 additions and 88 deletions
				
			
		
							
								
								
									
										55
									
								
								web/src/AddDialog.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								web/src/AddDialog.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,55 @@ | ||||||
|  | import * as React from 'react'; | ||||||
|  | import Button from '@mui/material/Button'; | ||||||
|  | import TextField from '@mui/material/TextField'; | ||||||
|  | import Dialog from '@mui/material/Dialog'; | ||||||
|  | import DialogActions from '@mui/material/DialogActions'; | ||||||
|  | import DialogContent from '@mui/material/DialogContent'; | ||||||
|  | import DialogContentText from '@mui/material/DialogContentText'; | ||||||
|  | import DialogTitle from '@mui/material/DialogTitle'; | ||||||
|  | import {useState} from "react"; | ||||||
|  | import Subscription from "./Subscription"; | ||||||
|  | 
 | ||||||
|  | const defaultBaseUrl = "https://ntfy.sh" | ||||||
|  | 
 | ||||||
|  | const AddDialog = (props) => { | ||||||
|  |     const [topic, setTopic] = useState(""); | ||||||
|  |     const handleCancel = () => { | ||||||
|  |         setTopic(''); | ||||||
|  |         props.onCancel(); | ||||||
|  |     } | ||||||
|  |     const handleSubmit = () => { | ||||||
|  |         const subscription = new Subscription(defaultBaseUrl, topic); | ||||||
|  |         props.onSubmit(subscription); | ||||||
|  |         setTopic(''); | ||||||
|  |     } | ||||||
|  |     return ( | ||||||
|  |         <> | ||||||
|  |             <Dialog open={props.open} onClose={props.onClose}> | ||||||
|  |                 <DialogTitle>Subscribe to topic</DialogTitle> | ||||||
|  |                 <DialogContent> | ||||||
|  |                     <DialogContentText> | ||||||
|  |                         Topics may not be password-protected, so choose a name that's not easy to guess. | ||||||
|  |                         Once subscribed, you can PUT/POST notifications. | ||||||
|  |                     </DialogContentText> | ||||||
|  |                     <TextField | ||||||
|  |                         autoFocus | ||||||
|  |                         margin="dense" | ||||||
|  |                         id="name" | ||||||
|  |                         label="Topic name, e.g. phil_alerts" | ||||||
|  |                         value={topic} | ||||||
|  |                         onChange={ev => setTopic(ev.target.value)} | ||||||
|  |                         type="text" | ||||||
|  |                         fullWidth | ||||||
|  |                         variant="standard" | ||||||
|  |                     /> | ||||||
|  |                 </DialogContent> | ||||||
|  |                 <DialogActions> | ||||||
|  |                     <Button onClick={handleCancel}>Cancel</Button> | ||||||
|  |                     <Button onClick={handleSubmit} autoFocus disabled={topic === ""}>Subscribe</Button> | ||||||
|  |                 </DialogActions> | ||||||
|  |             </Dialog> | ||||||
|  |         </> | ||||||
|  |     ); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default AddDialog; | ||||||
							
								
								
									
										100
									
								
								web/src/App.js
									
										
									
									
									
								
							
							
						
						
									
										100
									
								
								web/src/App.js
									
										
									
									
									
								
							|  | @ -4,9 +4,8 @@ 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 Link from '@mui/material/Link'; | import Link from '@mui/material/Link'; | ||||||
| import Subscription from './Subscription'; |  | ||||||
| import WsConnection from './WsConnection'; | import WsConnection from './WsConnection'; | ||||||
| import {createTheme, styled, ThemeProvider} from '@mui/material/styles'; | import {styled, ThemeProvider} from '@mui/material/styles'; | ||||||
| import CssBaseline from '@mui/material/CssBaseline'; | import CssBaseline from '@mui/material/CssBaseline'; | ||||||
| import MuiDrawer from '@mui/material/Drawer'; | import MuiDrawer from '@mui/material/Drawer'; | ||||||
| import MuiAppBar from '@mui/material/AppBar'; | import MuiAppBar from '@mui/material/AppBar'; | ||||||
|  | @ -17,7 +16,6 @@ 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 Badge from '@mui/material/Badge'; | ||||||
| import Grid from '@mui/material/Grid'; | import Grid from '@mui/material/Grid'; | ||||||
| import Paper from '@mui/material/Paper'; |  | ||||||
| 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 NotificationsIcon from '@mui/icons-material/Notifications'; | ||||||
|  | @ -28,19 +26,8 @@ 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 Card from "@mui/material/Card"; | ||||||
| import {Button, CardActions, CardContent, Stack} from "@mui/material"; | import {Button, CardActions, CardContent, Stack} from "@mui/material"; | ||||||
| 
 | import AddDialog from "./AddDialog"; | ||||||
| function Copyright(props) { | import theme from "./theme"; | ||||||
|     return ( |  | ||||||
|         <Typography variant="body2" color="text.secondary" align="center" {...props}> |  | ||||||
|             {'Copyright © '} |  | ||||||
|             <Link color="inherit" href="https://mui.com/"> |  | ||||||
|                 Your Website |  | ||||||
|             </Link>{' '} |  | ||||||
|             {new Date().getFullYear()} |  | ||||||
|             {'.'} |  | ||||||
|         </Typography> |  | ||||||
|     ); |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| const drawerWidth = 240; | const drawerWidth = 240; | ||||||
| 
 | 
 | ||||||
|  | @ -88,32 +75,29 @@ const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open' | ||||||
|     }), |     }), | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
| const mdTheme = createTheme(); |  | ||||||
| 
 | 
 | ||||||
| const SubscriptionNav = (props) => { | const SubscriptionNav = (props) => { | ||||||
|     const subscriptions = props.subscriptions; |     const subscriptions = props.subscriptions; | ||||||
|     return ( |     return ( | ||||||
|         <div className="subscriptionList"> |         <> | ||||||
|             {Object.keys(subscriptions).map(id => |             {Object.keys(subscriptions).map(id => | ||||||
|                 <SubscriptionItem |                 <SubscriptionNavItem | ||||||
|                     key={id} |                     key={id} | ||||||
|                     subscription={subscriptions[id]} |                     subscription={subscriptions[id]} | ||||||
|                     selected={props.selectedSubscription === subscriptions[id]} |                     selected={props.selectedSubscription === subscriptions[id]} | ||||||
|                     onClick={() => props.handleSubscriptionClick(id)} |                     onClick={() => props.handleSubscriptionClick(id)} | ||||||
|                 />) |                 />) | ||||||
|             } |             } | ||||||
|         </div> |         </> | ||||||
|     ); |     ); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const SubscriptionItem = (props) => { | const SubscriptionNavItem = (props) => { | ||||||
|     const subscription = props.subscription; |     const subscription = props.subscription; | ||||||
|     return ( |     return ( | ||||||
|         <ListItemButton onClick={props.onClick}> |         <ListItemButton onClick={props.onClick} selected={props.selected}> | ||||||
|             <ListItemIcon> |             <ListItemIcon><ChatBubbleOutlineIcon /></ListItemIcon> | ||||||
|                 <ChatBubbleOutlineIcon /> |             <ListItemText primary={subscription.shortUrl()}/> | ||||||
|             </ListItemIcon> |  | ||||||
|             <ListItemText primary={subscription.shortUrl()} /> |  | ||||||
|         </ListItemButton> |         </ListItemButton> | ||||||
|     ); |     ); | ||||||
| } | } | ||||||
|  | @ -136,68 +120,48 @@ const NotificationItem = (props) => { | ||||||
|                     {notification.time} |                     {notification.time} | ||||||
|                 </Typography> |                 </Typography> | ||||||
|                 {notification.title && <Typography variant="h5" component="div"> |                 {notification.title && <Typography variant="h5" component="div"> | ||||||
|                     title: {notification.title} |                     {notification.title} | ||||||
|                 </Typography>} |                 </Typography>} | ||||||
|                 <Typography variant="body1"> |                 <Typography variant="body1"> | ||||||
|                     msg: {notification.message} |                     {notification.message} | ||||||
|                 </Typography> |                 </Typography> | ||||||
|             </CardContent> |             </CardContent> | ||||||
|             <CardActions> |  | ||||||
|                 <Button size="small">Learn More</Button> |  | ||||||
|             </CardActions> |  | ||||||
|         </Card> |         </Card> | ||||||
|     ); |     ); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const defaultBaseUrl = "https://ntfy.sh" |  | ||||||
| 
 |  | ||||||
| const SubscriptionAddForm = (props) => { |  | ||||||
|     const [topic, setTopic] = useState(""); |  | ||||||
|     const handleSubmit = (ev) => { |  | ||||||
|         ev.preventDefault(); |  | ||||||
|         props.onSubmit(new Subscription(defaultBaseUrl, topic)); |  | ||||||
|         setTopic(''); |  | ||||||
|     } |  | ||||||
|     return ( |  | ||||||
|         <form onSubmit={handleSubmit}> |  | ||||||
|             <input |  | ||||||
|                 type="text" |  | ||||||
|                 value={topic} |  | ||||||
|                 onChange={ev => setTopic(ev.target.value)} |  | ||||||
|                 placeholder="Topic name, e.g. phil_alerts" |  | ||||||
|                 required |  | ||||||
|                 /> |  | ||||||
|         </form> |  | ||||||
|     ); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const App = () => { | const App = () => { | ||||||
|     const [open, setOpen] = React.useState(true); |     const [drawerOpen, setDrawerOpen] = useState(true); | ||||||
|     const [subscriptions, setSubscriptions] = useState({}); |     const [subscriptions, setSubscriptions] = useState({}); | ||||||
|     const [selectedSubscription, setSelectedSubscription] = useState(null); |     const [selectedSubscription, setSelectedSubscription] = useState(null); | ||||||
|     const [connections, setConnections] = useState({}); |     const [connections, setConnections] = useState({}); | ||||||
|  |     const [addDialogOpen, setAddDialogOpen] = useState(false); | ||||||
|     const subscriptionChanged = (subscription) => { |     const subscriptionChanged = (subscription) => { | ||||||
|         setSubscriptions(prev => ({...prev, [subscription.id]: subscription})); // Fake-replace
 |         setSubscriptions(prev => ({...prev, [subscription.id]: subscription})); // Fake-replace
 | ||||||
|     }; |     }; | ||||||
|     const addSubscription = (subscription) => { |     const handleAddSubmit = (subscription) => { | ||||||
|  |         setAddDialogOpen(false); | ||||||
|         const connection = new WsConnection(subscription, subscriptionChanged); |         const connection = new WsConnection(subscription, subscriptionChanged); | ||||||
|         setSubscriptions(prev => ({...prev, [subscription.id]: subscription})); |         setSubscriptions(prev => ({...prev, [subscription.id]: subscription})); | ||||||
|         setConnections(prev => ({...prev, [connection.id]: connection})); |         setConnections(prev => ({...prev, [connection.id]: connection})); | ||||||
|         connection.start(); |         connection.start(); | ||||||
|     }; |     }; | ||||||
|  |     const handleAddCancel = () => { | ||||||
|  |         setAddDialogOpen(false); | ||||||
|  |     } | ||||||
|     const handleSubscriptionClick = (subscriptionId) => { |     const handleSubscriptionClick = (subscriptionId) => { | ||||||
|         console.log(`handleSubscriptionClick ${subscriptionId}`) |         console.log(`handleSubscriptionClick ${subscriptionId}`) | ||||||
|         setSelectedSubscription(subscriptions[subscriptionId]); |         setSelectedSubscription(subscriptions[subscriptionId]); | ||||||
|     }; |     }; | ||||||
|     const notifications = (selectedSubscription !== null) ? selectedSubscription.notifications : []; |     const notifications = (selectedSubscription !== null) ? selectedSubscription.notifications : []; | ||||||
|     const toggleDrawer = () => { |     const toggleDrawer = () => { | ||||||
|         setOpen(!open); |         setDrawerOpen(!drawerOpen); | ||||||
|     }; |     }; | ||||||
|     return ( |     return ( | ||||||
|         <ThemeProvider theme={mdTheme}> |         <ThemeProvider theme={theme}> | ||||||
|             <Box sx={{ display: 'flex' }}> |             <Box sx={{ display: 'flex' }}> | ||||||
|                 <CssBaseline /> |                 <CssBaseline /> | ||||||
|                 <AppBar position="absolute" open={open}> |                 <AppBar position="absolute" open={drawerOpen}> | ||||||
|                     <Toolbar |                     <Toolbar | ||||||
|                         sx={{ |                         sx={{ | ||||||
|                             pr: '24px', // keep right padding when drawer closed
 |                             pr: '24px', // keep right padding when drawer closed
 | ||||||
|  | @ -211,7 +175,7 @@ const App = () => { | ||||||
|                             onClick={toggleDrawer} |                             onClick={toggleDrawer} | ||||||
|                             sx={{ |                             sx={{ | ||||||
|                                 marginRight: '36px', |                                 marginRight: '36px', | ||||||
|                                 ...(open && { display: 'none' }), |                                 ...(drawerOpen && { display: 'none' }), | ||||||
|                             }} |                             }} | ||||||
|                         > |                         > | ||||||
|                             <MenuIcon /> |                             <MenuIcon /> | ||||||
|  | @ -232,7 +196,7 @@ const App = () => { | ||||||
|                         </IconButton> |                         </IconButton> | ||||||
|                     </Toolbar> |                     </Toolbar> | ||||||
|                 </AppBar> |                 </AppBar> | ||||||
|                 <Drawer variant="permanent" open={open}> |                 <Drawer variant="permanent" open={drawerOpen}> | ||||||
|                     <Toolbar |                     <Toolbar | ||||||
|                         sx={{ |                         sx={{ | ||||||
|                             display: 'flex', |                             display: 'flex', | ||||||
|  | @ -259,7 +223,7 @@ const App = () => { | ||||||
|                             </ListItemIcon> |                             </ListItemIcon> | ||||||
|                             <ListItemText primary="Settings" /> |                             <ListItemText primary="Settings" /> | ||||||
|                         </ListItemButton> |                         </ListItemButton> | ||||||
|                         <ListItemButton> |                         <ListItemButton onClick={() => setAddDialogOpen(true)}> | ||||||
|                             <ListItemIcon> |                             <ListItemIcon> | ||||||
|                                 <AddIcon /> |                                 <AddIcon /> | ||||||
|                             </ListItemIcon> |                             </ListItemIcon> | ||||||
|  | @ -281,21 +245,17 @@ const App = () => { | ||||||
|                 > |                 > | ||||||
|                     <Toolbar /> |                     <Toolbar /> | ||||||
|                     <Container maxWidth="lg" sx={{ mt: 4, mb: 4 }}> |                     <Container maxWidth="lg" sx={{ mt: 4, mb: 4 }}> | ||||||
| 
 |  | ||||||
|                         <Grid container spacing={3}> |                         <Grid container spacing={3}> | ||||||
|                             <SubscriptionAddForm onSubmit={addSubscription}/> |  | ||||||
|                             <NotificationList notifications={notifications}/> |                             <NotificationList notifications={notifications}/> | ||||||
|                             {/* Recent Orders */} |  | ||||||
|                             <Grid item xs={12}> |  | ||||||
|                                 <Paper sx={{ p: 2, display: 'flex', flexDirection: 'column' }}> |  | ||||||
| 
 |  | ||||||
|                                 </Paper> |  | ||||||
|                             </Grid> |  | ||||||
|                         </Grid> |                         </Grid> | ||||||
|                         <Copyright sx={{ pt: 4 }} /> |  | ||||||
|                     </Container> |                     </Container> | ||||||
|                 </Box> |                 </Box> | ||||||
|             </Box> |             </Box> | ||||||
|  |             <AddDialog | ||||||
|  |                 open={addDialogOpen} | ||||||
|  |                 onCancel={handleAddCancel} | ||||||
|  |                 onSubmit={handleAddSubmit} | ||||||
|  |             /> | ||||||
|         </ThemeProvider> |         </ThemeProvider> | ||||||
|     ); |     ); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,17 +0,0 @@ | ||||||
| import * as React from 'react'; |  | ||||||
| import PropTypes from 'prop-types'; |  | ||||||
| import Typography from '@mui/material/Typography'; |  | ||||||
| 
 |  | ||||||
| function Title(props) { |  | ||||||
|   return ( |  | ||||||
|     <Typography component="h2" variant="h6" color="primary" gutterBottom> |  | ||||||
|       {props.children} |  | ||||||
|     </Typography> |  | ||||||
|   ); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| Title.propTypes = { |  | ||||||
|   children: PropTypes.node, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export default Title; |  | ||||||
|  | @ -8,7 +8,7 @@ const theme = createTheme({ | ||||||
|       main: '#338574', |       main: '#338574', | ||||||
|     }, |     }, | ||||||
|     secondary: { |     secondary: { | ||||||
|       main: '#338574', |       main: '#6cead0', | ||||||
|     }, |     }, | ||||||
|     error: { |     error: { | ||||||
|       main: red.A400, |       main: red.A400, | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue