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 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + width="50mm" + height="50mm" + viewBox="0 0 50 49.999999" + version="1.1" + id="svg8" + inkscape:version="1.1.1 (3bf5ae0, 2021-09-20)" + sodipodi:docname="main-list-icon.svg" + inkscape:export-filename="/home/pheckel/Code/ntfy-android/assets/launcher_full_bg.png" + inkscape:export-xdpi="260.10001" + inkscape:export-ydpi="260.10001" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <defs + id="defs2"> + <filter + style="color-interpolation-filters:sRGB" + inkscape:label="Drop Shadow" + id="filter3958" + x="-0.076083146" + y="-0.091641665" + width="1.1759423" + height="1.2114791"> + <feFlood + flood-opacity="0.192157" + flood-color="rgb(0,0,0)" + result="flood" + id="feFlood3948" /> + <feComposite + in="flood" + in2="SourceGraphic" + operator="in" + result="composite1" + id="feComposite3950" /> + <feGaussianBlur + in="composite1" + stdDeviation="4" + result="blur" + id="feGaussianBlur3952" /> + <feOffset + dx="3" + dy="2.95367" + result="offset" + id="feOffset3954" /> + <feComposite + in="SourceGraphic" + in2="offset" + operator="over" + result="composite2" + id="feComposite3956" /> + </filter> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:zoom="2.6887315" + inkscape:cx="42.40716" + inkscape:cy="53.189293" + inkscape:document-units="mm" + inkscape:current-layer="layer1" + showgrid="false" + inkscape:measure-start="0,0" + inkscape:measure-end="0,0" + inkscape:snap-text-baseline="true" + inkscape:window-width="1863" + inkscape:window-height="1025" + inkscape:window-x="57" + inkscape:window-y="27" + inkscape:window-maximized="1" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + showguides="false" + inkscape:guide-bbox="true" + inkscape:pagecheckerboard="0" + inkscape:rotation="-1"> + <sodipodi:guide + position="9.8690703,86.715698" + orientation="1,0" + id="guide1770" /> + <sodipodi:guide + position="39.661132,81.074874" + orientation="1,0" + id="guide1772" /> + <sodipodi:guide + position="9.8690703,58.786381" + orientation="0,-1" + id="guide1774" /> + <sodipodi:guide + position="-2.6121775,28.943566" + orientation="0,-1" + id="guide1776" /> + <sodipodi:guide + position="14.686182,55.195651" + orientation="1,0" + id="guide4020" /> + <sodipodi:guide + position="34.626283,58.786381" + orientation="1,0" + id="guide4022" /> + <sodipodi:guide + position="12.398156,51.002016" + orientation="0,-1" + id="guide4024" /> + <sodipodi:guide + position="11.073267,36.978591" + orientation="0,-1" + id="guide4026" /> + </sodipodi:namedview> + <metadata + id="metadata5"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="foreground" + inkscape:groupmode="layer" + id="layer1" + transform="translate(-51.451771,-87.327048)" + style="display:inline"> + <path + id="path2498" + style="color:#000000;fill:#777777;fill-opacity:1;stroke:none;stroke-width:0.754022;-inkscape-stroke:none" + d="m 59.291677,93.677052 c -3.579993,0 -6.646873,2.817003 -6.646873,6.398338 v 0.003 l 0.03508,27.86677 -0.899113,6.63475 12.226096,-3.24797 H 94.40052 c 3.579985,0 6.64687,-2.82079 6.64687,-6.40216 v -24.85449 c 0,-3.580312 -3.065184,-6.39668 -6.643822,-6.398338 h -0.0031 z m 0,4.516205 h 35.108844 0.0031 c 1.257851,0.0013 2.12767,0.916373 2.12767,1.882133 v 24.85442 c 0,0.9666 -0.871353,1.88213 -2.13072,1.88213 H 63.344139 l -6.211425,1.87679 0.0633,-0.36604 -0.03431,-28.2473 c 0,-0.966516 0.870609,-1.882133 2.129956,-1.882133 z" /> + <g + id="path1011-6-2" + transform="matrix(2.1452134,0,0,2.5503116,-71.247407,-178.388)" + style="font-size:8.48274px;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#777777;fill-opacity:1;stroke:none;stroke-width:0.525121"> + <path + style="color:#000000;-inkscape-font-specification:'JetBrains Mono, Bold';fill:#777777;fill-opacity:1;stroke:none;-inkscape-stroke:none" + d="m 62.57046,116.77004 v -1.31201 l 3.280018,-1.45904 q 0.158346,-0.0679 0.305381,-0.1018 0.158346,-0.0452 0.282761,-0.0679 0.135725,-0.0113 0.271449,-0.0226 v -0.0905 q -0.135724,-0.0113 -0.271449,-0.0452 -0.124415,-0.0226 -0.282761,-0.0566 -0.147035,-0.0452 -0.305381,-0.1131 l -3.280018,-1.45904 v -1.32332 l 5.067063,2.31863 v 1.4138 z" + id="path7553" /> + <path + style="color:#000000;-inkscape-font-specification:'JetBrains Mono, Bold';fill:#777777;fill-opacity:1;stroke:none;-inkscape-stroke:none" + d="m 62.308594,110.31055 v 1.90234 l 3.4375,1.5293 c 0.0073,0.003 0.0142,0.005 0.02148,0.008 -0.0073,0.003 -0.0142,0.005 -0.02148,0.008 l -3.4375,1.5293 v 1.89258 l 0.371093,-0.16992 5.220704,-2.39063 v -1.75 z m 0.52539,0.8164 4.541016,2.08008 v 1.07617 l -4.541016,2.07813 v -0.73242 l 3.119141,-1.38868 0.0039,-0.002 c 0.09141,-0.0389 0.178343,-0.0676 0.257813,-0.0859 h 0.0059 l 0.0078,-0.002 c 0.09483,-0.0271 0.176055,-0.0474 0.246093,-0.0606 l 0.498047,-0.041 v -0.57422 l -0.240234,-0.0195 c -0.07606,-0.006 -0.153294,-0.0198 -0.230469,-0.0391 l -0.0078,-0.002 -0.0078,-0.002 c -0.07608,-0.0138 -0.16556,-0.0318 -0.263672,-0.0527 -0.08398,-0.0262 -0.172736,-0.058 -0.265625,-0.0977 l -0.0039,-0.002 -3.119141,-1.38868 z" + id="path7555" /> + </g> + <g + id="g1224" + transform="matrix(2.1388566,0,0,2.4558588,-69.745456,-170.93962)" + style="font-size:8.48274px;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#777777;fill-opacity:1;stroke:none;stroke-width:0.525121"> + <path + style="color:#000000;-inkscape-font-specification:'JetBrains Mono, Bold';fill:#777777;fill-opacity:1;stroke:none;-inkscape-stroke:none" + d="m 69.17132,117.75404 h 5.428996 v 1.27808 H 69.17132 Z" + id="path1220" /> + <path + style="color:#000000;-inkscape-font-specification:'JetBrains Mono, Bold';fill:#777777;fill-opacity:1;stroke:none;-inkscape-stroke:none" + d="m 68.908203,117.49219 v 0.26172 1.54101 h 5.955078 v -1.80273 z m 0.525391,0.52344 h 4.904297 v 0.7539 h -4.904297 z" + id="path1222" /> + </g> + </g> +</svg> 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 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + width="50mm" + height="50mm" + viewBox="0 0 50 50" + version="1.1" + id="svg8" + inkscape:version="1.1.1 (3bf5ae0, 2021-09-20)" + sodipodi:docname="favicon-full.svg" + inkscape:export-filename="/home/pheckel/Code/ntfy-android/assets/favicon-full.png" + inkscape:export-xdpi="65.019997" + inkscape:export-ydpi="65.019997" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <defs + id="defs2"> + <linearGradient + inkscape:collect="always" + id="linearGradient28858-5"> + <stop + style="stop-color:#348878;stop-opacity:1" + offset="0" + id="stop28854-3" /> + <stop + style="stop-color:#56bda8;stop-opacity:1" + offset="1" + id="stop28856-5" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient28858-5" + id="linearGradient3255" + x1="160.72209" + y1="128.53317" + x2="168.41153" + y2="134.32626" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(5.5944799,0,0,5.5944799,-845.72623,-630.59839)" /> + <filter + style="color-interpolation-filters:sRGB;" + inkscape:label="Drop Shadow" + id="filter3958" + x="-0.076083149" + y="-0.091641662" + width="1.1759423" + height="1.2114791"> + <feFlood + flood-opacity="0.192157" + flood-color="rgb(0,0,0)" + result="flood" + id="feFlood3948" /> + <feComposite + in="flood" + in2="SourceGraphic" + operator="in" + result="composite1" + id="feComposite3950" /> + <feGaussianBlur + in="composite1" + stdDeviation="4" + result="blur" + id="feGaussianBlur3952" /> + <feOffset + dx="3" + dy="2.95367" + result="offset" + id="feOffset3954" /> + <feComposite + in="SourceGraphic" + in2="offset" + operator="over" + result="composite2" + id="feComposite3956" /> + </filter> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#747474" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:zoom="1.8244841" + inkscape:cx="-51.247364" + inkscape:cy="98.109926" + inkscape:document-units="mm" + inkscape:current-layer="layer1" + showgrid="false" + inkscape:measure-start="0,0" + inkscape:measure-end="0,0" + inkscape:snap-text-baseline="true" + inkscape:window-width="1863" + inkscape:window-height="1025" + inkscape:window-x="1977" + inkscape:window-y="27" + inkscape:window-maximized="1" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + showguides="false" + inkscape:guide-bbox="true" + inkscape:pagecheckerboard="0"> + <sodipodi:guide + position="10.173514,67.718331" + orientation="1,0" + id="guide1770" /> + <sodipodi:guide + position="39.965574,62.077508" + orientation="1,0" + id="guide1772" /> + <sodipodi:guide + position="10.173514,39.789015" + orientation="0,-1" + id="guide1774" /> + <sodipodi:guide + position="-2.3077334,9.9462015" + orientation="0,-1" + id="guide1776" /> + <sodipodi:guide + position="14.990626,36.198285" + orientation="1,0" + id="guide4020" /> + <sodipodi:guide + position="34.930725,39.789015" + orientation="1,0" + id="guide4022" /> + <sodipodi:guide + position="12.7026,32.00465" + orientation="0,-1" + id="guide4024" /> + <sodipodi:guide + position="11.377711,17.981227" + orientation="0,-1" + id="guide4026" /> + </sodipodi:namedview> + <metadata + id="metadata5"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:groupmode="layer" + id="layer2" + inkscape:label="background" + style="display:inline" /> + <g + inkscape:label="foreground" + inkscape:groupmode="layer" + id="layer1" + transform="translate(-51.147327,-81.515579)" + style="display:inline"> + <path + style="color:#000000;fill:url(#linearGradient3255);stroke:none;stroke-width:3.72347;-inkscape-stroke:none" + d="M 94.236816,89.911669 H 59.499347 c -2.388219,0 -4.34216,1.844109 -4.34216,4.098013 l 0.03319,27.754068 -0.648601,3.73856 9.297695,-2.80676 h 30.395197 c 2.388222,0 4.342215,-1.8441 4.342215,-4.09807 V 94.009682 c 0,-2.253904 -1.953993,-4.098013 -4.342215,-4.098013 z" + id="path7368" /> + <path + id="path2498" + style="color:#000000;fill:#ffffff;stroke:none;stroke-width:0.762343;-inkscape-stroke:none" + d="m 58.84854,86.790183 c -3.61952,0 -6.720259,2.848072 -6.720259,6.468906 v 0.0031 l 0.03546,30.272721 -0.90904,6.70792 12.361084,-3.2838 H 94.34501 c 3.619512,0 6.72026,-2.85186 6.72026,-6.47275 V 93.259089 c 0,-3.619798 -3.099027,-6.468906 -6.717176,-6.468906 h -0.0032 z m 0,4.566015 h 35.496477 0.0031 c 1.27174,0 2.15116,0.92648 2.15116,1.902891 v 27.227171 c 0,0.97724 -0.881151,1.92417 -2.154244,1.9029 H 62.945819 l -6.280004,1.89749 0.064,-0.3701 -0.0347,-30.657461 c 0,-0.977177 0.880222,-1.902891 2.153473,-1.902891 z" + sodipodi:nodetypes="ssccccsssscsccsssscccccc" /> + <g + id="path1011-6-2" + transform="matrix(2.1688984,0,0,2.5784384,-73.131815,-187.22607)" + style="font-size:8.48274px;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;stroke:none;stroke-width:0.525121"> + <path + style="color:#000000;-inkscape-font-specification:'JetBrains Mono, Bold';fill:#ffffff;stroke:none;-inkscape-stroke:none" + d="m 62.57046,116.77004 v -1.31201 l 3.280018,-1.45904 q 0.158346,-0.0679 0.305381,-0.1018 0.158346,-0.0452 0.282761,-0.0679 0.135725,-0.0113 0.271449,-0.0226 v -0.0905 q -0.135724,-0.0113 -0.271449,-0.0452 -0.124415,-0.0226 -0.282761,-0.0566 -0.147035,-0.0452 -0.305381,-0.1131 l -3.280018,-1.45904 v -1.32332 l 5.067063,2.31863 v 1.4138 z" + id="path7553" /> + <path + style="color:#000000;-inkscape-font-specification:'JetBrains Mono, Bold';fill:#ffffff;stroke:none;-inkscape-stroke:none" + d="m 62.308594,110.31055 v 1.90234 l 3.4375,1.5293 c 0.0073,0.003 0.0142,0.005 0.02148,0.008 -0.0073,0.003 -0.0142,0.005 -0.02148,0.008 l -3.4375,1.5293 v 1.89258 l 0.371093,-0.16992 5.220704,-2.39063 v -1.75 z m 0.52539,0.8164 4.541016,2.08008 v 1.07617 l -4.541016,2.07813 v -0.73242 l 3.119141,-1.38868 0.0039,-0.002 c 0.09141,-0.0389 0.178343,-0.0676 0.257813,-0.0859 h 0.0059 l 0.0078,-0.002 c 0.09483,-0.0271 0.176055,-0.0474 0.246093,-0.0606 l 0.498047,-0.041 v -0.57422 l -0.240234,-0.0195 c -0.07606,-0.006 -0.153294,-0.0198 -0.230469,-0.0391 l -0.0078,-0.002 -0.0078,-0.002 c -0.07608,-0.0138 -0.16556,-0.0318 -0.263672,-0.0527 -0.08398,-0.0262 -0.172736,-0.058 -0.265625,-0.0977 l -0.0039,-0.002 -3.119141,-1.38868 z" + id="path7555" /> + </g> + <g + id="g1224" + transform="matrix(2.1624714,0,0,2.4829436,-71.61328,-179.69552)" + style="font-size:8.48274px;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;stroke:none;stroke-width:0.525121"> + <path + style="color:#000000;-inkscape-font-specification:'JetBrains Mono, Bold';fill:#ffffff;stroke:none;-inkscape-stroke:none" + d="m 69.17132,117.75404 h 5.428996 v 1.27808 H 69.17132 Z" + id="path1220" /> + <path + style="color:#000000;-inkscape-font-specification:'JetBrains Mono, Bold';fill:#ffffff;stroke:none;-inkscape-stroke:none" + d="m 68.908203,117.49219 v 0.26172 1.54101 h 5.955078 v -1.80273 z m 0.525391,0.52344 h 4.904297 v 0.7539 h -4.904297 z" + id="path1222" /> + </g> + </g> + <g + inkscape:groupmode="layer" + id="layer3" + inkscape:label="round icon preview" + style="display:none"> + <path + id="path18850-8-1" + style="display:inline;fill:#ffffff;fill-opacity:1;stroke-width:0.255654" + d="M 50.337488,80.973198 V 131.61213 H 101.65302 V 80.973198 Z m 25.676545,1.442307 h 0.555989 a 24.369387,24.369387 0 0 1 23.860308,21.232925 v 6.09963 a 24.369387,24.369387 0 0 1 -21.288308,21.19336 h 21.288308 v 0.0138 H 51.963792 v -0.0158 H 73.428179 A 24.369387,24.369387 0 0 1 51.963792,107.97535 v -2.49089 A 24.369387,24.369387 0 0 1 76.014033,82.415508 Z" + transform="translate(-51.147326,-81.51558)" /> + </g> +</svg> 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) => { > <MenuIcon /> </IconButton> + <img src="static/img/ntfy.svg" height="28" style={{ marginRight: '10px' }}/> <Typography variant="h6" noWrap component="div" sx={{ flexGrow: 1 }}> {title} </Typography> 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 ( <ThemeProvider theme={theme}> @@ -137,8 +147,10 @@ const App = () => { <Box component="main" sx={{ + display: 'flex', flexGrow: 1, - p: 3, + flexDirection: 'column', + padding: 3, width: {sm: `calc(100% - ${Navigation.width}px)`}, height: '100vh', overflow: 'auto', @@ -146,10 +158,11 @@ const App = () => { }}> <Toolbar/> {selectedSubscription !== null && - <NotificationList + <Notifications subscription={selectedSubscription} onDeleteNotification={handleDeleteNotification} />} + {selectedSubscription == null && <NoTopics />} </Box> </Box> </ThemeProvider> 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 ( + <VerticallyCenteredContainer maxWidth="xs"> + <Typography variant="h5" align="center" sx={{ paddingBottom: 1 }}> + <img src="static/img/ntfy-outline.svg" height="64" width="64" alt="No topics"/><br /> + It looks like you don't have any subscriptions yet. + </Typography> + <Paragraph> + 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. + </Paragraph> + <Paragraph> + For more information, check out the <Link href="https://ntfy.sh" target="_blank" rel="noopener">website</Link> or + {" "}<Link href="https://ntfy.sh/docs" target="_blank" rel="noopener">documentation</Link>. + </Paragraph> + </VerticallyCenteredContainer> + ); +}; + +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 <NothingHereYet subscription={subscription}/>; + } return ( - <Container maxWidth="lg" sx={{ marginTop: 3, marginBottom: 3 }}> + <Container maxWidth="lg" sx={{marginTop: 3, marginBottom: 3}}> <Stack spacing={3}> {sortedNotifications.map(notification => <NotificationItem @@ -55,4 +59,28 @@ const NotificationItem = (props) => { ); } -export default NotificationList; +const NothingHereYet = (props) => { + return ( + <VerticallyCenteredContainer maxWidth="xs"> + <Typography variant="h5" align="center" sx={{ paddingBottom: 1 }}> + <img src="static/img/ntfy-outline.svg" height="64" width="64" alt="No notifications"/><br /> + You haven't received any notifications for this topic yet. + </Typography> + <Paragraph> + To send notifications to this topic, simply PUT or POST to the topic URL. + </Paragraph> + <Paragraph> + Example:<br/> + <tt> + $ curl -d "Hi" {props.subscription.shortUrl()} + </tt> + </Paragraph> + <Paragraph> + For more detailed instructions, check out the <Link href="https://ntfy.sh" target="_blank" rel="noopener">website</Link> or + {" "}<Link href="https://ntfy.sh/docs" target="_blank" rel="noopener">documentation</Link>. + </Paragraph> + </VerticallyCenteredContainer> + ); +}; + +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', + } }, });