mirror of
				https://github.com/binwiederhier/ntfy.git
				synced 2025-10-31 13:02:24 +01:00 
			
		
		
		
	SendDialog, cont'd
This commit is contained in:
		
							parent
							
								
									b20df55b88
								
							
						
					
					
						commit
						2eeb7d63a0
					
				
					 3 changed files with 220 additions and 71 deletions
				
			
		web/src
|  | @ -26,6 +26,7 @@ import TextField from "@mui/material/TextField"; | |||
| import SendIcon from "@mui/icons-material/Send"; | ||||
| import api from "../app/Api"; | ||||
| import SendDialog from "./SendDialog"; | ||||
| import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; | ||||
| 
 | ||||
| // TODO add drag and drop
 | ||||
| // TODO races when two tabs are open
 | ||||
|  | @ -149,7 +150,7 @@ const Sender = (props) => { | |||
|             }} | ||||
|         > | ||||
|             <IconButton color="inherit" size="large" edge="start" onClick={() => setSendDialogOpen(true)}> | ||||
|                 <MoreVert/> | ||||
|                 <KeyboardArrowUpIcon/> | ||||
|             </IconButton> | ||||
|             <TextField | ||||
|                 autoFocus | ||||
|  |  | |||
|  | @ -2,7 +2,17 @@ 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 { | ||||
|     Chip, | ||||
|     FormControl, | ||||
|     InputAdornment, InputLabel, | ||||
|     Link, | ||||
|     ListItemIcon, | ||||
|     ListItemText, | ||||
|     Select, | ||||
|     Tooltip, | ||||
|     useMediaQuery | ||||
| } from "@mui/material"; | ||||
| import TextField from "@mui/material/TextField"; | ||||
| import priority1 from "../img/priority-1.svg"; | ||||
| import priority2 from "../img/priority-2.svg"; | ||||
|  | @ -15,37 +25,30 @@ 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 | ||||
|         /> | ||||
|     ); | ||||
| } | ||||
| import IconButton from "@mui/material/IconButton"; | ||||
| import InsertEmoticonIcon from '@mui/icons-material/InsertEmoticon'; | ||||
| import {Close} from "@mui/icons-material"; | ||||
| import MenuItem from "@mui/material/MenuItem"; | ||||
| 
 | ||||
| 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 [priority, setPriority] = useState(3); | ||||
|     const [clickUrl, setClickUrl] = useState(""); | ||||
|     const [attachUrl, setAttachUrl] = useState(""); | ||||
|     const [filename, setFilename] = useState(""); | ||||
|     const [email, setEmail] = useState(""); | ||||
|     const [delay, setDelay] = useState(""); | ||||
| 
 | ||||
|     const [showTopicUrl, setShowTopicUrl] = useState(props.topicUrl === ""); | ||||
|     const [showClickUrl, setShowClickUrl] = useState(false); | ||||
|     const [showAttachUrl, setShowAttachUrl] = useState(false); | ||||
|     const [showAttachFile, setShowAttachFile] = useState(false); | ||||
|     const [showEmail, setShowEmail] = useState(false); | ||||
|     const [showDelay, setShowDelay] = useState(false); | ||||
| 
 | ||||
