Continued work on publishing from the web app
This commit is contained in:
		
							parent
							
								
									d5eff0cd34
								
							
						
					
					
						commit
						187c19f3b2
					
				
					 6 changed files with 182 additions and 52 deletions
				
			
		|  | @ -135,7 +135,11 @@ const SettingsIcons = (props) => { | |||
|             `I'm really excited that you're trying out ntfy. Did you know that there are a few public topics, such as ntfy.sh/stats and ntfy.sh/announcements.`, | ||||
|             `It's interesting to hear what people use ntfy for. I've heard people talk about using it for so many cool things. What do you use it for?` | ||||
|         ])[0]; | ||||
|         api.publish(baseUrl, topic, message, title, priority, tags); | ||||
|         api.publish(baseUrl, topic, message, { | ||||
|             title: title, | ||||
|             priority: priority, | ||||
|             tags: tags | ||||
|         }); | ||||
|         setOpen(false); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -14,7 +14,7 @@ import {useLiveQuery} from "dexie-react-hooks"; | |||
| import subscriptionManager from "../app/SubscriptionManager"; | ||||
| import userManager from "../app/UserManager"; | ||||
| import {BrowserRouter, Outlet, Route, Routes, useOutletContext, useParams} from "react-router-dom"; | ||||
| import {expandUrl} from "../app/utils"; | ||||
| import {expandUrl, topicUrl} from "../app/utils"; | ||||
| import ErrorBoundary from "./ErrorBoundary"; | ||||
| import routes from "./routes"; | ||||
| import {useAutoSubscribe, useConnectionListeners, useLocalStorageMigration} from "./hooks"; | ||||
|  | @ -22,7 +22,6 @@ import {Backdrop, ListItemIcon, ListItemText, Menu} from "@mui/material"; | |||
| import Paper from "@mui/material/Paper"; | ||||
| import IconButton from "@mui/material/IconButton"; | ||||
| import {MoreVert} from "@mui/icons-material"; | ||||
| import InsertEmoticonIcon from "@mui/icons-material/InsertEmoticon"; | ||||
| import MenuItem from "@mui/material/MenuItem"; | ||||
| import TextField from "@mui/material/TextField"; | ||||
| import SendIcon from "@mui/icons-material/Send"; | ||||
|  | @ -30,6 +29,8 @@ import priority1 from "../img/priority-1.svg"; | |||
| import priority2 from "../img/priority-2.svg"; | ||||
| import priority4 from "../img/priority-4.svg"; | ||||
| import priority5 from "../img/priority-5.svg"; | ||||
| import api from "../app/Api"; | ||||
| import SendDialog from "./SendDialog"; | ||||
| 
 | ||||
| // TODO add drag and drop
 | ||||
| // TODO races when two tabs are open
 | ||||
|  | @ -102,7 +103,7 @@ const Layout = () => { | |||
|                 <Toolbar/> | ||||
|                 <Outlet context={{ subscriptions, selected }}/> | ||||
|             </Main> | ||||
|             <Sender/> | ||||
|             <Sender selected={selected}/> | ||||
|         </Box> | ||||
|     ); | ||||
| } | ||||
|  | @ -128,23 +129,17 @@ const Main = (props) => { | |||
|     ); | ||||
| }; | ||||
| 
 | ||||
| const priorityFiles = { | ||||
|     1: priority1, | ||||
|     2: priority2, | ||||
|     4: priority4, | ||||
|     5: priority5 | ||||
| }; | ||||
| 
 | ||||
