Routing
This commit is contained in:
		
							parent
							
								
									e7bd3abadc
								
							
						
					
					
						commit
						b5670d9a71
					
				
					 9 changed files with 149 additions and 106 deletions
				
			
		
							
								
								
									
										58
									
								
								web/package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										58
									
								
								web/package-lock.json
									
										
									
										generated
									
									
									
								
							| 
						 | 
					@ -15,6 +15,7 @@
 | 
				
			||||||
        "dexie-react-hooks": "^1.1.1",
 | 
					        "dexie-react-hooks": "^1.1.1",
 | 
				
			||||||
        "react": "latest",
 | 
					        "react": "latest",
 | 
				
			||||||
        "react-dom": "latest",
 | 
					        "react-dom": "latest",
 | 
				
			||||||
 | 
					        "react-router-dom": "^6.2.2",
 | 
				
			||||||
        "react-scripts": "^3.0.1"
 | 
					        "react-scripts": "^3.0.1"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
| 
						 | 
					@ -8333,6 +8334,14 @@
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz",
 | 
				
			||||||
      "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ=="
 | 
					      "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": {
 | 
					    "node_modules/hmac-drbg": {
 | 
				
			||||||
      "version": "1.0.1",
 | 
					      "version": "1.0.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
 | 
				
			||||||
| 
						 | 
					@ -13693,6 +13702,30 @@
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
 | 
				
			||||||
      "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
 | 
					      "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/react-router": {
 | 
				
			||||||
 | 
					      "version": "6.2.2",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.2.2.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-/MbxyLzd7Q7amp4gDOGaYvXwhEojkJD5BtExkuKmj39VEE0m3l/zipf6h2WIB2jyAO0lI6NGETh4RDcktRm4AQ==",
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "history": "^5.2.0"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "peerDependencies": {
 | 
				
			||||||
 | 
					        "react": ">=16.8"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/react-router-dom": {
 | 
				
			||||||
 | 
					      "version": "6.2.2",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.2.2.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-AtYEsAST7bDD4dLSQHDnk/qxWLJdad5t1HFa1qJyUrCeGgEuCSw0VB/27ARbF9Fi/W5598ujvJOm3ujUCVzuYQ==",
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "history": "^5.2.0",
 | 
				
			||||||
 | 
					        "react-router": "6.2.2"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "peerDependencies": {
 | 
				
			||||||
 | 
					        "react": ">=16.8",
 | 
				
			||||||
 | 
					        "react-dom": ">=16.8"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/react-scripts": {
 | 
					    "node_modules/react-scripts": {
 | 
				
			||||||
      "version": "3.4.4",
 | 
					      "version": "3.4.4",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-3.4.4.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-3.4.4.tgz",
 | 
				
			||||||
| 
						 | 
					@ -24061,6 +24094,14 @@
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz",
 | 
				
			||||||
      "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ=="
 | 
					      "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": {
 | 
					    "hmac-drbg": {
 | 
				
			||||||
      "version": "1.0.1",
 | 
					      "version": "1.0.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
 | 
				
			||||||
| 
						 | 
					@ -28321,6 +28362,23 @@
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
 | 
				
			||||||
      "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
 | 
					      "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "react-router": {
 | 
				
			||||||
 | 
					      "version": "6.2.2",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.2.2.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-/MbxyLzd7Q7amp4gDOGaYvXwhEojkJD5BtExkuKmj39VEE0m3l/zipf6h2WIB2jyAO0lI6NGETh4RDcktRm4AQ==",
 | 
				
			||||||
 | 
					      "requires": {
 | 
				
			||||||
 | 
					        "history": "^5.2.0"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "react-router-dom": {
 | 
				
			||||||
 | 
					      "version": "6.2.2",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.2.2.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-AtYEsAST7bDD4dLSQHDnk/qxWLJdad5t1HFa1qJyUrCeGgEuCSw0VB/27ARbF9Fi/W5598ujvJOm3ujUCVzuYQ==",
 | 
				
			||||||
 | 
					      "requires": {
 | 
				
			||||||
 | 
					        "history": "^5.2.0",
 | 
				
			||||||
 | 
					        "react-router": "6.2.2"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "react-scripts": {
 | 
					    "react-scripts": {
 | 
				
			||||||
      "version": "3.4.4",
 | 
					      "version": "3.4.4",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-3.4.4.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-3.4.4.tgz",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,6 +16,7 @@
 | 
				
			||||||
    "dexie-react-hooks": "^1.1.1",
 | 
					    "dexie-react-hooks": "^1.1.1",
 | 
				
			||||||
    "react": "latest",
 | 
					    "react": "latest",
 | 
				
			||||||
    "react-dom": "latest",
 | 
					    "react-dom": "latest",
 | 
				
			||||||
 | 
					    "react-router-dom": "^6.2.2",
 | 
				
			||||||
    "react-scripts": "^3.0.1"
 | 
					    "react-scripts": "^3.0.1"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "browserslist": {
 | 
					  "browserslist": {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,6 @@ import {sha256} from "./utils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ConnectionManager {
 | 
					class ConnectionManager {
 | 
				
			||||||
    constructor() {
 | 
					    constructor() {
 | 
				
			||||||
        console.log(`connection manager`)
 | 
					 | 
				
			||||||
        this.connections = new Map(); // ConnectionId -> Connection (hash, see below)
 | 
					        this.connections = new Map(); // ConnectionId -> Connection (hash, see below)
 | 
				
			||||||
        this.stateListener = null; // Fired when connection state changes
 | 
					        this.stateListener = null; // Fired when connection state changes
 | 
				
			||||||
        this.notificationListener = null; // Fired when new notifications arrive
 | 
					        this.notificationListener = null; // Fired when new notifications arrive
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -114,6 +114,10 @@ export const openUrl = (url) => {
 | 
				
			||||||
    window.open(url, "_blank", "noopener,noreferrer");
 | 
					    window.open(url, "_blank", "noopener,noreferrer");
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const subscriptionRoute = (subscription) => {
 | 
				
			||||||
 | 
					    return `/${subscription.topic}`;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// From: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
 | 
					// From: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
 | 
				
			||||||
export async function* fetchLinesIterator(fileURL, headers) {
 | 
					export async function* fetchLinesIterator(fileURL, headers) {
 | 
				
			||||||
    const utf8Decoder = new TextDecoder('utf-8');
 | 
					    const utf8Decoder = new TextDecoder('utf-8');
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,11 +8,16 @@ import SubscribeSettings from "./SubscribeSettings";
 | 
				
			||||||
import * as React from "react";
 | 
					import * as React from "react";
 | 
				
			||||||
import Box from "@mui/material/Box";
 | 
					import Box from "@mui/material/Box";
 | 
				
			||||||
import {topicShortUrl} from "../app/utils";
 | 
					import {topicShortUrl} from "../app/utils";
 | 
				
			||||||
 | 
					import {useLocation} from "react-router-dom";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ActionBar = (props) => {
 | 
					const ActionBar = (props) => {
 | 
				
			||||||
    const title = (props.selectedSubscription)
 | 
					    const location = useLocation();
 | 
				
			||||||
        ? topicShortUrl(props.selectedSubscription.baseUrl, props.selectedSubscription.topic)
 | 
					    let title = "ntfy";
 | 
				
			||||||
        : "ntfy";
 | 
					    if (props.selectedSubscription) {
 | 
				
			||||||
 | 
					        title = topicShortUrl(props.selectedSubscription.baseUrl, props.selectedSubscription.topic);
 | 
				
			||||||
 | 
					    } else if (location.pathname === "/settings") {
 | 
				
			||||||
 | 
					        title = "Settings";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <AppBar position="fixed" sx={{
 | 
					        <AppBar position="fixed" sx={{
 | 
				
			||||||
            width: '100%',
 | 
					            width: '100%',
 | 
				
			||||||
| 
						 | 
					@ -36,7 +41,7 @@ const ActionBar = (props) => {
 | 
				
			||||||
                <Typography variant="h6" noWrap component="div" sx={{ flexGrow: 1 }}>
 | 
					                <Typography variant="h6" noWrap component="div" sx={{ flexGrow: 1 }}>
 | 
				
			||||||
                    {title}
 | 
					                    {title}
 | 
				
			||||||
                </Typography>
 | 
					                </Typography>
 | 
				
			||||||
                {props.selectedSubscription !== null && <SubscribeSettings
 | 
					                {props.selectedSubscription && <SubscribeSettings
 | 
				
			||||||
                    subscription={props.selectedSubscription}
 | 
					                    subscription={props.selectedSubscription}
 | 
				
			||||||
                    onUnsubscribe={props.onUnsubscribe}
 | 
					                    onUnsubscribe={props.onUnsubscribe}
 | 
				
			||||||
                />}
 | 
					                />}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,6 @@ import CssBaseline from '@mui/material/CssBaseline';
 | 
				
			||||||
import Toolbar from '@mui/material/Toolbar';
 | 
					import Toolbar from '@mui/material/Toolbar';
 | 
				
			||||||
import Notifications from "./Notifications";
 | 
					import Notifications from "./Notifications";
 | 
				
			||||||
import theme from "./theme";
 | 
					import theme from "./theme";
 | 
				
			||||||
import prefs from "../app/Prefs";
 | 
					 | 
				
			||||||
import connectionManager from "../app/ConnectionManager";
 | 
					import connectionManager from "../app/ConnectionManager";
 | 
				
			||||||
import Navigation from "./Navigation";
 | 
					import Navigation from "./Navigation";
 | 
				
			||||||
import ActionBar from "./ActionBar";
 | 
					import ActionBar from "./ActionBar";
 | 
				
			||||||
| 
						 | 
					@ -18,65 +17,64 @@ import poller from "../app/Poller";
 | 
				
			||||||
import pruner from "../app/Pruner";
 | 
					import pruner from "../app/Pruner";
 | 
				
			||||||
import subscriptionManager from "../app/SubscriptionManager";
 | 
					import subscriptionManager from "../app/SubscriptionManager";
 | 
				
			||||||
import userManager from "../app/UserManager";
 | 
					import userManager from "../app/UserManager";
 | 
				
			||||||
 | 
					import {BrowserRouter, Route, Routes, useLocation, useNavigate} from "react-router-dom";
 | 
				
			||||||
 | 
					import {subscriptionRoute} from "../app/utils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO make default server functional
 | 
					// TODO make default server functional
 | 
				
			||||||
// TODO routing
 | 
					 | 
				
			||||||
// TODO embed into ntfy server
 | 
					// TODO embed into ntfy server
 | 
				
			||||||
// TODO new notification indicator
 | 
					// TODO new notification indicator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const App = () => {
 | 
					const App = () => {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <BrowserRouter>
 | 
				
			||||||
 | 
					            <ThemeProvider theme={theme}>
 | 
				
			||||||
 | 
					                <CssBaseline/>
 | 
				
			||||||
 | 
					                <Root/>
 | 
				
			||||||
 | 
					            </ThemeProvider>
 | 
				
			||||||
 | 
					        </BrowserRouter>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Root = () => {
 | 
				
			||||||
    const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
 | 
					    const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
 | 
				
			||||||
    const [prefsOpen, setPrefsOpen] = useState(false);
 | 
					 | 
				
			||||||
    const [selectedSubscription, setSelectedSubscription] = useState(null);
 | 
					 | 
				
			||||||
    const [notificationsGranted, setNotificationsGranted] = useState(notificationManager.granted());
 | 
					    const [notificationsGranted, setNotificationsGranted] = useState(notificationManager.granted());
 | 
				
			||||||
    const subscriptions = useLiveQuery(() => subscriptionManager.all());
 | 
					    const navigate = useNavigate();
 | 
				
			||||||
 | 
					    const location = useLocation();
 | 
				
			||||||
    const users = useLiveQuery(() => userManager.all());
 | 
					    const users = useLiveQuery(() => userManager.all());
 | 
				
			||||||
 | 
					    const subscriptions = useLiveQuery(() => subscriptionManager.all());
 | 
				
			||||||
 | 
					    const [selectedSubscription] = (subscriptions && location) ? subscriptions.filter(s => location.pathname === subscriptionRoute(s)) : [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const handleSubscriptionClick = async (subscriptionId) => {
 | 
					    const handleSubscriptionClick = async (subscriptionId) => {
 | 
				
			||||||
        const subscription = await subscriptionManager.get(subscriptionId);
 | 
					        const subscription = await subscriptionManager.get(subscriptionId);
 | 
				
			||||||
        setSelectedSubscription(subscription);
 | 
					        navigate(subscriptionRoute(subscription));
 | 
				
			||||||
        setPrefsOpen(false);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const handleSubscribeSubmit = async (subscription) => {
 | 
					    const handleSubscribeSubmit = async (subscription) => {
 | 
				
			||||||
        console.log(`[App] New subscription: ${subscription.id}`, subscription);
 | 
					        console.log(`[App] New subscription: ${subscription.id}`, subscription);
 | 
				
			||||||
        setSelectedSubscription(subscription);
 | 
					        navigate(subscriptionRoute(subscription));
 | 
				
			||||||
        handleRequestPermission();
 | 
					        handleRequestPermission();
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    const handleUnsubscribe = async (subscriptionId) => {
 | 
					    const handleUnsubscribe = async (subscriptionId) => {
 | 
				
			||||||
        console.log(`[App] Unsubscribing from ${subscriptionId}`);
 | 
					        console.log(`[App] Unsubscribing from ${subscriptionId}`);
 | 
				
			||||||
        const newSelected = await subscriptionManager.first(); // May be undefined
 | 
					        const newSelected = await subscriptionManager.first(); // May be undefined
 | 
				
			||||||
        setSelectedSubscription(newSelected);
 | 
					        if (newSelected) {
 | 
				
			||||||
 | 
					            navigate(subscriptionRoute(newSelected));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    const handleRequestPermission = () => {
 | 
					    const handleRequestPermission = () => {
 | 
				
			||||||
        notificationManager.maybeRequestPermission(granted => setNotificationsGranted(granted));
 | 
					        notificationManager.maybeRequestPermission(granted => setNotificationsGranted(granted));
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    const handlePrefsClick = () => {
 | 
					 | 
				
			||||||
        setPrefsOpen(true);
 | 
					 | 
				
			||||||
        setSelectedSubscription(null);
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    // Define hooks: Note that the order of the hooks is important. The "loading" hooks
 | 
					    // Define hooks: Note that the order of the hooks is important. The "loading" hooks
 | 
				
			||||||
    // must be before the "saving" hooks.
 | 
					    // must be before the "saving" hooks.
 | 
				
			||||||
    useEffect(() => {
 | 
					    useEffect(() => {
 | 
				
			||||||
        poller.startWorker();
 | 
					        poller.startWorker();
 | 
				
			||||||
        pruner.startWorker();
 | 
					        pruner.startWorker();
 | 
				
			||||||
        const load = async () => {
 | 
					 | 
				
			||||||
            const subs = await subscriptionManager.all();             // FIXME this is broken
 | 
					 | 
				
			||||||
            const selectedSubscriptionId = await prefs.selectedSubscriptionId();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Set selected subscription
 | 
					 | 
				
			||||||
            const maybeSelectedSubscription = subs?.filter(s => s.id = selectedSubscriptionId);
 | 
					 | 
				
			||||||
            if (maybeSelectedSubscription.length > 0) {
 | 
					 | 
				
			||||||
                setSelectedSubscription(maybeSelectedSubscription[0]);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        setTimeout(() => load(), 5000);
 | 
					 | 
				
			||||||
    }, [/* initial render */]);
 | 
					    }, [/* initial render */]);
 | 
				
			||||||
    useEffect(() => {
 | 
					    useEffect(() => {
 | 
				
			||||||
        const handleNotification = async (subscriptionId, notification) => {
 | 
					        const handleNotification = async (subscriptionId, notification) => {
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                const added = await subscriptionManager.addNotification(subscriptionId, notification);
 | 
					                const added = await subscriptionManager.addNotification(subscriptionId, notification);
 | 
				
			||||||
                if (added) {
 | 
					                if (added) {
 | 
				
			||||||
                    const defaultClickAction = (subscription) => setSelectedSubscription(subscription);
 | 
					                    const defaultClickAction = (subscription) => navigate(subscriptionRoute(subscription));
 | 
				
			||||||
                    await notificationManager.notify(subscriptionId, notification, defaultClickAction)
 | 
					                    await notificationManager.notify(subscriptionId, notification, defaultClickAction)
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            } catch (e) {
 | 
					            } catch (e) {
 | 
				
			||||||
| 
						 | 
					@ -90,47 +88,37 @@ const App = () => {
 | 
				
			||||||
            connectionManager.resetNotificationListener();
 | 
					            connectionManager.resetNotificationListener();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }, [/* initial render */]);
 | 
					    }, [/* initial render */]);
 | 
				
			||||||
    useEffect(() => {
 | 
					    useEffect(() => { connectionManager.refresh(subscriptions, users) }, [subscriptions, users]); // Dangle!
 | 
				
			||||||
        connectionManager.refresh(subscriptions, users); // Dangle
 | 
					 | 
				
			||||||
    }, [subscriptions, users]);
 | 
					 | 
				
			||||||
    useEffect(() => {
 | 
					 | 
				
			||||||
        const subscriptionId = (selectedSubscription) ? selectedSubscription.id : "";
 | 
					 | 
				
			||||||
        prefs.setSelectedSubscriptionId(subscriptionId)
 | 
					 | 
				
			||||||
    }, [selectedSubscription]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <ThemeProvider theme={theme}>
 | 
					        <Box sx={{display: 'flex'}}>
 | 
				
			||||||
            <CssBaseline/>
 | 
					            <CssBaseline/>
 | 
				
			||||||
            <Box sx={{display: 'flex'}}>
 | 
					            <ActionBar
 | 
				
			||||||
                <CssBaseline/>
 | 
					                subscriptions={subscriptions}
 | 
				
			||||||
                <ActionBar
 | 
					                selectedSubscription={selectedSubscription}
 | 
				
			||||||
 | 
					                onUnsubscribe={handleUnsubscribe}
 | 
				
			||||||
 | 
					                onMobileDrawerToggle={() => setMobileDrawerOpen(!mobileDrawerOpen)}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					            <Box component="nav" sx={{width: {sm: Navigation.width}, flexShrink: {sm: 0}}}>
 | 
				
			||||||
 | 
					                <Navigation
 | 
				
			||||||
 | 
					                    subscriptions={subscriptions}
 | 
				
			||||||
                    selectedSubscription={selectedSubscription}
 | 
					                    selectedSubscription={selectedSubscription}
 | 
				
			||||||
                    onUnsubscribe={handleUnsubscribe}
 | 
					                    mobileDrawerOpen={mobileDrawerOpen}
 | 
				
			||||||
 | 
					                    notificationsGranted={notificationsGranted}
 | 
				
			||||||
                    onMobileDrawerToggle={() => setMobileDrawerOpen(!mobileDrawerOpen)}
 | 
					                    onMobileDrawerToggle={() => setMobileDrawerOpen(!mobileDrawerOpen)}
 | 
				
			||||||
 | 
					                    onSubscriptionClick={handleSubscriptionClick}
 | 
				
			||||||
 | 
					                    onSubscribeSubmit={handleSubscribeSubmit}
 | 
				
			||||||
 | 
					                    onRequestPermissionClick={handleRequestPermission}
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
                <Box component="nav" sx={{width: {sm: Navigation.width}, flexShrink: {sm: 0}}}>
 | 
					 | 
				
			||||||
                    <Navigation
 | 
					 | 
				
			||||||
                        subscriptions={subscriptions}
 | 
					 | 
				
			||||||
                        selectedSubscription={selectedSubscription}
 | 
					 | 
				
			||||||
                        mobileDrawerOpen={mobileDrawerOpen}
 | 
					 | 
				
			||||||
                        notificationsGranted={notificationsGranted}
 | 
					 | 
				
			||||||
                        prefsOpen={prefsOpen}
 | 
					 | 
				
			||||||
                        onMobileDrawerToggle={() => setMobileDrawerOpen(!mobileDrawerOpen)}
 | 
					 | 
				
			||||||
                        onSubscriptionClick={handleSubscriptionClick}
 | 
					 | 
				
			||||||
                        onSubscribeSubmit={handleSubscribeSubmit}
 | 
					 | 
				
			||||||
                        onPrefsClick={handlePrefsClick}
 | 
					 | 
				
			||||||
                        onRequestPermissionClick={handleRequestPermission}
 | 
					 | 
				
			||||||
                    />
 | 
					 | 
				
			||||||
                </Box>
 | 
					 | 
				
			||||||
                <Main>
 | 
					 | 
				
			||||||
                    <Toolbar/>
 | 
					 | 
				
			||||||
                    <Content
 | 
					 | 
				
			||||||
                        subscription={selectedSubscription}
 | 
					 | 
				
			||||||
                        prefsOpen={prefsOpen}
 | 
					 | 
				
			||||||
                    />
 | 
					 | 
				
			||||||
                </Main>
 | 
					 | 
				
			||||||
            </Box>
 | 
					            </Box>
 | 
				
			||||||
        </ThemeProvider>
 | 
					            <Main>
 | 
				
			||||||
 | 
					                <Toolbar/>
 | 
				
			||||||
 | 
					                <Routes>
 | 
				
			||||||
 | 
					                    <Route path="/" element={<NoTopics />} />
 | 
				
			||||||
 | 
					                    <Route path="settings" element={<Preferences />} />
 | 
				
			||||||
 | 
					                    <Route path=":topic" element={<Notifications subscriptions={subscriptions}/>} />
 | 
				
			||||||
 | 
					                </Routes>
 | 
				
			||||||
 | 
					            </Main>
 | 
				
			||||||
 | 
					        </Box>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -154,14 +142,4 @@ const Main = (props) => {
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Content = (props) => {
 | 
					 | 
				
			||||||
    if (props.prefsOpen) {
 | 
					 | 
				
			||||||
        return <Preferences/>;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (props.subscription) {
 | 
					 | 
				
			||||||
        return <Notifications subscription={props.subscription}/>;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return <NoTopics/>;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default App;
 | 
					export default App;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,8 +14,9 @@ import SubscribeDialog from "./SubscribeDialog";
 | 
				
			||||||
import {Alert, AlertTitle, CircularProgress, ListSubheader} from "@mui/material";
 | 
					import {Alert, AlertTitle, CircularProgress, ListSubheader} from "@mui/material";
 | 
				
			||||||
import Button from "@mui/material/Button";
 | 
					import Button from "@mui/material/Button";
 | 
				
			||||||
import Typography from "@mui/material/Typography";
 | 
					import Typography from "@mui/material/Typography";
 | 
				
			||||||
import {topicShortUrl} from "../app/utils";
 | 
					import {subscriptionRoute, topicShortUrl} from "../app/utils";
 | 
				
			||||||
import {ConnectionState} from "../app/Connection";
 | 
					import {ConnectionState} from "../app/Connection";
 | 
				
			||||||
 | 
					import {useLocation, useNavigate} from "react-router-dom";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const navWidth = 240;
 | 
					const navWidth = 240;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -53,6 +54,8 @@ const Navigation = (props) => {
 | 
				
			||||||
Navigation.width = navWidth;
 | 
					Navigation.width = navWidth;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const NavList = (props) => {
 | 
					const NavList = (props) => {
 | 
				
			||||||
 | 
					    const navigate = useNavigate();
 | 
				
			||||||
 | 
					    const location = useLocation();
 | 
				
			||||||
    const [subscribeDialogKey, setSubscribeDialogKey] = useState(0);
 | 
					    const [subscribeDialogKey, setSubscribeDialogKey] = useState(0);
 | 
				
			||||||
    const [subscribeDialogOpen, setSubscribeDialogOpen] = useState(false);
 | 
					    const [subscribeDialogOpen, setSubscribeDialogOpen] = useState(false);
 | 
				
			||||||
    const handleSubscribeReset = () => {
 | 
					    const handleSubscribeReset = () => {
 | 
				
			||||||
| 
						 | 
					@ -67,39 +70,24 @@ const NavList = (props) => {
 | 
				
			||||||
    const showGrantPermissionsBox = props.subscriptions?.length > 0 && !props.notificationsGranted;
 | 
					    const showGrantPermissionsBox = props.subscriptions?.length > 0 && !props.notificationsGranted;
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <>
 | 
					        <>
 | 
				
			||||||
            <Toolbar sx={{
 | 
					            <Toolbar sx={{ display: { xs: 'none', sm: 'block' } }}/>
 | 
				
			||||||
                display: { xs: 'none', sm: 'block' }
 | 
					            <List component="nav" sx={{ paddingTop: (showGrantPermissionsBox) ? '0' : '' }}>
 | 
				
			||||||
            }}/>
 | 
					 | 
				
			||||||
            <List component="nav" sx={{
 | 
					 | 
				
			||||||
                paddingTop: (showGrantPermissionsBox) ? '0' : ''
 | 
					 | 
				
			||||||
            }}>
 | 
					 | 
				
			||||||
                {showGrantPermissionsBox && <PermissionAlert onRequestPermissionClick={props.onRequestPermissionClick}/>}
 | 
					                {showGrantPermissionsBox && <PermissionAlert onRequestPermissionClick={props.onRequestPermissionClick}/>}
 | 
				
			||||||
                {showSubscriptionsList &&
 | 
					                {showSubscriptionsList &&
 | 
				
			||||||
                    <>
 | 
					                    <>
 | 
				
			||||||
                        <ListSubheader component="div" id="nested-list-subheader">
 | 
					                        <ListSubheader>Subscribed topics</ListSubheader>
 | 
				
			||||||
                            Subscribed topics
 | 
					 | 
				
			||||||
                        </ListSubheader>
 | 
					 | 
				
			||||||
                        <SubscriptionList
 | 
					                        <SubscriptionList
 | 
				
			||||||
                            subscriptions={props.subscriptions}
 | 
					                            subscriptions={props.subscriptions}
 | 
				
			||||||
                            selectedSubscription={props.selectedSubscription}
 | 
					                            selectedSubscription={props.selectedSubscription}
 | 
				
			||||||
                            prefsOpen={props.prefsOpen}
 | 
					 | 
				
			||||||
                            onSubscriptionClick={props.onSubscriptionClick}
 | 
					 | 
				
			||||||
                        />
 | 
					                        />
 | 
				
			||||||
                        <Divider sx={{my: 1}}/>
 | 
					                        <Divider sx={{my: 1}}/>
 | 
				
			||||||
                    </>}
 | 
					                    </>}
 | 
				
			||||||
                <ListItemButton
 | 
					                <ListItemButton onClick={() => navigate("/settings")} selected={location.pathname === "/settings"}>
 | 
				
			||||||
                    onClick={props.onPrefsClick}
 | 
					                    <ListItemIcon><SettingsIcon/></ListItemIcon>
 | 
				
			||||||
                    selected={props.prefsOpen}
 | 
					 | 
				
			||||||
                >
 | 
					 | 
				
			||||||
                    <ListItemIcon>
 | 
					 | 
				
			||||||
                        <SettingsIcon/>
 | 
					 | 
				
			||||||
                    </ListItemIcon>
 | 
					 | 
				
			||||||
                    <ListItemText primary="Settings"/>
 | 
					                    <ListItemText primary="Settings"/>
 | 
				
			||||||
                </ListItemButton>
 | 
					                </ListItemButton>
 | 
				
			||||||
                <ListItemButton onClick={() => setSubscribeDialogOpen(true)}>
 | 
					                <ListItemButton onClick={() => setSubscribeDialogOpen(true)}>
 | 
				
			||||||
                    <ListItemIcon>
 | 
					                    <ListItemIcon><AddIcon/></ListItemIcon>
 | 
				
			||||||
                        <AddIcon/>
 | 
					 | 
				
			||||||
                    </ListItemIcon>
 | 
					 | 
				
			||||||
                    <ListItemText primary="Add subscription"/>
 | 
					                    <ListItemText primary="Add subscription"/>
 | 
				
			||||||
                </ListItemButton>
 | 
					                </ListItemButton>
 | 
				
			||||||
            </List>
 | 
					            </List>
 | 
				
			||||||
| 
						 | 
					@ -121,20 +109,20 @@ const SubscriptionList = (props) => {
 | 
				
			||||||
                <SubscriptionItem
 | 
					                <SubscriptionItem
 | 
				
			||||||
                    key={subscription.id}
 | 
					                    key={subscription.id}
 | 
				
			||||||
                    subscription={subscription}
 | 
					                    subscription={subscription}
 | 
				
			||||||
                    selected={props.selectedSubscription && !props.prefsOpen && props.selectedSubscription.id === subscription.id}
 | 
					                    selected={props.selectedSubscription && props.selectedSubscription.id === subscription.id}
 | 
				
			||||||
                    onClick={() => props.onSubscriptionClick(subscription.id)}
 | 
					 | 
				
			||||||
            />)}
 | 
					            />)}
 | 
				
			||||||
        </>
 | 
					        </>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const SubscriptionItem = (props) => {
 | 
					const SubscriptionItem = (props) => {
 | 
				
			||||||
 | 
					    const navigate = useNavigate();
 | 
				
			||||||
    const subscription = props.subscription;
 | 
					    const subscription = props.subscription;
 | 
				
			||||||
    const icon = (subscription.state === ConnectionState.Connecting)
 | 
					    const icon = (subscription.state === ConnectionState.Connecting)
 | 
				
			||||||
        ? <CircularProgress size="24px"/>
 | 
					        ? <CircularProgress size="24px"/>
 | 
				
			||||||
        : <ChatBubbleOutlineIcon/>;
 | 
					        : <ChatBubbleOutlineIcon/>;
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <ListItemButton onClick={props.onClick} selected={props.selected}>
 | 
					        <ListItemButton onClick={() => navigate(subscriptionRoute(subscription))} selected={props.selected}>
 | 
				
			||||||
            <ListItemIcon>{icon}</ListItemIcon>
 | 
					            <ListItemIcon>{icon}</ListItemIcon>
 | 
				
			||||||
            <ListItemText primary={topicShortUrl(subscription.baseUrl, subscription.topic)}/>
 | 
					            <ListItemText primary={topicShortUrl(subscription.baseUrl, subscription.topic)}/>
 | 
				
			||||||
        </ListItemButton>
 | 
					        </ListItemButton>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,12 +20,23 @@ import {useLiveQuery} from "dexie-react-hooks";
 | 
				
			||||||
import Box from "@mui/material/Box";
 | 
					import Box from "@mui/material/Box";
 | 
				
			||||||
import Button from "@mui/material/Button";
 | 
					import Button from "@mui/material/Button";
 | 
				
			||||||
import subscriptionManager from "../app/SubscriptionManager";
 | 
					import subscriptionManager from "../app/SubscriptionManager";
 | 
				
			||||||
 | 
					import { useParams } from "react-router-dom";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Notifications = (props) => {
 | 
					const Notifications = (props) => {
 | 
				
			||||||
 | 
					    const params = useParams();
 | 
				
			||||||
 | 
					    if (!props.subscriptions) {
 | 
				
			||||||
 | 
					        return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const [subscription] = props.subscriptions.filter(s => s.topic === params.topic);
 | 
				
			||||||
 | 
					    if (!subscription) {
 | 
				
			||||||
 | 
					        return null; // FIXME
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return <NotificationList subscription={subscription}/>;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NotificationList = (props) => {
 | 
				
			||||||
    const subscription = props.subscription;
 | 
					    const subscription = props.subscription;
 | 
				
			||||||
    const notifications = useLiveQuery(() => {
 | 
					    const notifications = useLiveQuery(() => subscriptionManager.getNotifications(subscription.id), [subscription]);
 | 
				
			||||||
        return subscriptionManager.getNotifications(subscription.id);
 | 
					 | 
				
			||||||
    }, [subscription]);
 | 
					 | 
				
			||||||
    if (!notifications || notifications.length === 0) {
 | 
					    if (!notifications || notifications.length === 0) {
 | 
				
			||||||
        return <NothingHereYet subscription={subscription}/>;
 | 
					        return <NothingHereYet subscription={subscription}/>;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,6 @@ import {
 | 
				
			||||||
    useMediaQuery
 | 
					    useMediaQuery
 | 
				
			||||||
} from "@mui/material";
 | 
					} from "@mui/material";
 | 
				
			||||||
import Typography from "@mui/material/Typography";
 | 
					import Typography from "@mui/material/Typography";
 | 
				
			||||||
import Paper from "@mui/material/Paper";
 | 
					 | 
				
			||||||
import prefs from "../app/Prefs";
 | 
					import prefs from "../app/Prefs";
 | 
				
			||||||
import {Paragraph} from "./styles";
 | 
					import {Paragraph} from "./styles";
 | 
				
			||||||
import EditIcon from '@mui/icons-material/Edit';
 | 
					import EditIcon from '@mui/icons-material/Edit';
 | 
				
			||||||
| 
						 | 
					@ -33,7 +32,7 @@ import DialogContent from "@mui/material/DialogContent";
 | 
				
			||||||
import DialogActions from "@mui/material/DialogActions";
 | 
					import DialogActions from "@mui/material/DialogActions";
 | 
				
			||||||
import userManager from "../app/UserManager";
 | 
					import userManager from "../app/UserManager";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Preferences = (props) => {
 | 
					const Preferences = () => {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <Container maxWidth="md" sx={{marginTop: 3, marginBottom: 3}}>
 | 
					        <Container maxWidth="md" sx={{marginTop: 3, marginBottom: 3}}>
 | 
				
			||||||
            <Stack spacing={3}>
 | 
					            <Stack spacing={3}>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue