Просмотр исходного кода

Continued work on publishing from the web app

Philipp Heckel 3 лет назад
Родитель
Сommit
187c19f3b2

+ 9 - 14
web/src/app/Api.js

@@ -26,23 +26,18 @@ class Api {
         return messages;
     }
 
-    async publish(baseUrl, topic, message, title, priority, tags) {
+    async publish(baseUrl, topic, message, options) {
         const user = await userManager.get(baseUrl);
-        const url = topicUrl(baseUrl, topic);
-        console.log(`[Api] Publishing message to ${url}`);
+        console.log(`[Api] Publishing message to ${topicUrl(baseUrl, topic)}`);
         const headers = {};
-        if (title) {
-            headers["X-Title"] = title;
-        }
-        if (priority !== 3) {
-            headers["X-Priority"] = `${priority}`;
-        }
-        if (tags.length > 0) {
-            headers["X-Tags"] = tags.join(",");
-        }
-        await fetch(url, {
+        const body = {
+            topic: topic,
+            message: message,
+            ...options
+        };
+        await fetch(baseUrl, {
             method: 'PUT',
-            body: message,
+            body: JSON.stringify(body),
             headers: maybeWithBasicAuth(headers, user)
         });
     }

+ 5 - 1
web/src/components/ActionBar.js

@@ -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);
     }
 

+ 30 - 35
web/src/components/App.js

@@ -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>
     );
 };

+ 1 - 2
web/src/components/Notifications.js

@@ -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 - 0
web/src/components/SendDialog.js

@@ -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;

+ 1 - 0
web/src/img/priority-3.svg

@@ -0,0 +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>