| const Sender = (props) => { | ||||
|     const [priority, setPriority] = useState(5); | ||||
|     const [priorityAnchorEl, setPriorityAnchorEl] = React.useState(null); | ||||
|     const priorityMenuOpen = Boolean(priorityAnchorEl); | ||||
| 
 | ||||
|     const handlePriorityClick = (p) => { | ||||
|         setPriority(p); | ||||
|         setPriorityAnchorEl(null); | ||||
|     const [message, setMessage] = useState(""); | ||||
|     const [sendDialogOpen, setSendDialogOpen] = useState(false); | ||||
|     const subscription = props.selected; | ||||
|     const handleSendClick = () => { | ||||
|         api.publish(subscription.baseUrl, subscription.topic, message); | ||||
|         setMessage(""); | ||||
|     }; | ||||
| 
 | ||||
|     if (!props.selected) { | ||||
|         return null; | ||||
|     } | ||||
|     return ( | ||||
|         <Paper | ||||
|             elevation={3} | ||||
|  | @ -158,22 +153,9 @@ const Sender = (props) => { | |||
|                 backgroundColor: (theme) => theme.palette.mode === 'light' ? theme.palette.grey[100] : theme.palette.grey[900] | ||||
|             }} | ||||
|         > | ||||
|             {false && <IconButton color="inherit" size="large" edge="start"> | ||||
|             <IconButton color="inherit" size="large" edge="start" onClick={() => setSendDialogOpen(true)}> | ||||
|                 <MoreVert/> | ||||
|             </IconButton>} | ||||
|             {false && <IconButton color="inherit" size="large" edge="start" onClick={(ev) => setPriorityAnchorEl(ev.currentTarget)}> | ||||
|                 <img src={priorityFiles[priority]}/> | ||||
|             </IconButton>} | ||||
|             <Menu | ||||
|                 anchorEl={priorityAnchorEl} | ||||
|                 open={priorityMenuOpen} | ||||
|                 onClose={() => setPriorityAnchorEl(null)} | ||||
|             > | ||||
|                 {[5,4,2,1].map(p => <MenuItem onClick={() => handlePriorityClick(p)}> | ||||
|                     <ListItemIcon><img src={priorityFiles[p]}/></ListItemIcon> | ||||
|                     <ListItemText>Priority {p}</ListItemText> | ||||
|                 </MenuItem>)} | ||||
|             </Menu> | ||||
|             </IconButton> | ||||
|             <TextField | ||||
|                 autoFocus | ||||
|                 margin="dense" | ||||
|  | @ -181,11 +163,24 @@ const Sender = (props) => { | |||
|                 type="text" | ||||
|                 fullWidth | ||||
|                 variant="standard" | ||||
|                 multiline | ||||
|                 value={message} | ||||
|                 onChange={ev => setMessage(ev.target.value)} | ||||
|                 onKeyPress={(ev) => { | ||||
|                     if (ev.key === 'Enter') { | ||||
|                         ev.preventDefault(); | ||||
|                         handleSendClick(); | ||||
|                     } | ||||
|                 }} | ||||
|             /> | ||||
|             <IconButton color="inherit" size="large" edge="end"> | ||||
|             <IconButton color="inherit" size="large" edge="end" onClick={handleSendClick}> | ||||
|                 <SendIcon/> | ||||
|             </IconButton> | ||||
|             <SendDialog | ||||
|                 open={sendDialogOpen} | ||||
|                 onCancel={() => setSendDialogOpen(false)} | ||||
|                 topicUrl={topicUrl(subscription.baseUrl, subscription.topic)} | ||||
|                 message={message} | ||||
|             /> | ||||
|         </Paper> | ||||
|     ); | ||||
| }; | ||||
|  |  | |||
|  | @ -120,13 +120,12 @@ const NotificationList = (props) => { | |||
| 
 | ||||
| const NotificationItem = (props) => { | ||||
|     const notification = props.notification; | ||||
|     const subscriptionId = notification.subscriptionId; | ||||
|     const attachment = notification.attachment; | ||||
|     const date = formatShortDateTime(notification.time); | ||||
|     const otherTags = unmatchedTags(notification.tags); | ||||
|     const tags = (otherTags.length > 0) ? otherTags.join(', ') : null; | ||||
|     const handleDelete = async () => { | ||||
|         console.log(`[Notifications] Deleting notification ${notification.id} from ${subscriptionId}`); | ||||
|         console.log(`[Notifications] Deleting notification ${notification.id}`); | ||||
|         await subscriptionManager.deleteNotification(notification.id) | ||||
|     } | ||||
|     const handleCopy = (s) => { | ||||
|  |  | |||
							
								
								
									
										136
									
								
								web/src/components/SendDialog.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								web/src/components/SendDialog.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,136 @@ | |||
| import * as React from 'react'; | ||||
| import {useState} from 'react'; | ||||
| import {NotificationItem} from "./Notifications"; | ||||
| import theme from "./theme"; | ||||
| import {Link, Rating, useMediaQuery} from "@mui/material"; | ||||
| import TextField from "@mui/material/TextField"; | ||||
| import priority1 from "../img/priority-1.svg"; | ||||
| import priority2 from "../img/priority-2.svg"; | ||||
| import priority3 from "../img/priority-3.svg"; | ||||
| import priority4 from "../img/priority-4.svg"; | ||||
| import priority5 from "../img/priority-5.svg"; | ||||
| import Dialog from "@mui/material/Dialog"; | ||||
| import DialogTitle from "@mui/material/DialogTitle"; | ||||
| import DialogContent from "@mui/material/DialogContent"; | ||||
| import DialogActions from "@mui/material/DialogActions"; | ||||
| import Button from "@mui/material/Button"; | ||||
| import Typography from "@mui/material/Typography"; | ||||
| 
 | ||||
| const priorityFiles = { | ||||
|     1: priority1, | ||||
|     2: priority2, | ||||
|     3: priority3, | ||||
|     4: priority4, | ||||
|     5: priority5 | ||||
| }; | ||||
| 
 | ||||
| function IconContainer(props) { | ||||
|     const { value, ...other } = props; | ||||
|     return <span {...other}><img src={priorityFiles[value]}/></span>; | ||||
| } | ||||
| 
 | ||||
| const PrioritySelect = () => { | ||||
|     return ( | ||||
|         <Rating | ||||
|             defaultValue={3} | ||||
|             IconContainerComponent={IconContainer} | ||||
|             highlightSelectedOnly | ||||
|         /> | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| const SendDialog = (props) => { | ||||
|     const [topicUrl, setTopicUrl] = useState(props.topicUrl); | ||||
|     const [message, setMessage] = useState(props.message || ""); | ||||
|     const [title, setTitle] = useState(""); | ||||
|     const [tags, setTags] = useState(""); | ||||
|     const [click, setClick] = useState(""); | ||||
|     const [email, setEmail] = useState(""); | ||||
|     const fullScreen = useMediaQuery(theme.breakpoints.down('sm')); | ||||
|     const sendButtonEnabled = (() => { | ||||
|         return true; | ||||
|     })(); | ||||
|     const handleSubmit = async () => { | ||||
|         props.onSubmit({ | ||||
|             baseUrl: "xx", | ||||
|             username: username, | ||||
|             password: password | ||||
|         }) | ||||
|     }; | ||||
|     return ( | ||||
|         <Dialog open={props.open} onClose={props.onCancel} fullScreen={fullScreen}> | ||||
|             <DialogTitle>Publish notification</DialogTitle> | ||||
|             <DialogContent> | ||||
|                 <TextField | ||||
|                     margin="dense" | ||||
|                     label="Topic URL" | ||||
|                     value={topicUrl} | ||||
|                     onChange={ev => setTopicUrl(ev.target.value)} | ||||
|                     type="text" | ||||
|                     variant="standard" | ||||
|                     fullWidth | ||||
|                     required | ||||
|                 /> | ||||
|                 <TextField | ||||
|                     margin="dense" | ||||
|                     label="Message" | ||||
|                     value={message} | ||||
|                     onChange={ev => setMessage(ev.target.value)} | ||||
|                     type="text" | ||||
|                     variant="standard" | ||||
|                     fullWidth | ||||
|                     required | ||||
|                     autoFocus | ||||
|                     multiline | ||||
|                 /> | ||||
|                 <TextField | ||||
|                     margin="dense" | ||||
|                     label="Title" | ||||
|                     value={title} | ||||
|                     onChange={ev => setTitle(ev.target.value)} | ||||
|                     type="text" | ||||
|                     fullWidth | ||||
|                     variant="standard" | ||||
|                 /> | ||||
|                 <TextField | ||||
|                     margin="dense" | ||||
|                     label="Tags" | ||||
|                     value={tags} | ||||
|                     onChange={ev => setTags(ev.target.value)} | ||||
|                     type="text" | ||||
|                     fullWidth | ||||
|                     variant="standard" | ||||
|                 /> | ||||
|                 <TextField | ||||
|                     margin="dense" | ||||
|                     label="Click URL" | ||||
|                     value={click} | ||||
|                     onChange={ev => setClick(ev.target.value)} | ||||
|                     type="url" | ||||
|                     fullWidth | ||||
|                     variant="standard" | ||||
|                 /> | ||||
|                 <TextField | ||||
|                     margin="dense" | ||||
|                     label="Email" | ||||
|                     value={email} | ||||
|                     onChange={ev => setEmail(ev.target.value)} | ||||
|                     type="email" | ||||
|                     fullWidth | ||||
|                     variant="standard" | ||||
|                 /> | ||||
|                 <PrioritySelect/> | ||||
|                 <Typography variant="body1"> | ||||
|                     For details on what these fields mean, please check out the | ||||
|                     {" "}<Link href="/docs">documentation</Link>. | ||||
|                 </Typography> | ||||
|             </DialogContent> | ||||
|             <DialogActions> | ||||
|                 <Button onClick={props.onCancel}>Cancel</Button> | ||||
|                 <Button onClick={handleSubmit} disabled={!sendButtonEnabled}>Send</Button> | ||||
|             </DialogActions> | ||||
|         </Dialog> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| export default SendDialog; | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue