Remove mui/styles, Settings page, make minPriority functional, ahh so ugly
This commit is contained in:
parent
f23c7a2dbf
commit
8036aa2942
9 changed files with 265 additions and 60 deletions
|
@ -12,7 +12,6 @@
|
||||||
"@emotion/styled": "latest",
|
"@emotion/styled": "latest",
|
||||||
"@mui/icons-material": "^5.4.2",
|
"@mui/icons-material": "^5.4.2",
|
||||||
"@mui/material": "latest",
|
"@mui/material": "latest",
|
||||||
"@mui/styles": "^5.4.2",
|
|
||||||
"react": "latest",
|
"react": "latest",
|
||||||
"react-dom": "latest",
|
"react-dom": "latest",
|
||||||
"react-scripts": "^3.0.1"
|
"react-scripts": "^3.0.1"
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
import {formatMessage, formatTitleWithFallback, topicShortUrl} from "./utils";
|
import {formatMessage, formatTitleWithFallback, topicShortUrl} from "./utils";
|
||||||
|
import repository from "./Repository";
|
||||||
|
|
||||||
class NotificationManager {
|
class NotificationManager {
|
||||||
notify(subscription, notification, onClickFallback) {
|
notify(subscription, notification, onClickFallback) {
|
||||||
|
if (!this.shouldNotify(subscription, notification)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const message = formatMessage(notification);
|
const message = formatMessage(notification);
|
||||||
const title = formatTitleWithFallback(notification, topicShortUrl(subscription.baseUrl, subscription.topic));
|
const title = formatTitleWithFallback(notification, topicShortUrl(subscription.baseUrl, subscription.topic));
|
||||||
const n = new Notification(title, {
|
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();
|
const notificationManager = new NotificationManager();
|
||||||
|
|
|
@ -87,6 +87,24 @@ class Repository {
|
||||||
console.log(`[Repository] Saving selected subscription ${selectedSubscriptionId} to localStorage`);
|
console.log(`[Repository] Saving selected subscription ${selectedSubscriptionId} to localStorage`);
|
||||||
localStorage.setItem('selectedSubscriptionId', selectedSubscriptionId);
|
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();
|
const repository = new Repository();
|
||||||
|
|
|
@ -10,7 +10,7 @@ class Subscription {
|
||||||
}
|
}
|
||||||
|
|
||||||
addNotification(notification) {
|
addNotification(notification) {
|
||||||
if (this.notifications.has(notification.id)) {
|
if (!notification.event || notification.event !== 'message' || this.notifications.has(notification.id)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this.notifications.set(notification.id, notification);
|
this.notifications.set(notification.id, notification);
|
||||||
|
|
|
@ -22,6 +22,9 @@ import Preferences from "./Preferences";
|
||||||
// - add baseUrl
|
// - add baseUrl
|
||||||
// TODO user management
|
// TODO user management
|
||||||
// TODO embed into ntfy server
|
// TODO embed into ntfy server
|
||||||
|
// TODO make default server functional
|
||||||
|
// TODO indexeddb for notifications + subscriptions
|
||||||
|
// TODO business logic with callbacks
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
console.log(`[App] Rendering main view`);
|
console.log(`[App] Rendering main view`);
|
||||||
|
|
|
@ -14,7 +14,6 @@ import SubscribeDialog from "./SubscribeDialog";
|
||||||
import {Alert, AlertTitle, ListSubheader} from "@mui/material";
|
import {Alert, AlertTitle, ListSubheader} from "@mui/material";
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import Preferences from "./Preferences";
|
|
||||||
|
|
||||||
const navWidth = 240;
|
const navWidth = 240;
|
||||||
|
|
||||||
|
@ -72,24 +71,7 @@ const NavList = (props) => {
|
||||||
<List component="nav" sx={{
|
<List component="nav" sx={{
|
||||||
paddingTop: (showGrantPermissionsBox) ? '0' : ''
|
paddingTop: (showGrantPermissionsBox) ? '0' : ''
|
||||||
}}>
|
}}>
|
||||||
{showGrantPermissionsBox &&
|
{showGrantPermissionsBox && <PermissionAlert onRequestPermissionClick={props.onRequestPermissionClick}/>}
|
||||||
<>
|
|
||||||
<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/>
|
|
||||||
</>}
|
|
||||||
{showSubscriptionsList &&
|
{showSubscriptionsList &&
|
||||||
<>
|
<>
|
||||||
<ListSubheader component="div" id="nested-list-subheader">
|
<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;
|
export default Navigation;
|
||||||
|
|
|
@ -1,22 +1,191 @@
|
||||||
import * as React from 'react';
|
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 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) => {
|
const Preferences = (props) => {
|
||||||
return (
|
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">
|
<Typography variant="h5">
|
||||||
Manage users
|
Notifications
|
||||||
</Typography>
|
</Typography>
|
||||||
<Card sx={{ minWidth: 275 }}>
|
<PrefGroup>
|
||||||
<CardContent>
|
<MinPriority/>
|
||||||
You may manage users for your protected topics here. Please note that since this is a client
|
<DeleteAfter/>
|
||||||
application only, username and password are stored in the browser's local storage.
|
</PrefGroup>
|
||||||
</CardContent>
|
</Paper>
|
||||||
</Card>
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
export default Preferences;
|
||||||
|
|
|
@ -12,8 +12,8 @@ import {Autocomplete, Checkbox, FormControlLabel, useMediaQuery} from "@mui/mate
|
||||||
import theme from "./theme";
|
import theme from "./theme";
|
||||||
import api from "../app/Api";
|
import api from "../app/Api";
|
||||||
import {topicUrl, validTopic, validUrl} from "../app/utils";
|
import {topicUrl, validTopic, validUrl} from "../app/utils";
|
||||||
import useStyles from "./styles";
|
|
||||||
import User from "../app/User";
|
import User from "../app/User";
|
||||||
|
import Box from "@mui/material/Box";
|
||||||
|
|
||||||
const defaultBaseUrl = "http://127.0.0.1"
|
const defaultBaseUrl = "http://127.0.0.1"
|
||||||
//const defaultBaseUrl = "https://ntfy.sh"
|
//const defaultBaseUrl = "https://ntfy.sh"
|
||||||
|
@ -123,7 +123,6 @@ const SubscribePage = (props) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const LoginPage = (props) => {
|
const LoginPage = (props) => {
|
||||||
const styles = useStyles();
|
|
||||||
const [username, setUsername] = useState("");
|
const [username, setUsername] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
const [errorText, setErrorText] = useState("");
|
const [errorText, setErrorText] = useState("");
|
||||||
|
@ -170,17 +169,35 @@ const LoginPage = (props) => {
|
||||||
variant="standard"
|
variant="standard"
|
||||||
/>
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<div className={styles.bottomBar}>
|
<DialogFooter status={errorText}>
|
||||||
<DialogContentText className={styles.statusText}>
|
<Button onClick={props.onBack}>Back</Button>
|
||||||
{errorText}
|
<Button onClick={handleLogin}>Login</Button>
|
||||||
</DialogContentText>
|
</DialogFooter>
|
||||||
<DialogActions>
|
|
||||||
<Button onClick={props.onBack}>Back</Button>
|
|
||||||
<Button onClick={handleLogin}>Login</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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;
|
export default SubscribeDialog;
|
||||||
|
|
|
@ -1,23 +1,8 @@
|
||||||
import {makeStyles, styled} from "@mui/styles";
|
import {styled} from "@mui/styles";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import theme from "./theme";
|
import theme from "./theme";
|
||||||
import Container from "@mui/material/Container";
|
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)({
|
export const Paragraph = styled(Typography)({
|
||||||
paddingTop: 8,
|
paddingTop: 8,
|
||||||
paddingBottom: 8,
|
paddingBottom: 8,
|
||||||
|
@ -31,5 +16,3 @@ export const VerticallyCenteredContainer = styled(Container)({
|
||||||
alignContent: 'center',
|
alignContent: 'center',
|
||||||
color: theme.palette.body.main
|
color: theme.palette.body.main
|
||||||
});
|
});
|
||||||
|
|
||||||
export default useStyles;
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue