2022-03-27 13:10:47 +00:00
import * as React from 'react' ;
2022-03-30 18:11:18 +00:00
import { useEffect , useRef , useState } from 'react' ;
2022-03-27 13:10:47 +00:00
import { NotificationItem } from "./Notifications" ;
import theme from "./theme" ;
2022-03-29 19:22:26 +00:00
import { Chip , FormControl , InputLabel , Link , Select , useMediaQuery } from "@mui/material" ;
2022-03-27 13:10:47 +00:00
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 Button from "@mui/material/Button" ;
import Typography from "@mui/material/Typography" ;
2022-03-29 02:54:27 +00:00
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" ;
2022-04-01 12:41:45 +00:00
import { basicAuth , formatBytes , shortUrl , splitNoEmpty , splitTopicUrl , validTopicUrl } from "../app/utils" ;
2022-03-29 19:22:26 +00:00
import Box from "@mui/material/Box" ;
import Icon from "./Icon" ;
import DialogFooter from "./DialogFooter" ;
import api from "../app/Api" ;
2022-03-30 13:57:22 +00:00
import Divider from "@mui/material/Divider" ;
2022-03-30 18:11:18 +00:00
import EditIcon from '@mui/icons-material/Edit' ;
import CheckIcon from '@mui/icons-material/Check' ;
2022-04-01 12:41:45 +00:00
import userManager from "../app/UserManager" ;
2022-03-27 13:10:47 +00:00
const SendDialog = ( props ) => {
const [ topicUrl , setTopicUrl ] = useState ( props . topicUrl ) ;
const [ message , setMessage ] = useState ( props . message || "" ) ;
const [ title , setTitle ] = useState ( "" ) ;
const [ tags , setTags ] = useState ( "" ) ;
2022-03-29 02:54:27 +00:00
const [ priority , setPriority ] = useState ( 3 ) ;
const [ clickUrl , setClickUrl ] = useState ( "" ) ;
const [ attachUrl , setAttachUrl ] = useState ( "" ) ;
2022-03-29 19:22:26 +00:00
const [ attachFile , setAttachFile ] = useState ( null ) ;
2022-03-29 02:54:27 +00:00
const [ filename , setFilename ] = useState ( "" ) ;
2022-03-31 16:03:36 +00:00
const [ filenameEdited , setFilenameEdited ] = useState ( false ) ;
2022-03-27 13:10:47 +00:00
const [ email , setEmail ] = useState ( "" ) ;
2022-03-29 02:54:27 +00:00
const [ delay , setDelay ] = useState ( "" ) ;
const [ showTopicUrl , setShowTopicUrl ] = useState ( props . topicUrl === "" ) ;
const [ showClickUrl , setShowClickUrl ] = useState ( false ) ;
const [ showAttachUrl , setShowAttachUrl ] = useState ( false ) ;
const [ showEmail , setShowEmail ] = useState ( false ) ;
const [ showDelay , setShowDelay ] = useState ( false ) ;
2022-03-30 13:57:22 +00:00
const showAttachFile = ! ! attachFile && ! showAttachUrl ;
2022-03-29 19:22:26 +00:00
const attachFileInput = useRef ( ) ;
2022-03-30 13:57:22 +00:00
2022-04-01 12:41:45 +00:00
const [ sendRequest , setSendRequest ] = useState ( null ) ;
const [ statusText , setStatusText ] = useState ( "" ) ;
const disabled = ! ! sendRequest ;
2022-03-29 19:22:26 +00:00
2022-03-27 13:10:47 +00:00
const fullScreen = useMediaQuery ( theme . breakpoints . down ( 'sm' ) ) ;
const sendButtonEnabled = ( ( ) => {
2022-03-29 19:22:26 +00:00
if ( ! validTopicUrl ( topicUrl ) ) {
return false ;
}
2022-03-27 13:10:47 +00:00
return true ;
} ) ( ) ;
const handleSubmit = async ( ) => {
2022-03-29 19:22:26 +00:00
const { baseUrl , topic } = splitTopicUrl ( topicUrl ) ;
2022-04-01 12:41:45 +00:00
const headers = { } ;
2022-03-29 19:22:26 +00:00
if ( title . trim ( ) ) {
2022-04-01 12:41:45 +00:00
headers [ "X-Title" ] = title . trim ( ) ;
2022-03-29 19:22:26 +00:00
}
if ( tags . trim ( ) ) {
2022-04-01 12:41:45 +00:00
headers [ "X-Tags" ] = tags . trim ( ) ;
2022-03-29 19:22:26 +00:00
}
if ( priority && priority !== 3 ) {
2022-04-01 12:41:45 +00:00
headers [ "X-Priority" ] = priority . toString ( ) ;
2022-03-29 19:22:26 +00:00
}
if ( clickUrl . trim ( ) ) {
2022-04-01 12:41:45 +00:00
headers [ "X-Click" ] = clickUrl . trim ( ) ;
2022-03-29 19:22:26 +00:00
}
if ( attachUrl . trim ( ) ) {
2022-04-01 12:41:45 +00:00
headers [ "X-Attach" ] = attachUrl . trim ( ) ;
2022-03-29 19:22:26 +00:00
}
if ( filename . trim ( ) ) {
2022-04-01 12:41:45 +00:00
headers [ "X-Filename" ] = filename . trim ( ) ;
2022-03-29 19:22:26 +00:00
}
if ( email . trim ( ) ) {
2022-04-01 12:41:45 +00:00
headers [ "X-Email" ] = email . trim ( ) ;
2022-03-29 19:22:26 +00:00
}
if ( delay . trim ( ) ) {
2022-04-01 12:41:45 +00:00
headers [ "X-Delay" ] = delay . trim ( ) ;
2022-03-29 19:22:26 +00:00
}
2022-04-01 12:41:45 +00:00
if ( attachFile && message . trim ( ) ) {
headers [ "X-Message" ] = message . replaceAll ( "\n" , "\\n" ) . trim ( ) ;
}
const body = ( attachFile ) ? attachFile : message ;
2022-03-29 19:22:26 +00:00
try {
2022-04-01 12:41:45 +00:00
const user = await userManager . get ( baseUrl ) ;
if ( user ) {
headers [ "Authorization" ] = basicAuth ( user . username , user . password ) ;
}
const progressFn = ( ev ) => {
console . log ( ev ) ;
if ( ev . loaded > 0 && ev . total > 0 ) {
const percent = Math . round ( ev . loaded * 100.0 / ev . total ) ;
setStatusText ( ` Uploading ${ formatBytes ( ev . loaded ) } / ${ formatBytes ( ev . total ) } ( ${ percent } %) ... ` ) ;
} else {
setStatusText ( ` Uploading ... ` ) ;
}
} ;
const request = api . publishXHR ( baseUrl , topic , body , headers , progressFn ) ;
setSendRequest ( request ) ;
await request ;
setStatusText ( "Message published" ) ;
//props.onClose();
2022-03-29 19:22:26 +00:00
} catch ( e ) {
2022-04-01 12:41:45 +00:00
console . log ( "error" , e ) ;
setStatusText ( "An error occurred" ) ;
2022-03-29 19:22:26 +00:00
}
2022-04-01 12:41:45 +00:00
setSendRequest ( null ) ;
2022-03-29 19:22:26 +00:00
} ;
const handleAttachFileClick = ( ) => {
attachFileInput . current . click ( ) ;
} ;
const handleAttachFileChanged = ( ev ) => {
2022-03-30 13:57:22 +00:00
const file = ev . target . files [ 0 ] ;
setAttachFile ( file ) ;
setFilename ( file . name ) ;
2022-03-29 19:22:26 +00:00
console . log ( ev . target . files [ 0 ] ) ;
console . log ( URL . createObjectURL ( ev . target . files [ 0 ] ) ) ;
2022-03-27 13:10:47 +00:00
} ;
return (
2022-03-29 02:54:27 +00:00
< Dialog maxWidth = "md" open = { props . open } onClose = { props . onCancel } fullScreen = { fullScreen } >
2022-03-29 19:22:26 +00:00
< DialogTitle > Publish to { shortUrl ( topicUrl ) } < / D i a l o g T i t l e >
2022-03-27 13:10:47 +00:00
< DialogContent >
2022-03-29 02:54:27 +00:00
{ showTopicUrl &&
2022-04-01 12:41:45 +00:00
< ClosableRow disabled = { disabled } onClose = { ( ) => {
2022-03-29 02:54:27 +00:00
setTopicUrl ( props . topicUrl ) ;
setShowTopicUrl ( false ) ;
} } >
< TextField
margin = "dense"
label = "Topic URL"
value = { topicUrl }
onChange = { ev => setTopicUrl ( ev . target . value ) }
2022-04-01 12:41:45 +00:00
disabled = { disabled }
2022-03-29 02:54:27 +00:00
type = "text"
variant = "standard"
fullWidth
required
/ >
< / C l o s a b l e R o w >
}
2022-03-27 13:10:47 +00:00
< TextField
margin = "dense"
2022-03-29 02:54:27 +00:00
label = "Title"
value = { title }
onChange = { ev => setTitle ( ev . target . value ) }
2022-04-01 12:41:45 +00:00
disabled = { disabled }
2022-03-27 13:10:47 +00:00
type = "text"
fullWidth
2022-03-29 02:54:27 +00:00
variant = "standard"
placeholder = "Notification title, e.g. Disk space alert"
2022-03-27 13:10:47 +00:00
/ >
< TextField
margin = "dense"
label = "Message"
2022-03-29 02:54:27 +00:00
placeholder = "Type the main message body here."
2022-03-27 13:10:47 +00:00
value = { message }
onChange = { ev => setMessage ( ev . target . value ) }
2022-04-01 12:41:45 +00:00
disabled = { disabled }
2022-03-27 13:10:47 +00:00
type = "text"
variant = "standard"
2022-03-29 02:54:27 +00:00
rows = { 5 }
2022-03-27 13:10:47 +00:00
fullWidth
autoFocus
multiline
/ >
2022-03-29 02:54:27 +00:00
< div style = { { display : 'flex' } } >
2022-04-01 12:41:45 +00:00
< DialogIconButton disabled = { disabled } onClick = { ( ) => null } > < InsertEmoticonIcon / > < / D i a l o g I c o n B u t t o n >
2022-03-29 02:54:27 +00:00
< TextField
margin = "dense"
label = "Tags"
placeholder = "Comma-separated list of tags, e.g. warning, srv1-backup"
value = { tags }
onChange = { ev => setTags ( ev . target . value ) }
2022-04-01 12:41:45 +00:00
disabled = { disabled }
2022-03-29 02:54:27 +00:00
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 ) }
2022-04-01 12:41:45 +00:00
disabled = { disabled }
2022-03-29 02:54:27 +00:00
>
2022-03-30 13:57:22 +00:00
{ [ 5 , 4 , 3 , 2 , 1 ] . map ( priority =>
2022-03-29 02:54:27 +00:00
< MenuItem value = { priority } >
< div style = { { display : 'flex' , alignItems : 'center' } } >
< img src = { priorities [ priority ] . file } style = { { marginRight : "8px" } } / >
< div > { priorities [ priority ] . label } < / d i v >
< / d i v >
< / M e n u I t e m >
) }
< / S e l e c t >
< / F o r m C o n t r o l >
< / d i v >
{ showClickUrl &&
2022-04-01 12:41:45 +00:00
< ClosableRow disabled = { disabled } onClose = { ( ) => {
2022-03-29 02:54:27 +00:00
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 ) }
2022-04-01 12:41:45 +00:00
disabled = { disabled }
2022-03-29 02:54:27 +00:00
type = "url"
fullWidth
variant = "standard"
/ >
< / C l o s a b l e R o w >
}
{ showEmail &&
2022-04-01 12:41:45 +00:00
< ClosableRow disabled = { disabled } onClose = { ( ) => {
2022-03-29 02:54:27 +00:00
setEmail ( "" ) ;
setShowEmail ( false ) ;
} } >
< TextField
2022-03-31 16:03:36 +00:00
margin = "dense"
label = "Email"
placeholder = "Address to forward the message to, e.g. phil@example.com"
value = { email }
onChange = { ev => setEmail ( ev . target . value ) }
2022-04-01 12:41:45 +00:00
disabled = { disabled }
2022-03-31 16:03:36 +00:00
type = "email"
variant = "standard"
fullWidth
/ >
2022-03-29 02:54:27 +00:00
< / C l o s a b l e R o w >
}
2022-03-29 19:22:26 +00:00
{ showAttachUrl &&
2022-04-01 12:41:45 +00:00
< ClosableRow disabled = { disabled } onClose = { ( ) => {
2022-03-29 19:22:26 +00:00
setAttachUrl ( "" ) ;
2022-03-31 16:03:36 +00:00
setFilename ( "" ) ;
setFilenameEdited ( false ) ;
2022-03-29 19:22:26 +00:00
setShowAttachUrl ( false ) ;
} } >
< TextField
margin = "dense"
label = "Attachment URL"
2022-03-31 16:03:36 +00:00
placeholder = "Attach file by URL, e.g. https://f-droid.org/F-Droid.apk"
2022-03-29 19:22:26 +00:00
value = { attachUrl }
2022-03-31 16:03:36 +00:00
onChange = { ev => {
const url = ev . target . value ;
setAttachUrl ( url ) ;
if ( ! filenameEdited ) {
try {
const u = new URL ( url ) ;
const parts = u . pathname . split ( "/" ) ;
if ( parts . length > 0 ) {
setFilename ( parts [ parts . length - 1 ] ) ;
}
} catch ( e ) {
// Do nothing
}
}
} }
2022-04-01 12:41:45 +00:00
disabled = { disabled }
2022-03-29 19:22:26 +00:00
type = "url"
variant = "standard"
2022-03-31 16:03:36 +00:00
sx = { { flexGrow : 5 , marginRight : 1 } }
/ >
< TextField
margin = "dense"
label = "Filename"
placeholder = "Attachment filename"
value = { filename }
onChange = { ev => {
setFilename ( ev . target . value ) ;
setFilenameEdited ( true ) ;
} }
2022-04-01 12:41:45 +00:00
disabled = { disabled }
2022-03-31 16:03:36 +00:00
type = "text"
variant = "standard"
sx = { { flexGrow : 1 } }
2022-03-29 19:22:26 +00:00
/ >
< / C l o s a b l e R o w >
}
< input
type = "file"
ref = { attachFileInput }
onChange = { handleAttachFileChanged }
style = { { display : 'none' } }
/ >
2022-03-30 13:57:22 +00:00
{ showAttachFile && < AttachmentBox
file = { attachFile }
filename = { filename }
2022-04-01 12:41:45 +00:00
disabled = { disabled }
2022-03-30 13:57:22 +00:00
onChangeFilename = { ( f ) => setFilename ( f ) }
2022-03-31 16:03:36 +00:00
onClose = { ( ) => {
setAttachFile ( null ) ;
setFilename ( "" ) ;
} }
2022-03-29 02:54:27 +00:00
/ > }
{ showDelay &&
2022-04-01 12:41:45 +00:00
< ClosableRow disabled = { disabled } onClose = { ( ) => {
2022-03-29 02:54:27 +00:00
setDelay ( "" ) ;
setShowDelay ( false ) ;
} } >
< TextField
margin = "dense"
label = "Delay"
placeholder = "Unix timestamp, duration or English natural language"
value = { delay }
onChange = { ev => setDelay ( ev . target . value ) }
2022-04-01 12:41:45 +00:00
disabled = { disabled }
2022-03-29 02:54:27 +00:00
type = "text"
variant = "standard"
fullWidth
/ >
< / C l o s a b l e R o w >
}
< Typography variant = "body1" sx = { { marginTop : 2 , marginBottom : 1 } } >
Other features :
< / T y p o g r a p h y >
< div >
2022-04-01 12:41:45 +00:00
{ ! showClickUrl && < Chip clickable disabled = { disabled } label = "Click URL" onClick = { ( ) => setShowClickUrl ( true ) } sx = { { marginRight : 1 , marginBottom : 1 } } / > }
{ ! showEmail && < Chip clickable disabled = { disabled } label = "Forward to email" onClick = { ( ) => setShowEmail ( true ) } sx = { { marginRight : 1 , marginBottom : 1 } } / > }
{ ! showAttachUrl && ! showAttachFile && < Chip clickable disabled = { disabled } label = "Attach file by URL" onClick = { ( ) => setShowAttachUrl ( true ) } sx = { { marginRight : 1 , marginBottom : 1 } } / > }
{ ! showAttachFile && ! showAttachUrl && < Chip clickable disabled = { disabled } label = "Attach local file" onClick = { ( ) => handleAttachFileClick ( ) } sx = { { marginRight : 1 , marginBottom : 1 } } / > }
{ ! showDelay && < Chip clickable disabled = { disabled } label = "Delay delivery" onClick = { ( ) => setShowDelay ( true ) } sx = { { marginRight : 1 , marginBottom : 1 } } / > }
{ ! showTopicUrl && < Chip clickable disabled = { disabled } label = "Change topic" onClick = { ( ) => setShowTopicUrl ( true ) } sx = { { marginRight : 1 , marginBottom : 1 } } / > }
2022-03-29 02:54:27 +00:00
< / d i v >
2022-04-01 12:41:45 +00:00
< Typography variant = "body1" sx = { { marginTop : 1 , marginBottom : 1 } } >
2022-03-29 02:54:27 +00:00
For examples and a detailed description of all send features , please
refer to the < Link href = "/docs" > documentation < / L i n k > .
2022-03-27 13:10:47 +00:00
< / T y p o g r a p h y >
< / D i a l o g C o n t e n t >
2022-04-01 12:41:45 +00:00
< DialogFooter status = { statusText } >
{ sendRequest && < Button onClick = { ( ) => sendRequest . abort ( ) } > Cancel sending < / B u t t o n > }
{ ! sendRequest &&
< >
< Button onClick = { props . onClose } > Cancel < / B u t t o n >
< Button onClick = { handleSubmit } disabled = { ! sendButtonEnabled } > Send < / B u t t o n >
< / >
}
2022-03-29 19:22:26 +00:00
< / D i a l o g F o o t e r >
2022-03-27 13:10:47 +00:00
< / D i a l o g >
) ;
} ;
2022-03-29 02:54:27 +00:00
const Row = ( props ) => {
return (
< div style = { { display : 'flex' } } >
{ props . children }
< / d i v >
) ;
} ;
const ClosableRow = ( props ) => {
return (
< Row >
{ props . children }
2022-04-01 12:41:45 +00:00
< DialogIconButton disabled = { props . disabled } onClick = { props . onClose } sx = { { marginLeft : "6px" } } > < Close / > < / D i a l o g I c o n B u t t o n >
2022-03-29 02:54:27 +00:00
< / R o w >
) ;
} ;
const DialogIconButton = ( props ) => {
2022-03-29 19:22:26 +00:00
const sx = props . sx || { } ;
2022-03-29 02:54:27 +00:00
return (
< IconButton
color = "inherit"
size = "large"
edge = "start"
2022-03-29 19:22:26 +00:00
sx = { { height : "45px" , marginTop : "17px" , ... sx } }
2022-03-29 02:54:27 +00:00
onClick = { props . onClick }
2022-04-01 12:41:45 +00:00
disabled = { props . disabled }
2022-03-29 02:54:27 +00:00
>
{ props . children }
< / I c o n B u t t o n >
) ;
} ;
2022-03-29 19:22:26 +00:00
const AttachmentBox = ( props ) => {
const file = props . file ;
return (
2022-03-30 13:57:22 +00:00
< >
< Typography variant = "body1" sx = { { marginTop : 2 } } >
Attached file :
2022-03-29 19:22:26 +00:00
< / T y p o g r a p h y >
2022-03-30 13:57:22 +00:00
< Box sx = { {
display : 'flex' ,
alignItems : 'center' ,
2022-03-30 18:11:18 +00:00
padding : 0.5 ,
2022-03-30 13:57:22 +00:00
borderRadius : '4px' ,
} } >
< Icon type = { file . type } / >
< Typography variant = "body2" sx = { { marginLeft : 1 , textAlign : 'left' , color : 'text.primary' } } >
2022-03-31 16:03:36 +00:00
< ExpandingTextField
minWidth = { 140 }
variant = "body2"
2022-03-30 18:11:18 +00:00
value = { props . filename }
2022-03-31 16:03:36 +00:00
onChange = { ( ev ) => props . onChangeFilename ( ev . target . value ) }
2022-04-01 12:41:45 +00:00
disabled = { props . disabled }
2022-03-30 18:11:18 +00:00
/ >
2022-03-30 13:57:22 +00:00
< br / >
2022-03-30 18:11:18 +00:00
{ formatBytes ( file . size ) }
2022-03-30 13:57:22 +00:00
< / T y p o g r a p h y >
2022-04-01 12:41:45 +00:00
< DialogIconButton disabled = { props . disabled } onClick = { props . onClose } sx = { { marginLeft : "6px" } } > < Close / > < / D i a l o g I c o n B u t t o n >
2022-03-30 13:57:22 +00:00
< / B o x >
< / >
2022-03-29 19:22:26 +00:00
) ;
} ;
2022-03-31 16:03:36 +00:00
const ExpandingTextField = ( props ) => {
const invisibleFieldRef = useRef ( ) ;
const [ textWidth , setTextWidth ] = useState ( props . minWidth ) ;
const determineTextWidth = ( ) => {
const boundingRect = invisibleFieldRef ? . current ? . getBoundingClientRect ( ) ;
if ( ! boundingRect ) {
return props . minWidth ;
}
return ( boundingRect . width >= props . minWidth ) ? Math . round ( boundingRect . width ) : props . minWidth ;
} ;
useEffect ( ( ) => {
setTextWidth ( determineTextWidth ( ) + 5 ) ;
} , [ props . value ] ) ;
return (
< >
< Typography
ref = { invisibleFieldRef }
component = "span"
variant = { props . variant }
sx = { { position : "absolute" , left : "-100%" } }
>
{ props . value }
< / T y p o g r a p h y >
< TextField
margin = "dense"
placeholder = "Attachment filename"
value = { props . value }
onChange = { props . onChange }
type = "text"
variant = "standard"
sx = { { width : ` ${ textWidth } px ` , borderBottom : "none" } }
InputProps = { { style : { fontSize : theme . typography [ props . variant ] . fontSize } } }
inputProps = { { style : { paddingBottom : 0 , paddingTop : 0 } } }
2022-04-01 12:41:45 +00:00
disabled = { props . disabled }
2022-03-31 16:03:36 +00:00
/ >
< / >
)
} ;
2022-03-29 02:54:27 +00:00
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 }
} ;
2022-03-27 13:10:47 +00:00
export default SendDialog ;