diff --git a/web/public/static/img/ntfy-outline.svg b/web/public/static/img/ntfy-outline.svg new file mode 100644 index 00000000..481fe5d9 --- /dev/null +++ b/web/public/static/img/ntfy-outline.svg @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/web/public/static/img/ntfy.svg b/web/public/static/img/ntfy.svg new file mode 100644 index 00000000..bbe27783 --- /dev/null +++ b/web/public/static/img/ntfy.svg @@ -0,0 +1,214 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/web/src/app/Repository.js b/web/src/app/Repository.js index b4c2b56e..5371c0a4 100644 --- a/web/src/app/Repository.js +++ b/web/src/app/Repository.js @@ -76,6 +76,17 @@ class Repository { })); localStorage.setItem('users', serialized); } + + loadSelectedSubscriptionId() { + console.log(`[Repository] Loading selected subscription ID from localStorage`); + const selectedSubscriptionId = localStorage.getItem('selectedSubscriptionId'); + return (selectedSubscriptionId) ? selectedSubscriptionId : ""; + } + + saveSelectedSubscriptionId(selectedSubscriptionId) { + console.log(`[Repository] Saving selected subscription ${selectedSubscriptionId} to localStorage`); + localStorage.setItem('selectedSubscriptionId', selectedSubscriptionId); + } } const repository = new Repository(); diff --git a/web/src/components/ActionBar.js b/web/src/components/ActionBar.js index d5472ed9..132f47e4 100644 --- a/web/src/components/ActionBar.js +++ b/web/src/components/ActionBar.js @@ -26,6 +26,7 @@ const ActionBar = (props) => { > + {title} diff --git a/web/src/components/App.js b/web/src/components/App.js index ee42513c..e55bd95e 100644 --- a/web/src/components/App.js +++ b/web/src/components/App.js @@ -4,7 +4,7 @@ import Box from '@mui/material/Box'; import {ThemeProvider} from '@mui/material/styles'; import CssBaseline from '@mui/material/CssBaseline'; import Toolbar from '@mui/material/Toolbar'; -import NotificationList from "./NotificationList"; +import Notifications from "./Notifications"; import theme from "./theme"; import api from "../app/Api"; import repository from "../app/Repository"; @@ -14,14 +14,13 @@ import Navigation from "./Navigation"; import ActionBar from "./ActionBar"; import Users from "../app/Users"; import notificationManager from "../app/NotificationManager"; +import NoTopics from "./NoTopics"; -// FIXME chrome notification order // TODO subscribe dialog: // - check/use existing user // - add baseUrl // TODO user management // TODO embed into ntfy server -// TODO remember selected subscription const App = () => { console.log(`[App] Rendering main view`); @@ -84,10 +83,17 @@ const App = () => { useEffect(() => { // Load subscriptions and users const subscriptions = repository.loadSubscriptions(); + const selectedSubscriptionId = repository.loadSelectedSubscriptionId(); const users = repository.loadUsers(); setSubscriptions(subscriptions); setUsers(users); + // Set selected subscription + const maybeSelectedSubscription = subscriptions.get(selectedSubscriptionId); + if (maybeSelectedSubscription) { + setSelectedSubscription(maybeSelectedSubscription); + } + // Poll all subscriptions subscriptions.forEach((subscriptionId, subscription) => { const user = users.get(subscription.baseUrl); // May be null @@ -109,6 +115,10 @@ const App = () => { }, [subscriptions, users]); useEffect(() => repository.saveSubscriptions(subscriptions), [subscriptions]); useEffect(() => repository.saveUsers(users), [users]); + useEffect(() => { + const subscriptionId = (selectedSubscription) ? selectedSubscription.id : ""; + repository.saveSelectedSubscriptionId(subscriptionId) + }, [selectedSubscription]); return ( @@ -137,8 +147,10 @@ const App = () => { { }}> {selectedSubscription !== null && - } + {selectedSubscription == null && } diff --git a/web/src/components/NoTopics.js b/web/src/components/NoTopics.js new file mode 100644 index 00000000..364f4362 --- /dev/null +++ b/web/src/components/NoTopics.js @@ -0,0 +1,25 @@ +import {Link} from "@mui/material"; +import Typography from "@mui/material/Typography"; +import * as React from "react"; +import {Paragraph, VerticallyCenteredContainer} from "./styles"; + +const NoTopics = (props) => { + return ( + + + No topics
+ It looks like you don't have any subscriptions yet. +
+ + Click the "Add subscription" link to create or subscribe to a topic. After that, you can send messages + via PUT or POST and you'll receive notifications here. + + + For more information, check out the website or + {" "}documentation. + +
+ ); +}; + +export default NoTopics; diff --git a/web/src/components/NotificationList.js b/web/src/components/Notifications.js similarity index 62% rename from web/src/components/NotificationList.js rename to web/src/components/Notifications.js index 9facfb76..bfdf85ec 100644 --- a/web/src/components/NotificationList.js +++ b/web/src/components/Notifications.js @@ -1,18 +1,22 @@ import Container from "@mui/material/Container"; -import {CardContent, Stack} from "@mui/material"; +import {CardContent, Link, Stack} from "@mui/material"; import Card from "@mui/material/Card"; import Typography from "@mui/material/Typography"; import * as React from "react"; import {formatMessage, formatTitle, unmatchedTags} from "../app/utils"; import IconButton from "@mui/material/IconButton"; import CloseIcon from '@mui/icons-material/Close'; +import {Paragraph, VerticallyCenteredContainer} from "./styles"; -const NotificationList = (props) => { +const Notifications = (props) => { const subscription = props.subscription; const sortedNotifications = subscription.getNotifications() - .sort((a, b) => a.time < b.time); + .sort((a, b) => a.time < b.time ? 1 : -1); + if (sortedNotifications.length === 0) { + return ; + } return ( - + {sortedNotifications.map(notification => { ); } -export default NotificationList; +const NothingHereYet = (props) => { + return ( + + + No notifications
+ You haven't received any notifications for this topic yet. +
+ + To send notifications to this topic, simply PUT or POST to the topic URL. + + + Example:
+ + $ curl -d "Hi" {props.subscription.shortUrl()} + +
+ + For more detailed instructions, check out the website or + {" "}documentation. + +
+ ); +}; + +export default Notifications; diff --git a/web/src/components/styles.js b/web/src/components/styles.js index 5c47e4a6..ce044edc 100644 --- a/web/src/components/styles.js +++ b/web/src/components/styles.js @@ -1,4 +1,7 @@ -import {makeStyles} from "@mui/styles"; +import {makeStyles, 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: { @@ -15,4 +18,18 @@ const useStyles = makeStyles(theme => ({ } })); +export const Paragraph = styled(Typography)({ + paddingTop: 8, + paddingBottom: 8, +}); + +export const VerticallyCenteredContainer = styled(Container)({ + display: 'flex', + flexGrow: 1, + flexDirection: 'column', + justifyContent: 'center', + alignContent: 'center', + color: theme.palette.body.main +}); + export default useStyles; diff --git a/web/src/components/theme.js b/web/src/components/theme.js index 28fa1bce..669d5e7d 100644 --- a/web/src/components/theme.js +++ b/web/src/components/theme.js @@ -12,6 +12,9 @@ const theme = createTheme({ error: { main: red.A400, }, + body: { + main: '#444', + } }, });