Add dialog
This commit is contained in:
parent
c859f866b8
commit
8c0f3b2304
4 changed files with 86 additions and 88 deletions
55
web/src/AddDialog.js
Normal file
55
web/src/AddDialog.js
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import TextField from '@mui/material/TextField';
|
||||||
|
import Dialog from '@mui/material/Dialog';
|
||||||
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
|
import DialogContentText from '@mui/material/DialogContentText';
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
|
import {useState} from "react";
|
||||||
|
import Subscription from "./Subscription";
|
||||||
|
|
||||||
|
const defaultBaseUrl = "https://ntfy.sh"
|
||||||
|
|
||||||
|
const AddDialog = (props) => {
|
||||||
|
const [topic, setTopic] = useState("");
|
||||||
|
const handleCancel = () => {
|
||||||
|
setTopic('');
|
||||||
|
props.onCancel();
|
||||||
|
}
|
||||||
|
const handleSubmit = () => {
|
||||||
|
const subscription = new Subscription(defaultBaseUrl, topic);
|
||||||
|
props.onSubmit(subscription);
|
||||||
|
setTopic('');
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Dialog open={props.open} onClose={props.onClose}>
|
||||||
|
<DialogTitle>Subscribe to topic</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText>
|
||||||
|
Topics may not be password-protected, so choose a name that's not easy to guess.
|
||||||
|
Once subscribed, you can PUT/POST notifications.
|
||||||
|
</DialogContentText>
|
||||||
|
<TextField
|
||||||
|
autoFocus
|
||||||
|
margin="dense"
|
||||||
|
id="name"
|
||||||
|
label="Topic name, e.g. phil_alerts"
|
||||||
|
value={topic}
|
||||||
|
onChange={ev => setTopic(ev.target.value)}
|
||||||
|
type="text"
|
||||||
|
fullWidth
|
||||||
|
variant="standard"
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={handleCancel}>Cancel</Button>
|
||||||
|
<Button onClick={handleSubmit} autoFocus disabled={topic === ""}>Subscribe</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddDialog;
|
100
web/src/App.js
100
web/src/App.js
|
@ -4,9 +4,8 @@ import Container from '@mui/material/Container';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Link from '@mui/material/Link';
|
import Link from '@mui/material/Link';
|
||||||
import Subscription from './Subscription';
|
|
||||||
import WsConnection from './WsConnection';
|
import WsConnection from './WsConnection';
|
||||||
import {createTheme, styled, ThemeProvider} from '@mui/material/styles';
|
import {styled, ThemeProvider} from '@mui/material/styles';
|
||||||
import CssBaseline from '@mui/material/CssBaseline';
|
import CssBaseline from '@mui/material/CssBaseline';
|
||||||
import MuiDrawer from '@mui/material/Drawer';
|
import MuiDrawer from '@mui/material/Drawer';
|
||||||
import MuiAppBar from '@mui/material/AppBar';
|
import MuiAppBar from '@mui/material/AppBar';
|
||||||
|
@ -17,7 +16,6 @@ import Divider from '@mui/material/Divider';
|
||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from '@mui/material/IconButton';
|
||||||
import Badge from '@mui/material/Badge';
|
import Badge from '@mui/material/Badge';
|
||||||
import Grid from '@mui/material/Grid';
|
import Grid from '@mui/material/Grid';
|
||||||
import Paper from '@mui/material/Paper';
|
|
||||||
import MenuIcon from '@mui/icons-material/Menu';
|
import MenuIcon from '@mui/icons-material/Menu';
|
||||||
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
|
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
|
||||||
import NotificationsIcon from '@mui/icons-material/Notifications';
|
import NotificationsIcon from '@mui/icons-material/Notifications';
|
||||||
|
@ -28,19 +26,8 @@ import SettingsIcon from "@mui/icons-material/Settings";
|
||||||
import AddIcon from "@mui/icons-material/Add";
|
import AddIcon from "@mui/icons-material/Add";
|
||||||
import Card from "@mui/material/Card";
|
import Card from "@mui/material/Card";
|
||||||
import {Button, CardActions, CardContent, Stack} from "@mui/material";
|
import {Button, CardActions, CardContent, Stack} from "@mui/material";
|
||||||
|
import AddDialog from "./AddDialog";
|
||||||
function Copyright(props) {
|
import theme from "./theme";
|
||||||
return (
|
|
||||||
<Typography variant="body2" color="text.secondary" align="center" {...props}>
|
|
||||||
{'Copyright © '}
|
|
||||||
<Link color="inherit" href="https://mui.com/">
|
|
||||||
Your Website
|
|
||||||
</Link>{' '}
|
|
||||||
{new Date().getFullYear()}
|
|
||||||
{'.'}
|
|
||||||
</Typography>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const drawerWidth = 240;
|
const drawerWidth = 240;
|
||||||
|
|
||||||
|
@ -88,32 +75,29 @@ const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open'
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const mdTheme = createTheme();
|
|
||||||
|
|
||||||
const SubscriptionNav = (props) => {
|
const SubscriptionNav = (props) => {
|
||||||
const subscriptions = props.subscriptions;
|
const subscriptions = props.subscriptions;
|
||||||
return (
|
return (
|
||||||
<div className="subscriptionList">
|
<>
|
||||||
{Object.keys(subscriptions).map(id =>
|
{Object.keys(subscriptions).map(id =>
|
||||||
<SubscriptionItem
|
<SubscriptionNavItem
|
||||||
key={id}
|
key={id}
|
||||||
subscription={subscriptions[id]}
|
subscription={subscriptions[id]}
|
||||||
selected={props.selectedSubscription === subscriptions[id]}
|
selected={props.selectedSubscription === subscriptions[id]}
|
||||||
onClick={() => props.handleSubscriptionClick(id)}
|
onClick={() => props.handleSubscriptionClick(id)}
|
||||||
/>)
|
/>)
|
||||||
}
|
}
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const SubscriptionItem = (props) => {
|
const SubscriptionNavItem = (props) => {
|
||||||
const subscription = props.subscription;
|
const subscription = props.subscription;
|
||||||
return (
|
return (
|
||||||
<ListItemButton onClick={props.onClick}>
|
<ListItemButton onClick={props.onClick} selected={props.selected}>
|
||||||
<ListItemIcon>
|
<ListItemIcon><ChatBubbleOutlineIcon /></ListItemIcon>
|
||||||
<ChatBubbleOutlineIcon />
|
<ListItemText primary={subscription.shortUrl()}/>
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText primary={subscription.shortUrl()} />
|
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -136,68 +120,48 @@ const NotificationItem = (props) => {
|
||||||
{notification.time}
|
{notification.time}
|
||||||
</Typography>
|
</Typography>
|
||||||
{notification.title && <Typography variant="h5" component="div">
|
{notification.title && <Typography variant="h5" component="div">
|
||||||
title: {notification.title}
|
{notification.title}
|
||||||
</Typography>}
|
</Typography>}
|
||||||
<Typography variant="body1">
|
<Typography variant="body1">
|
||||||
msg: {notification.message}
|
{notification.message}
|
||||||
</Typography>
|
</Typography>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardActions>
|
|
||||||
<Button size="small">Learn More</Button>
|
|
||||||
</CardActions>
|
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultBaseUrl = "https://ntfy.sh"
|
|
||||||
|
|
||||||
const SubscriptionAddForm = (props) => {
|
|
||||||
const [topic, setTopic] = useState("");
|
|
||||||
const handleSubmit = (ev) => {
|
|
||||||
ev.preventDefault();
|
|
||||||
props.onSubmit(new Subscription(defaultBaseUrl, topic));
|
|
||||||
setTopic('');
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<form onSubmit={handleSubmit}>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={topic}
|
|
||||||
onChange={ev => setTopic(ev.target.value)}
|
|
||||||
placeholder="Topic name, e.g. phil_alerts"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [open, setOpen] = React.useState(true);
|
const [drawerOpen, setDrawerOpen] = useState(true);
|
||||||
const [subscriptions, setSubscriptions] = useState({});
|
const [subscriptions, setSubscriptions] = useState({});
|
||||||
const [selectedSubscription, setSelectedSubscription] = useState(null);
|
const [selectedSubscription, setSelectedSubscription] = useState(null);
|
||||||
const [connections, setConnections] = useState({});
|
const [connections, setConnections] = useState({});
|
||||||
|
const [addDialogOpen, setAddDialogOpen] = useState(false);
|
||||||
const subscriptionChanged = (subscription) => {
|
const subscriptionChanged = (subscription) => {
|
||||||
setSubscriptions(prev => ({...prev, [subscription.id]: subscription})); // Fake-replace
|
setSubscriptions(prev => ({...prev, [subscription.id]: subscription})); // Fake-replace
|
||||||
};
|
};
|
||||||
const addSubscription = (subscription) => {
|
const handleAddSubmit = (subscription) => {
|
||||||
|
setAddDialogOpen(false);
|
||||||
const connection = new WsConnection(subscription, subscriptionChanged);
|
const connection = new WsConnection(subscription, subscriptionChanged);
|
||||||
setSubscriptions(prev => ({...prev, [subscription.id]: subscription}));
|
setSubscriptions(prev => ({...prev, [subscription.id]: subscription}));
|
||||||
setConnections(prev => ({...prev, [connection.id]: connection}));
|
setConnections(prev => ({...prev, [connection.id]: connection}));
|
||||||
connection.start();
|
connection.start();
|
||||||
};
|
};
|
||||||
|
const handleAddCancel = () => {
|
||||||
|
setAddDialogOpen(false);
|
||||||
|
}
|
||||||
const handleSubscriptionClick = (subscriptionId) => {
|
const handleSubscriptionClick = (subscriptionId) => {
|
||||||
console.log(`handleSubscriptionClick ${subscriptionId}`)
|
console.log(`handleSubscriptionClick ${subscriptionId}`)
|
||||||
setSelectedSubscription(subscriptions[subscriptionId]);
|
setSelectedSubscription(subscriptions[subscriptionId]);
|
||||||
};
|
};
|
||||||
const notifications = (selectedSubscription !== null) ? selectedSubscription.notifications : [];
|
const notifications = (selectedSubscription !== null) ? selectedSubscription.notifications : [];
|
||||||
const toggleDrawer = () => {
|
const toggleDrawer = () => {
|
||||||
setOpen(!open);
|
setDrawerOpen(!drawerOpen);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={mdTheme}>
|
<ThemeProvider theme={theme}>
|
||||||
<Box sx={{ display: 'flex' }}>
|
<Box sx={{ display: 'flex' }}>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<AppBar position="absolute" open={open}>
|
<AppBar position="absolute" open={drawerOpen}>
|
||||||
<Toolbar
|
<Toolbar
|
||||||
sx={{
|
sx={{
|
||||||
pr: '24px', // keep right padding when drawer closed
|
pr: '24px', // keep right padding when drawer closed
|
||||||
|
@ -211,7 +175,7 @@ const App = () => {
|
||||||
onClick={toggleDrawer}
|
onClick={toggleDrawer}
|
||||||
sx={{
|
sx={{
|
||||||
marginRight: '36px',
|
marginRight: '36px',
|
||||||
...(open && { display: 'none' }),
|
...(drawerOpen && { display: 'none' }),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MenuIcon />
|
<MenuIcon />
|
||||||
|
@ -232,7 +196,7 @@ const App = () => {
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</AppBar>
|
</AppBar>
|
||||||
<Drawer variant="permanent" open={open}>
|
<Drawer variant="permanent" open={drawerOpen}>
|
||||||
<Toolbar
|
<Toolbar
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
@ -259,7 +223,7 @@ const App = () => {
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText primary="Settings" />
|
<ListItemText primary="Settings" />
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
<ListItemButton>
|
<ListItemButton onClick={() => setAddDialogOpen(true)}>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<AddIcon />
|
<AddIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
|
@ -281,21 +245,17 @@ const App = () => {
|
||||||
>
|
>
|
||||||
<Toolbar />
|
<Toolbar />
|
||||||
<Container maxWidth="lg" sx={{ mt: 4, mb: 4 }}>
|
<Container maxWidth="lg" sx={{ mt: 4, mb: 4 }}>
|
||||||
|
|
||||||
<Grid container spacing={3}>
|
<Grid container spacing={3}>
|
||||||
<SubscriptionAddForm onSubmit={addSubscription}/>
|
|
||||||
<NotificationList notifications={notifications}/>
|
<NotificationList notifications={notifications}/>
|
||||||
{/* Recent Orders */}
|
|
||||||
<Grid item xs={12}>
|
|
||||||
<Paper sx={{ p: 2, display: 'flex', flexDirection: 'column' }}>
|
|
||||||
|
|
||||||
</Paper>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
|
||||||
<Copyright sx={{ pt: 4 }} />
|
|
||||||
</Container>
|
</Container>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
<AddDialog
|
||||||
|
open={addDialogOpen}
|
||||||
|
onCancel={handleAddCancel}
|
||||||
|
onSubmit={handleAddSubmit}
|
||||||
|
/>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
import * as React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import Typography from '@mui/material/Typography';
|
|
||||||
|
|
||||||
function Title(props) {
|
|
||||||
return (
|
|
||||||
<Typography component="h2" variant="h6" color="primary" gutterBottom>
|
|
||||||
{props.children}
|
|
||||||
</Typography>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Title.propTypes = {
|
|
||||||
children: PropTypes.node,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Title;
|
|
|
@ -8,7 +8,7 @@ const theme = createTheme({
|
||||||
main: '#338574',
|
main: '#338574',
|
||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
main: '#338574',
|
main: '#6cead0',
|
||||||
},
|
},
|
||||||
error: {
|
error: {
|
||||||
main: red.A400,
|
main: red.A400,
|
||||||
|
|
Loading…
Reference in a new issue