|     const fullScreen = useMediaQuery(theme.breakpoints.down('sm')); | ||||
|     const sendButtonEnabled = (() => { | ||||
|         return true; | ||||
|  | @ -58,31 +61,26 @@ const SendDialog = (props) => { | |||
|         }) | ||||
|     }; | ||||
|     return ( | ||||
|         <Dialog open={props.open} onClose={props.onCancel} fullScreen={fullScreen}> | ||||
|         <Dialog maxWidth="md" 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 | ||||
|                 /> | ||||
|                 {showTopicUrl && | ||||
|                     <ClosableRow onClose={() => { | ||||
|                         setTopicUrl(props.topicUrl); | ||||
|                         setShowTopicUrl(false); | ||||
|                     }}> | ||||
|                         <TextField | ||||
|                             margin="dense" | ||||
|                             label="Topic URL" | ||||
|                             value={topicUrl} | ||||
|                             onChange={ev => setTopicUrl(ev.target.value)} | ||||
|                             type="text" | ||||
|                             variant="standard" | ||||
|                             fullWidth | ||||
|                             required | ||||
|                         /> | ||||
|                     </ClosableRow> | ||||
|                 } | ||||
|                 <TextField | ||||
|                     margin="dense" | ||||
|                     label="Title" | ||||
|  | @ -91,38 +89,139 @@ const SendDialog = (props) => { | |||
|                     type="text" | ||||
|                     fullWidth | ||||
|                     variant="standard" | ||||
|                     placeholder="Notification title, e.g. Disk space alert" | ||||
|                 /> | ||||
|                 <TextField | ||||
|                     margin="dense" | ||||
|                     label="Tags" | ||||
|                     value={tags} | ||||
|                     onChange={ev => setTags(ev.target.value)} | ||||
|                     label="Message" | ||||
|                     placeholder="Type the main message body here." | ||||
|                     value={message} | ||||
|                     onChange={ev => setMessage(ev.target.value)} | ||||
|                     type="text" | ||||
|                     fullWidth | ||||
|                     variant="standard" | ||||
|                     rows={5} | ||||
|                     fullWidth | ||||
|                     autoFocus | ||||
|                     multiline | ||||
|                 /> | ||||
|                 <TextField | ||||
|                 <div style={{display: 'flex'}}> | ||||
|                     <DialogIconButton onClick={() => null}><InsertEmoticonIcon/></DialogIconButton> | ||||
|                     <TextField | ||||
|                         margin="dense" | ||||
|                         label="Tags" | ||||
|                         placeholder="Comma-separated list of tags, e.g. warning, srv1-backup" | ||||
|                         value={tags} | ||||
|                         onChange={ev => setTags(ev.target.value)} | ||||
|                         type="text" | ||||
|                         variant="standard" | ||||
|                         sx={{flexGrow: 1, marginRight: 1}} | ||||
|                     /> | ||||
|                     <FormControl | ||||
|                         variant="standard" | ||||
|                         margin="dense" | ||||
|                         sx={{minWidth: 120, maxWidth: 200, flexGrow: 1}} | ||||
|                     > | ||||
|                         <InputLabel/> | ||||
|                         <Select | ||||
|                             label="Priority" | ||||
|                             margin="dense" | ||||
|                             value={priority} | ||||
|                             onChange={(ev) => setPriority(ev.target.value)} | ||||
|                         > | ||||
|                             {[1,2,3,4,5].map(priority => | ||||
|                                 <MenuItem value={priority}> | ||||
|                                     <div style={{ display: 'flex', alignItems: 'center' }}> | ||||
|                                         <img src={priorities[priority].file} style={{marginRight: "8px"}}/> | ||||
|                                         <div>{priorities[priority].label}</div> | ||||
|                                     </div> | ||||
|                                 </MenuItem> | ||||
|                             )} | ||||
|                         </Select> | ||||
|                     </FormControl> | ||||
|                 </div> | ||||
|                 {showClickUrl && | ||||
|                     <ClosableRow onClose={() => { | ||||
|                         setClickUrl(""); | ||||
|                         setShowClickUrl(false); | ||||
|                     }}> | ||||
|                         <TextField | ||||
|                             margin="dense" | ||||
|                             label="Click URL" | ||||
|                             placeholder="URL that is opened when notification is clicked" | ||||
|                             value={clickUrl} | ||||
|                             onChange={ev => setClickUrl(ev.target.value)} | ||||
|                             type="url" | ||||
|                             fullWidth | ||||
|                             variant="standard" | ||||
|                         /> | ||||
|                     </ClosableRow> | ||||
|                 } | ||||
|                 {showEmail && | ||||
|                     <ClosableRow onClose={() => { | ||||
|                         setEmail(""); | ||||
|                         setShowEmail(false); | ||||
|                     }}> | ||||
|                         <TextField | ||||
|                         margin="dense" | ||||
|                         label="Email" | ||||
|                         placeholder="Address to forward the message to, e.g. phil@example.com" | ||||
|                         value={email} | ||||
|                         onChange={ev => setEmail(ev.target.value)} | ||||
|                         type="email" | ||||
|                         variant="standard" | ||||
|                         fullWidth | ||||
|                     /> | ||||
|                     </ClosableRow> | ||||
|                 } | ||||
|                 {showAttachUrl && <TextField | ||||
|                     margin="dense" | ||||
|                     label="Click URL" | ||||
|                     value={click} | ||||
|                     onChange={ev => setClick(ev.target.value)} | ||||
|                     label="Attachment URL" | ||||
|                     value={attachUrl} | ||||
|                     onChange={ev => setAttachUrl(ev.target.value)} | ||||
|                     type="url" | ||||
|                     fullWidth | ||||
|                     variant="standard" | ||||
|                 /> | ||||
|                 <TextField | ||||
|                     fullWidth | ||||
|                 />} | ||||
|                 {(showAttachFile || showAttachUrl) && <TextField | ||||
|                     margin="dense" | ||||
|                     label="Email" | ||||
|                     value={email} | ||||
|                     onChange={ev => setEmail(ev.target.value)} | ||||
|                     type="email" | ||||
|                     fullWidth | ||||
|                     label="Attachment Filename" | ||||
|                     value={filename} | ||||
|                     onChange={ev => setFilename(ev.target.value)} | ||||
|                     type="text" | ||||
|                     variant="standard" | ||||
|                 /> | ||||
|                 <PrioritySelect/> | ||||
|                 <Typography variant="body1"> | ||||
|                     For details on what these fields mean, please check out the | ||||
|                     {" "}<Link href="/docs">documentation</Link>. | ||||
|                     fullWidth | ||||
|                 />} | ||||
|                 {showDelay && | ||||
|                     <ClosableRow onClose={() => { | ||||
|                         setDelay(""); | ||||
|                         setShowDelay(false); | ||||
|                     }}> | ||||
|                         <TextField | ||||
|                             margin="dense" | ||||
|                             label="Delay" | ||||
|                             placeholder="Unix timestamp, duration or English natural language" | ||||
|                             value={delay} | ||||
|                             onChange={ev => setDelay(ev.target.value)} | ||||
|                             type="text" | ||||
|                             variant="standard" | ||||
|                             fullWidth | ||||
|                         /> | ||||
|                     </ClosableRow> | ||||
|                 } | ||||
|                 <Typography variant="body1" sx={{marginTop: 2, marginBottom: 1}}> | ||||
|                     Other features: | ||||
|                 </Typography> | ||||
|                 <div> | ||||
|                     {!showClickUrl && <Chip clickable label="Click URL" onClick={() => setShowClickUrl(true)} sx={{marginRight: 1}}/>} | ||||
|                     {!showEmail && <Chip clickable label="Forward to email" onClick={() => setShowEmail(true)} sx={{marginRight: 1}}/>} | ||||
|                     {!showAttachUrl && <Chip clickable label="Attach file by URL" onClick={() => setShowAttachUrl(true)} sx={{marginRight: 1}}/>} | ||||
|                     {!showAttachFile && <Chip clickable label="Attach local file" onClick={() => setShowAttachFile(true)} sx={{marginRight: 1}}/>} | ||||
|                     {!showDelay && <Chip clickable label="Delay delivery" onClick={() => setShowDelay(true)} sx={{marginRight: 1}}/>} | ||||
|                     {!showTopicUrl && <Chip clickable label="Change topic" onClick={() => setShowTopicUrl(true)} sx={{marginRight: 1}}/>} | ||||
|                 </div> | ||||
|                 <Typography variant="body1" sx={{marginTop: 2, marginBottom: 1}}> | ||||
|                     For examples and a detailed description of all send features, please | ||||
|                     refer to the <Link href="/docs">documentation</Link>. | ||||
|                 </Typography> | ||||
|             </DialogContent> | ||||
|             <DialogActions> | ||||
|  | @ -133,4 +232,53 @@ const SendDialog = (props) => { | |||
|     ); | ||||
| }; | ||||
| 
 | ||||
| const Row = (props) => { | ||||
|     return ( | ||||
|         <div style={{display: 'flex'}}> | ||||
|             {props.children} | ||||
|         </div> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| const ClosableRow = (props) => { | ||||
|     return ( | ||||
|         <Row> | ||||
|             {props.children} | ||||
|             <DialogIconButton onClick={props.onClose}><Close/></DialogIconButton> | ||||
|         </Row> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| const PrioritySelect = () => { | ||||
|     return ( | ||||
|         <Tooltip title="Message priority"> | ||||
|             <IconButton color="inherit" size="large" sx={{height: "45px", marginTop: "15px"}} onClick={() => setSendDialogOpen(true)}> | ||||
|                 <img src={priority3}/> | ||||
|             </IconButton> | ||||
|         </Tooltip> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| const DialogIconButton = (props) => { | ||||
|     return ( | ||||
|         <IconButton | ||||
|             color="inherit" | ||||
|             size="large" | ||||
|             edge="start" | ||||
|             sx={{height: "45px", marginTop: "17px", marginLeft: "6px"}} | ||||
|             onClick={props.onClick} | ||||
|         > | ||||
|             {props.children} | ||||
|         </IconButton> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| const priorities = { | ||||
|     1: { label: "Minimum priority", file: priority1 }, | ||||
|     2: { label: "Low priority", file: priority2 }, | ||||
|     3: { label: "Default priority", file: priority3 }, | ||||
|     4: { label: "High priority", file: priority4 }, | ||||
|     5: { label: "Maximum priority", file: priority5 } | ||||
| }; | ||||
| 
 | ||||
| export default SendDialog; | ||||
|  |  | |||
|  | @ -1 +1 @@ | |||
| <svg height="24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M16.137 11.652a4.21 4.21 0 01-4.21 4.209 4.21 4.21 0 01-4.209-4.21 4.21 4.21 0 014.21-4.209 4.21 4.21 0 014.209 4.21z" fill="#999"/></svg> | ||||
| <svg height="24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M4.882 16.057c-.33-.114-.636-.42-.795-.797-.084-.202-.1-.292-.1-.578-.001-.302.011-.366.116-.6a1.44 1.44 0 01.652-.704l.205-.106h14.077l.205.106c.756.39 1.01 1.376.548 2.128a1.217 1.217 0 01-.588.515l-.201.091-6.985-.001c-5.641-.002-7.013-.012-7.134-.054zM4.858 10.595c-.33-.114-.635-.42-.794-.797-.085-.201-.1-.292-.101-.578 0-.302.012-.366.116-.6a1.44 1.44 0 01.653-.704l.205-.106h14.076l.205.106c.757.39 1.01 1.377.548 2.128a1.217 1.217 0 01-.587.515l-.202.092-6.984-.002c-5.642-.002-7.013-.012-7.135-.054z" fill="#0091ff"/></svg> | ||||
| Before (image error) Size: 210 B After (image error) Size: 605 B | 
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Philipp Heckel
						Philipp Heckel