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

pull/149/head
Philipp Heckel 2022-03-01 16:22:47 -05:00
parent f23c7a2dbf
commit 8036aa2942
9 changed files with 265 additions and 60 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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">
Manage users
Notifications
</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>
<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>
<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;

View File

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

View File

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