diff --git a/web/src/app/utils.js b/web/src/app/utils.js index c9b5b4c5..d8763409 100644 --- a/web/src/app/utils.js +++ b/web/src/app/utils.js @@ -10,6 +10,7 @@ export const topicUrlJsonPollWithSince = (baseUrl, topic, since) => `${topicUrlJ export const topicUrlAuth = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/auth`; export const topicShortUrl = (baseUrl, topic) => shortUrl(topicUrl(baseUrl, topic)); export const shortUrl = (url) => url.replaceAll(/https?:\/\//g, ""); +export const expandUrl = (url) => [`https://${url}`, `http://${url}`]; export const validUrl = (url) => { return url.match(/^https?:\/\//); diff --git a/web/src/components/ActionBar.js b/web/src/components/ActionBar.js index 88e36dcd..a135a238 100644 --- a/web/src/components/ActionBar.js +++ b/web/src/components/ActionBar.js @@ -22,8 +22,8 @@ import subscriptionManager from "../app/SubscriptionManager"; const ActionBar = (props) => { const location = useLocation(); let title = "ntfy"; - if (props.selectedSubscription) { - title = topicShortUrl(props.selectedSubscription.baseUrl, props.selectedSubscription.topic); + if (props.selected) { + title = topicShortUrl(props.selected.baseUrl, props.selected.topic); } else if (location.pathname === "/settings") { title = "Settings"; } @@ -50,8 +50,8 @@ const ActionBar = (props) => { <Typography variant="h6" noWrap component="div" sx={{ flexGrow: 1 }}> {title} </Typography> - {props.selectedSubscription && <SettingsIcon - subscription={props.selectedSubscription} + {props.selected && <SettingsIcon + subscription={props.selected} onUnsubscribe={props.onUnsubscribe} />} </Toolbar> diff --git a/web/src/components/App.js b/web/src/components/App.js index 2367fa6b..29899850 100644 --- a/web/src/components/App.js +++ b/web/src/components/App.js @@ -12,16 +12,12 @@ import ActionBar from "./ActionBar"; import notifier from "../app/Notifier"; import Preferences from "./Preferences"; import {useLiveQuery} from "dexie-react-hooks"; -import poller from "../app/Poller"; -import pruner from "../app/Pruner"; import subscriptionManager from "../app/SubscriptionManager"; import userManager from "../app/UserManager"; -import {BrowserRouter, Route, Routes, useLocation, useNavigate} from "react-router-dom"; -import {subscriptionRoute} from "../app/utils"; +import {BrowserRouter, Route, Routes, useNavigate, useParams} from "react-router-dom"; +import {expandUrl, subscriptionRoute} from "../app/utils"; // TODO support unsubscribed routes -// TODO add "home" route that is selected when nothing else fits -// TODO new notification indicator // TODO "copy url" toast // TODO "copy link url" button // TODO races when two tabs are open @@ -32,42 +28,78 @@ const App = () => { <BrowserRouter> <ThemeProvider theme={theme}> <CssBaseline/> - <Root/> + <Content/> </ThemeProvider> </BrowserRouter> ); } -const Root = () => { +const Content = () => { + const subscriptions = useLiveQuery(() => subscriptionManager.all()); + // const context = { subscriptions }; + return ( + <Routes> + <Route path="settings" element={<PrefLayout subscriptions={subscriptions}/>} /> + <Route path="settings" element={<PrefLayout subscriptions={subscriptions}/>} /> + <Route path="/" element={<AllSubscriptions subscriptions={subscriptions}/>} /> + <Route path=":baseUrl/:topic" element={<SingleSubscription subscriptions={subscriptions}/>} /> + <Route path=":topic" element={<SingleSubscription subscriptions={subscriptions}/>} /> + </Routes> + ) +}; + +const AllSubscriptions = (props) => { + return ( + <Layout subscriptions={props.subscriptions}> + <Notifications mode="all" subscriptions={props.subscriptions}/> + </Layout> + ); +} + +const SingleSubscription = (props) => { + const params = useParams(); + const [selected] = (props.subscriptions || []).filter(s => { + return (params.baseUrl && expandUrl(params.baseUrl).includes(s.baseUrl) && params.topic === s.topic) + || (window.location.origin === s.baseUrl && params.topic === s.topic) + }); + return ( + <Layout subscriptions={props.subscriptions} selected={selected}> + <Notifications mode="one" subscription={selected}/> + </Layout> + ); +} + +const PrefLayout = (props) => { + return ( + <Layout subscriptions={props.subscriptions}> + <Preferences/> + </Layout> + ); +} + +const Layout = (props) => { + const subscriptions = props.subscriptions; // May be null/undefined + const selected = props.selected; // May be null/undefined const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false); const [notificationsGranted, setNotificationsGranted] = useState(notifier.granted()); - const location = useLocation(); const users = useLiveQuery(() => userManager.all()); - const subscriptions = useLiveQuery(() => subscriptionManager.all()); - const selectedSubscription = findSelected(location, subscriptions); const newNotificationsCount = subscriptions?.reduce((prev, cur) => prev + cur.new, 0) || 0; useConnectionListeners(); - useEffect(() => { - connectionManager.refresh(subscriptions, users); - }, [subscriptions, users]); // Dangle! - - useEffect(() => { - document.title = (newNotificationsCount > 0) ? `(${newNotificationsCount}) ntfy web` : "ntfy web"; - }, [newNotificationsCount]); + useEffect(() => connectionManager.refresh(subscriptions, users), [subscriptions, users]); + useEffect(() => updateTitle(newNotificationsCount), [newNotificationsCount]); return ( <Box sx={{display: 'flex'}}> <CssBaseline/> <ActionBar - subscriptions={subscriptions} - selectedSubscription={selectedSubscription} + selected={selected} onMobileDrawerToggle={() => setMobileDrawerOpen(!mobileDrawerOpen)} /> <Navigation subscriptions={subscriptions} - selectedSubscription={selectedSubscription} + selectedSubscription={selected} notificationsGranted={notificationsGranted} mobileDrawerOpen={mobileDrawerOpen} onMobileDrawerToggle={() => setMobileDrawerOpen(!mobileDrawerOpen)} @@ -75,12 +107,7 @@ const Root = () => { /> <Main> <Toolbar/> - <Routes> - <Route path="settings" element={<Preferences />} /> - <Route path="/" element={<Notifications mode="all" subscriptions={subscriptions} />} /> - <Route path=":baseUrl/:topic" element={<Notifications mode="one" subscription={selectedSubscription}/>} /> - <Route path=":topic" element={<Notifications mode="one" subscription={selectedSubscription}/>} /> - </Routes> + {props.children} </Main> </Box> ); @@ -107,32 +134,6 @@ const Main = (props) => { ); }; -const findSelected = (location, subscriptions) => { - if (!subscriptions || !location) { - return null; - } - const [subscription] = subscriptions.filter(s => location.pathname === subscriptionRoute(s)); - return subscription; - - /* - if (location.pathname === "/" || location.pathname === "/settings") { - return null; - } - if (!subscription) { - const [, topic] = location.pathname.split("/"); - const subscription = { - id: topicUrl(window.location.origin, topic), - baseUrl: window.location.origin, - topic: topic, - last: "" - } - subscriptionManager.save(subscription); - return subscription; - } - - */ -}; - const useConnectionListeners = () => { const navigate = useNavigate(); useEffect(() => { @@ -155,4 +156,8 @@ const useConnectionListeners = () => { []); }; +const updateTitle = (newNotificationsCount) => { + document.title = (newNotificationsCount > 0) ? `(${newNotificationsCount}) ntfy web` : "ntfy web"; +} + export default App;