diff --git a/web/package-lock.json b/web/package-lock.json index 86f97a3..067a0fa 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -13,6 +13,7 @@ "@mui/material": "latest", "react": "latest", "react-dom": "latest", + "react-router-dom": "^6.2.1", "react-scripts": "^3.0.1" } }, @@ -8313,6 +8314,14 @@ "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==" }, + "node_modules/history": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", + "integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==", + "dependencies": { + "@babel/runtime": "^7.7.6" + } + }, "node_modules/hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -13673,6 +13682,30 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "node_modules/react-router": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.2.1.tgz", + "integrity": "sha512-2fG0udBtxou9lXtK97eJeET2ki5//UWfQSl1rlJ7quwe6jrktK9FCCc8dQb5QY6jAv3jua8bBQRhhDOM/kVRsg==", + "dependencies": { + "history": "^5.2.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.2.1.tgz", + "integrity": "sha512-I6Zax+/TH/cZMDpj3/4Fl2eaNdcvoxxHoH1tYOREsQ22OKDYofGebrNm6CTPUcvLvZm63NL/vzCYdjf9CUhqmA==", + "dependencies": { + "history": "^5.2.0", + "react-router": "6.2.1" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-scripts": { "version": "3.4.4", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-3.4.4.tgz", @@ -24030,6 +24063,14 @@ "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==" }, + "history": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", + "integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==", + "requires": { + "@babel/runtime": "^7.7.6" + } + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -28290,6 +28331,23 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "react-router": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.2.1.tgz", + "integrity": "sha512-2fG0udBtxou9lXtK97eJeET2ki5//UWfQSl1rlJ7quwe6jrktK9FCCc8dQb5QY6jAv3jua8bBQRhhDOM/kVRsg==", + "requires": { + "history": "^5.2.0" + } + }, + "react-router-dom": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.2.1.tgz", + "integrity": "sha512-I6Zax+/TH/cZMDpj3/4Fl2eaNdcvoxxHoH1tYOREsQ22OKDYofGebrNm6CTPUcvLvZm63NL/vzCYdjf9CUhqmA==", + "requires": { + "history": "^5.2.0", + "react-router": "6.2.1" + } + }, "react-scripts": { "version": "3.4.4", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-3.4.4.tgz", diff --git a/web/package.json b/web/package.json index a4279ef..d81b9a9 100644 --- a/web/package.json +++ b/web/package.json @@ -9,12 +9,13 @@ "eject": "react-scripts eject" }, "dependencies": { - "react": "latest", - "react-dom": "latest", - "react-scripts": "^3.0.1", + "@emotion/styled": "latest", "@mui/icons-material": "^5.4.2", "@mui/material": "latest", - "@emotion/styled": "latest" + "react": "latest", + "react-dom": "latest", + "react-router-dom": "^6.2.1", + "react-scripts": "^3.0.1" }, "browserslist": { "production": [ diff --git a/web/src/app/utils.js b/web/src/app/utils.js index 00d56db..416be6d 100644 --- a/web/src/app/utils.js +++ b/web/src/app/utils.js @@ -60,7 +60,6 @@ export async function* fetchLinesIterator(fileURL) { const re = /\n|\r|\r\n/gm; let startIndex = 0; - let result; for (;;) { let result = re.exec(chunk); diff --git a/web/src/components/App.js b/web/src/components/App.js index 89f5b6f..5c56a4b 100644 --- a/web/src/components/App.js +++ b/web/src/components/App.js @@ -2,25 +2,24 @@ import * as React from 'react'; import {useEffect, useState} from 'react'; import Typography from '@mui/material/Typography'; import Box from '@mui/material/Box'; -import {styled, ThemeProvider} from '@mui/material/styles'; +import {ThemeProvider} from '@mui/material/styles'; import CssBaseline from '@mui/material/CssBaseline'; -import MuiDrawer from '@mui/material/Drawer'; -import MuiAppBar from '@mui/material/AppBar'; +import Drawer from '@mui/material/Drawer'; +import AppBar from '@mui/material/AppBar'; import Toolbar from '@mui/material/Toolbar'; import ChatBubbleOutlineIcon from '@mui/icons-material/ChatBubbleOutline'; import List from '@mui/material/List'; import Divider from '@mui/material/Divider'; import IconButton from '@mui/material/IconButton'; import MenuIcon from '@mui/icons-material/Menu'; -import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'; import ListItemIcon from "@mui/material/ListItemIcon"; import ListItemText from "@mui/material/ListItemText"; import ListItemButton from "@mui/material/ListItemButton"; import SettingsIcon from "@mui/icons-material/Settings"; import AddIcon from "@mui/icons-material/Add"; -import AddDialog from "./AddDialog"; +import SubscribeDialog from "./SubscribeDialog"; import NotificationList from "./NotificationList"; -import SubscriptionSettings from "./SubscriptionSettings"; +import IconSubscribeSettings from "./IconSubscribeSettings"; import theme from "./theme"; import api from "../app/Api"; import repository from "../app/Repository"; @@ -29,68 +28,23 @@ import Subscriptions from "../app/Subscriptions"; const drawerWidth = 240; -const AppBar = styled(MuiAppBar, { - shouldForwardProp: (prop) => prop !== 'open', -})(({ theme, open }) => ({ - zIndex: theme.zIndex.drawer + 1, - transition: theme.transitions.create(['width', 'margin'], { - easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.leavingScreen, - }), - ...(open && { - marginLeft: drawerWidth, - width: `calc(100% - ${drawerWidth}px)`, - transition: theme.transitions.create(['width', 'margin'], { - easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.enteringScreen, - }), - }), -})); - -const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open' })( - ({ theme, open }) => ({ - '& .MuiDrawer-paper': { - position: 'relative', - whiteSpace: 'nowrap', - width: drawerWidth, - transition: theme.transitions.create('width', { - easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.enteringScreen, - }), - boxSizing: 'border-box', - ...(!open && { - overflowX: 'hidden', - transition: theme.transitions.create('width', { - easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.leavingScreen, - }), - width: theme.spacing(7), - [theme.breakpoints.up('sm')]: { - width: theme.spacing(9), - }, - }), - }, - }), -); - - -const SubscriptionNav = (props) => { +const NavSubscriptionList = (props) => { const subscriptions = props.subscriptions; return ( <> {subscriptions.map((id, subscription) => - props.handleSubscriptionClick(id)} + onClick={() => props.onSubscriptionClick(id)} />) } ); } -const SubscriptionNavItem = (props) => { +const NavSubscriptionItem = (props) => { const subscription = props.subscription; return ( @@ -100,13 +54,115 @@ const SubscriptionNavItem = (props) => { ); } +const NavList = (props) => { + const [subscribeDialogOpen, setSubscribeDialogOpen] = useState(false); + const handleSubscribeSubmit = (subscription) => { + setSubscribeDialogOpen(false); + props.onSubscribeSubmit(subscription); + } + return ( + <> + + + + + + + + + + + + setSubscribeDialogOpen(true)}> + + + + + + + setSubscribeDialogOpen(false)} + onSubmit={handleSubscribeSubmit} + /> + + ); +}; + +const ActionBar = (props) => { + const title = (props.selectedSubscription !== null) + ? props.selectedSubscription.shortUrl() + : "ntfy"; + return ( + + + + + + {title} + {props.selectedSubscription !== null && } + + + ); +}; + +const Sidebar = (props) => { + const navigationList = + ; + return ( + <> + {/* Mobile drawer; only shown if menu icon clicked (mobile open) and display is small */} + + {navigationList} + + {/* Big screen drawer; persistent, shown if screen is big */} + + {navigationList} + + + ); +}; + const App = () => { console.log(`[App] Rendering main view`); - const [drawerOpen, setDrawerOpen] = useState(true); + const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false); const [subscriptions, setSubscriptions] = useState(new Subscriptions()); const [selectedSubscription, setSelectedSubscription] = useState(null); - const [subscribeDialogOpen, setSubscribeDialogOpen] = useState(false); const handleNotification = (subscriptionId, notification) => { setSubscriptions(prev => { const newSubscription = prev.get(subscriptionId).addNotification(notification); @@ -115,7 +171,6 @@ const App = () => { }; const handleSubscribeSubmit = (subscription) => { console.log(`[App] New subscription: ${subscription.id}`); - setSubscribeDialogOpen(false); setSubscriptions(prev => prev.add(subscription).clone()); setSelectedSubscription(subscription); api.poll(subscription.baseUrl, subscription.topic) @@ -126,10 +181,6 @@ const App = () => { }); }); }; - const handleSubscribeCancel = () => { - console.log(`[App] Cancel clicked`); - setSubscribeDialogOpen(false); - }; const handleClearAll = (subscriptionId) => { console.log(`[App] Deleting all notifications from ${subscriptionId}`); setSubscriptions(prev => { @@ -145,14 +196,7 @@ const App = () => { return newSubscriptions; }); }; - const handleSubscriptionClick = (subscriptionId) => { - console.log(`[App] Selected ${subscriptionId}`); - setSelectedSubscription(subscriptions.get(subscriptionId)); - }; const notifications = (selectedSubscription !== null) ? selectedSubscription.getNotifications() : []; - const toggleDrawer = () => { - setDrawerOpen(!drawerOpen); - }; useEffect(() => { setSubscriptions(repository.loadSubscriptions()); }, [/* initial render only */]); @@ -160,96 +204,42 @@ const App = () => { connectionManager.refresh(subscriptions, handleNotification); repository.saveSubscriptions(subscriptions); }, [subscriptions]); + return ( - - - - - - - - - {(selectedSubscription !== null) ? selectedSubscription.shortUrl() : "ntfy"} - - {selectedSubscription !== null && } - - - - - - - - - - - - - - - - - - - setSubscribeDialogOpen(true)}> - - - - - - - + + + + setMobileDrawerOpen(!mobileDrawerOpen)} + /> + + setMobileDrawerOpen(!mobileDrawerOpen)} + onSubscriptionClick={(subscriptionId) => setSelectedSubscription(subscriptions.get(subscriptionId))} + onSubscribeSubmit={handleSubscribeSubmit} + /> + - theme.palette.mode === 'light' - ? theme.palette.grey[100] - : theme.palette.grey[900], flexGrow: 1, + p: 3, + width: {sm: `calc(100% - ${drawerWidth}px)`}, height: '100vh', overflow: 'auto', - }} - > - + backgroundColor: (theme) => theme.palette.mode === 'light' ? theme.palette.grey[100] : theme.palette.grey[900] + }}> + - ); } diff --git a/web/src/components/SubscriptionSettings.js b/web/src/components/IconSubscribeSettings.js similarity index 98% rename from web/src/components/SubscriptionSettings.js rename to web/src/components/IconSubscribeSettings.js index 731cd8b..a3f1877 100644 --- a/web/src/components/SubscriptionSettings.js +++ b/web/src/components/IconSubscribeSettings.js @@ -11,7 +11,7 @@ import MoreVertIcon from "@mui/icons-material/MoreVert"; import api from "../app/Api"; // Originally from https://mui.com/components/menus/#MenuListComposition.js -const SubscriptionSettings = (props) => { +const IconSubscribeSettings = (props) => { const [open, setOpen] = useState(false); const anchorRef = useRef(null); @@ -114,4 +114,4 @@ const SubscriptionSettings = (props) => { ); } -export default SubscriptionSettings; +export default IconSubscribeSettings; diff --git a/web/src/components/AddDialog.js b/web/src/components/SubscribeDialog.js similarity index 96% rename from web/src/components/AddDialog.js rename to web/src/components/SubscribeDialog.js index c1b1fe2..1a8388f 100644 --- a/web/src/components/AddDialog.js +++ b/web/src/components/SubscribeDialog.js @@ -12,7 +12,7 @@ import Subscription from "../app/Subscription"; const defaultBaseUrl = "http://127.0.0.1" //const defaultBaseUrl = "https://ntfy.sh" -const AddDialog = (props) => { +const SubscribeDialog = (props) => { const [topic, setTopic] = useState(""); const handleCancel = () => { setTopic(''); @@ -53,4 +53,4 @@ const AddDialog = (props) => { ); }; -export default AddDialog; +export default SubscribeDialog;