Ver Fonte

Remove mui/styles, Settings page, make minPriority functional, ahh so ugly

Philipp Heckel há 4 anos atrás
pai
commit
8036aa2942

+ 0 - 1
web/package.json

@@ -12,7 +12,6 @@
     "@emotion/styled": "latest",
     "@mui/icons-material": "^5.4.2",
     "@mui/material": "latest",
-    "@mui/styles": "^5.4.2",
     "react": "latest",
     "react-dom": "latest",
     "react-scripts": "^3.0.1"

+ 12 - 0
web/src/app/NotificationManager.js

@@ -1,7 +1,11 @@
 import {formatMessage, formatTitleWithFallback, topicShortUrl} from "./utils";
+import repository from "./Repository";
 
 class NotificationManager {
     notify(subscription, notification, onClickFallback) {
+        if (!this.shouldNotify(subscription, notification)) {
+            return;
+        }
         const message = formatMessage(notification);
         const title = formatTitleWithFallback(notification, topicShortUrl(subscription.baseUrl, subscription.topic));
         const n = new Notification(title, {
@@ -27,6 +31,14 @@ class NotificationManager {
             });
         }
     }
+
+    shouldNotify(subscription, notification) {
+        const priority = (notification.priority) ? notification.priority : 3;
+        if (priority < repository.getMinPriority()) {
+            return false;
+        }
+        return true;
+    }
 }
 
 const notificationManager = new NotificationManager();

+ 18 - 0
web/src/app/Repository.js

@@ -87,6 +87,24 @@ class Repository {
         console.log(`[Repository] Saving selected subscription ${selectedSubscriptionId} to localStorage`);
         localStorage.setItem('selectedSubscriptionId', selectedSubscriptionId);
     }
+
+    setMinPriority(minPriority) {
+        localStorage.setItem('minPriority', minPriority.toString());
+    }
+
+    getMinPriority() {
+        const minPriority = localStorage.getItem('minPriority');
+        return (minPriority) ? Number(minPriority) : 1;
+    }
+
+    setDeleteAfter(deleteAfter) {
+        localStorage.setItem('deleteAfter', deleteAfter.toString());
+    }
+
+    getDeleteAfter() {
+        const deleteAfter = localStorage.getItem('deleteAfter');
+        return (deleteAfter) ? Number(deleteAfter) : 604800; // Default is one week
+    }
 }
 
 const repository = new Repository();

+ 1 - 1
web/src/app/Subscription.js

@@ -10,7 +10,7 @@ class Subscription {
     }
 
     addNotification(notification) {
-        if (this.notifications.has(notification.id)) {
+        if (!notification.event || notification.event !== 'message' || this.notifications.has(notification.id)) {
             return false;
         }
         this.notifications.set(notification.id, notification);

+ 3 - 0
web/src/components/App.js

@@ -22,6 +22,9 @@ import Preferences from "./Preferences";
 //  - add baseUrl
 // TODO user management
 // TODO embed into ntfy server
+// TODO make default server functional
+// TODO indexeddb for notifications + subscriptions
+// TODO business logic with callbacks
 
 const App = () => {
     console.log(`[App] Rendering main view`);

+ 23 - 19
web/src/components/Navigation.js

@@ -14,7 +14,6 @@ import SubscribeDialog from "./SubscribeDialog";
 import {Alert, AlertTitle, ListSubheader} from "@mui/material";
 import Button from "@mui/material/Button";
 import Typography from "@mui/material/Typography";
-import Preferences from "./Preferences";
 
 const navWidth = 240;
 
@@ -72,24 +71,7 @@ const NavList = (props) => {
             <List component="nav" sx={{
                 paddingTop: (showGrantPermissionsBox) ? '0' : ''
             }}>
-                {showGrantPermissionsBox &&
-                    <>
-                        <Alert severity="warning" sx={{paddingTop: 2}}>
-                            <AlertTitle>Notifications are disabled</AlertTitle>
-                            <Typography gutterBottom>
-                                Grant your browser permission to display desktop notifications.
-                            </Typography>
-                            <Button
-                                sx={{float: 'right'}}
-                                color="inherit"
-                                size="small"
-                                onClick={props.onRequestPermissionClick}
-                            >
-                                Grant now
-                            </Button>
-                        </Alert>
-                        <Divider/>
-                    </>}
+                {showGrantPermissionsBox && <PermissionAlert onRequestPermissionClick={props.onRequestPermissionClick}/>}
                 {showSubscriptionsList &&
                     <>
                         <ListSubheader component="div" id="nested-list-subheader">
@@ -147,4 +129,26 @@ const SubscriptionList = (props) => {
     );
 }
 
+const PermissionAlert = (props) => {
+    return (
+        <>
+            <Alert severity="warning" sx={{paddingTop: 2}}>
+                <AlertTitle>Notifications are disabled</AlertTitle>
+                <Typography gutterBottom>
+                    Grant your browser permission to display desktop notifications.
+                </Typography>
+                <Button
+                    sx={{float: 'right'}}
+                    color="inherit"
+                    size="small"
+                    onClick={props.onRequestPermissionClick}
+                >
+                    Grant now
+                </Button>
+            </Alert>
+            <Divider/>
+        </>
+    );
+};
+
 export default Navigation;

+ 178 - 9
web/src/components/Preferences.js

@@ -1,22 +1,191 @@
 import * as React from 'react';
-import {CardContent} from "@mui/material";
+import {useState} from 'react';
+import {FormControl, Select, Stack, Table, TableBody, TableCell, TableHead, TableRow} from "@mui/material";
 import Typography from "@mui/material/Typography";
-import Card from "@mui/material/Card";
+import Paper from "@mui/material/Paper";
+import repository from "../app/Repository";
+import {Paragraph} from "./styles";
+import EditIcon from '@mui/icons-material/Edit';
+import CloseIcon from "@mui/icons-material/Close";
+import IconButton from "@mui/material/IconButton";
+import Container from "@mui/material/Container";
+import TextField from "@mui/material/TextField";
+import MenuItem from "@mui/material/MenuItem";
 
 const Preferences = (props) => {
+    return (
+        <Container maxWidth="lg" sx={{marginTop: 3, marginBottom: 3}}>
+            <Stack spacing={3}>
+                <Notifications/>
+                <DefaultServer/>
+                <Users/>
+            </Stack>
+        </Container>
+    );
+};
+
+const Notifications = (props) => {
+    return (
+        <Paper sx={{p: 3}}>
+            <Typography variant="h5">
+                Notifications
+            </Typography>
+            <PrefGroup>
+                <MinPriority/>
+                <DeleteAfter/>
+            </PrefGroup>
+        </Paper>
+    );
+};
+
+const MinPriority = () => {
+    const [minPriority, setMinPriority] = useState(repository.getMinPriority());
+    const handleChange = (ev) => {
+        setMinPriority(ev.target.value);
+        repository.setMinPriority(ev.target.value);
+    }
+    return (
+        <Pref title="Minimum priority">
+            <FormControl fullWidth variant="standard" sx={{ m: 1 }}>
+                <Select value={minPriority} onChange={handleChange}>
+                    <MenuItem value={1}><em>Any priority</em></MenuItem>
+                    <MenuItem value={2}>Low priority and higher</MenuItem>
+                    <MenuItem value={3}>Default priority and higher</MenuItem>
+                    <MenuItem value={4}>High priority and higher</MenuItem>
+                    <MenuItem value={5}>Only max priority</MenuItem>
+                </Select>
+            </FormControl>
+        </Pref>
+    )
+};
+
+const DeleteAfter = () => {
+    const [deleteAfter, setDeleteAfter] = useState(repository.getDeleteAfter());
+    const handleChange = (ev) => {
+        setDeleteAfter(ev.target.value);
+        repository.setDeleteAfter(ev.target.value);
+    }
+    return (
+        <Pref title="Minimum priority">
+            <FormControl fullWidth variant="standard" sx={{ m: 1 }}>
+                <Select value={deleteAfter} onChange={handleChange}>
+                    <MenuItem value={0}>Never</MenuItem>
+                    <MenuItem value={10800}>After three hour</MenuItem>
+                    <MenuItem value={86400}>After one day</MenuItem>
+                    <MenuItem value={604800}>After one week</MenuItem>
+                    <MenuItem value={2592000}>After one month</MenuItem>
+                </Select>
+            </FormControl>
+        </Pref>
+    )
+};
+
+
+const PrefGroup = (props) => {
+    return (
+        <div style={{
+            display: 'flex',
+            flexWrap: 'wrap'
+        }}>
+            {props.children}
+        </div>
+    )
+};
+
+const Pref = (props) => {
     return (
         <>
+            <div style={{
+                flex: '1 0 30%',
+                display: 'inline-flex',
+                flexDirection: 'column',
+                minHeight: '60px',
+                justifyContent: 'center'
+            }}>
+                <b>{props.title}</b>
+            </div>
+            <div style={{
+                flex: '1 0 calc(70% - 50px)',
+                display: 'inline-flex',
+                flexDirection: 'column',
+                minHeight: '60px',
+                justifyContent: 'center'
+            }}>
+                {props.children}
+            </div>
+        </>
+    );
+};
+
+const DefaultServer = (props) => {
+    return (
+        <Paper sx={{p: 3}}>
+            <Typography variant="h5">
+                Default server
+            </Typography>
+            <Paragraph>
+                This server is used as a default when adding new topics.
+            </Paragraph>
+            <TextField
+                margin="dense"
+                id="defaultBaseUrl"
+                placeholder="https://ntfy.sh"
+                type="text"
+                fullWidth
+                variant="standard"
+            />
+        </Paper>
+    );
+};
+
+const Users = (props) => {
+    return (
+        <Paper sx={{p: 3}}>
             <Typography variant="h5">
                 Manage users
             </Typography>
-            <Card sx={{ minWidth: 275 }}>
-                <CardContent>
-                    You may manage users for your protected topics here. Please note that since this is a client
-                    application only, username and password are stored in the browser's local storage.
-                </CardContent>
-            </Card>
-        </>
+            <Paragraph>
+                You may manage users for your protected topics here. Please note that since this is a client
+                application only, username and password are stored in the browser's local storage.
+            </Paragraph>
+            <UserTable/>
+        </Paper>
     );
 };
 
+const UserTable = () => {
+    const users = repository.loadUsers();
+    return (
+            <Table size="small">
+                <TableHead>
+                    <TableRow>
+                        <TableCell>User</TableCell>
+                        <TableCell>Service URL</TableCell>
+                        <TableCell/>
+                    </TableRow>
+                </TableHead>
+                <TableBody>
+                    {users.map((user, i) => (
+                        <TableRow
+                            key={i}
+                            sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
+                        >
+                            <TableCell component="th" scope="row">{user.username}</TableCell>
+                            <TableCell>{user.baseUrl}</TableCell>
+                            <TableCell align="right">
+                                <IconButton>
+                                    <EditIcon/>
+                                </IconButton>
+                                <IconButton>
+                                    <CloseIcon />
+                                </IconButton>
+                            </TableCell>
+                        </TableRow>
+                    ))}
+                </TableBody>
+            </Table>
+
+    );
+}
+
 export default Preferences;

+ 28 - 11
web/src/components/SubscribeDialog.js

@@ -12,8 +12,8 @@ import {Autocomplete, Checkbox, FormControlLabel, useMediaQuery} from "@mui/mate
 import theme from "./theme";
 import api from "../app/Api";
 import {topicUrl, validTopic, validUrl} from "../app/utils";
-import useStyles from "./styles";
 import User from "../app/User";
+import Box from "@mui/material/Box";
 
 const defaultBaseUrl = "http://127.0.0.1"
 //const defaultBaseUrl = "https://ntfy.sh"
@@ -123,7 +123,6 @@ const SubscribePage = (props) => {
 };
 
 const LoginPage = (props) => {
-    const styles = useStyles();
     const [username, setUsername] = useState("");
     const [password, setPassword] = useState("");
     const [errorText, setErrorText] = useState("");
@@ -170,17 +169,35 @@ const LoginPage = (props) => {
                     variant="standard"
                 />
             </DialogContent>
-            <div className={styles.bottomBar}>
-                <DialogContentText className={styles.statusText}>
-                    {errorText}
-                </DialogContentText>
-                <DialogActions>
-                    <Button onClick={props.onBack}>Back</Button>
-                    <Button onClick={handleLogin}>Login</Button>
-                </DialogActions>
-            </div>
+            <DialogFooter status={errorText}>
+                <Button onClick={props.onBack}>Back</Button>
+                <Button onClick={handleLogin}>Login</Button>
+            </DialogFooter>
         </>
     );
 };
 
+const DialogFooter = (props) => {
+    return (
+        <Box sx={{
+            display: 'flex',
+            flexDirection: 'row',
+            justifyContent: 'space-between',
+            paddingLeft: '24px',
+            paddingTop: '8px 24px',
+            paddingBottom: '8px 24px',
+        }}>
+            <DialogContentText sx={{
+                margin: '0px',
+                paddingTop: '8px',
+            }}>
+                {props.status}
+            </DialogContentText>
+            <DialogActions>
+                {props.children}
+            </DialogActions>
+        </Box>
+    );
+};
+
 export default SubscribeDialog;

+ 1 - 18
web/src/components/styles.js

@@ -1,23 +1,8 @@
-import {makeStyles, styled} from "@mui/styles";
+import {styled} from "@mui/styles";
 import Typography from "@mui/material/Typography";
 import theme from "./theme";
 import Container from "@mui/material/Container";
 
-const useStyles = makeStyles(theme => ({
-  bottomBar: {
-    display: 'flex',
-    flexDirection: 'row',
-    justifyContent: 'space-between',
-    paddingLeft: '24px',
-    paddingTop: '8px 24px',
-    paddingBottom: '8px 24px',
-  },
-  statusText: {
-    margin: '0px',
-    paddingTop: '8px',
-  }
-}));
-
 export const Paragraph = styled(Typography)({
   paddingTop: 8,
   paddingBottom: 8,
@@ -31,5 +16,3 @@ export const VerticallyCenteredContainer = styled(Container)({
   alignContent: 'center',
   color: theme.palette.body.main
 });
-
-export default useStyles;