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",
 | 
			
		||||
        "react": "latest",
 | 
			
		||||
        "react-dom": "latest",
 | 
			
		||||
        "react-router-dom": "^6.2.2",
 | 
			
		||||
        "react-scripts": "^3.0.1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -8333,6 +8334,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",
 | 
			
		||||
| 
						 | 
				
			
			@ -13693,6 +13702,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.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": {
 | 
			
		||||
      "version": "3.4.4",
 | 
			
		||||
      "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",
 | 
			
		||||
      "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",
 | 
			
		||||
| 
						 | 
				
			
			@ -28321,6 +28362,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.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": {
 | 
			
		||||
      "version": "3.4.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-3.4.4.tgz",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,6 +16,7 @@
 | 
			
		|||
    "dexie-react-hooks": "^1.1.1",
 | 
			
		||||
    "react": "latest",
 | 
			
		||||
    "react-dom": "latest",
 | 
			
		||||
    "react-router-dom": "^6.2.2",
 | 
			
		||||
    "react-scripts": "^3.0.1"
 | 
			
		||||
  },
 | 
			
		||||
  "browserslist": {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,6 @@ import {sha256} from "./utils";
 | 
			
		|||
 | 
			
		||||
class ConnectionManager {
 | 
			
		||||
    constructor() {
 | 
			
		||||
        console.log(`connection manager`)
 | 
			
		||||
        this.connections = new Map(); // ConnectionId -> Connection (hash, see below)
 | 
			
		||||
        this.stateListener = null; // Fired when connection state changes
 | 
			
		||||
        this.notificationListener = null; // Fired when new notifications arrive
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -114,6 +114,10 @@ export const openUrl = (url) => {
 | 
			
		|||
    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
 | 
			
		||||
export async function* fetchLinesIterator(fileURL, headers) {
 | 
			
		||||
    const utf8Decoder = new TextDecoder('utf-8');
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,11 +8,16 @@ import SubscribeSettings from "./SubscribeSettings";
 | 
			
		|||
import * as React from "react";
 | 
			
		||||
import Box from "@mui/material/Box";
 | 
			
		||||
import {topicShortUrl} from "../app/utils";
 | 
			
		||||
import {useLocation} from "react-router-dom";
 | 
			
		||||
 | 
			
		||||
const ActionBar = (props) => {
 | 
			
		||||
    const title = (props.selectedSubscription)
 | 
			
		||||
        ? topicShortUrl(props.selectedSubscription.baseUrl, props.selectedSubscription.topic)
 | 
			
		||||
        : "ntfy";
 | 
			
		||||
    const location = useLocation();
 | 
			
		||||
    let title = "ntfy";
 | 
			
		||||
    if (props.selectedSubscription) {
 | 
			
		||||
        title = topicShortUrl(props.selectedSubscription.baseUrl, props.selectedSubscription.topic);
 | 
			
		||||
    } else if (location.pathname === "/settings") {
 | 
			
		||||
        title = "Settings";
 | 
			
		||||
    }
 | 
			
		||||
    return (
 | 
			
		||||
        <AppBar position="fixed" sx={{
 | 
			
		||||
            width: '100%',
 | 
			
		||||
| 
						 | 
				
			
			@ -36,7 +41,7 @@ const ActionBar = (props) => {
 | 
			
		|||
                <Typography variant="h6" noWrap component="div" sx={{ flexGrow: 1 }}>
 | 
			
		||||
                    {title}
 | 
			
		||||
                </Typography>
 | 
			
		||||
                {props.selectedSubscription !== null && <SubscribeSettings
 | 
			
		||||
                {props.selectedSubscription && <SubscribeSettings
 | 
			
		||||
                    subscription={props.selectedSubscription}
 | 
			
		||||
                    onUnsubscribe={props.onUnsubscribe}
 | 
			
		||||
                />}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,6 @@ import CssBaseline from '@mui/material/CssBaseline';
 | 
			
		|||
import Toolbar from '@mui/material/Toolbar';
 | 
			
		||||
import Notifications from "./Notifications";
 | 
			
		||||
import theme from "./theme";
 | 
			
		||||
import prefs from "../app/Prefs";
 | 
			
		||||
import connectionManager from "../app/ConnectionManager";
 | 
			
		||||
import Navigation from "./Navigation";
 | 
			
		||||
import ActionBar from "./ActionBar";
 | 
			
		||||
| 
						 | 
				
			
			@ -18,65 +17,64 @@ 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";
 | 
			
		||||
 | 
			
		||||
// TODO make default server functional
 | 
			
		||||
// TODO routing
 | 
			
		||||
// TODO embed into ntfy server
 | 
			
		||||
// TODO new notification indicator
 | 
			
		||||
 | 
			
		||||
const App = () => {
 | 
			
		||||
    return (
 | 
			
		||||
        <BrowserRouter>
 | 
			
		||||
            <ThemeProvider theme={theme}>
 | 
			
		||||
                <CssBaseline/>
 | 
			
		||||
                <Root/>
 | 
			
		||||
            </ThemeProvider>
 | 
			
		||||
        </BrowserRouter>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const Root = () => {
 | 
			
		||||
    const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
 | 
			
		||||
    const [prefsOpen, setPrefsOpen] = useState(false);
 | 
			
		||||
    const [selectedSubscription, setSelectedSubscription] = useState(null);
 | 
			
		||||
    const [notificationsGranted, setNotificationsGranted] = useState(notificationManager.granted());
 | 
			
		||||
    const subscriptions = useLiveQuery(() => subscriptionManager.all());
 | 
			
		||||
    const navigate = useNavigate();
 | 
			
		||||
    const location = useLocation();
 | 
			
		||||
    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 subscription = await subscriptionManager.get(subscriptionId);
 | 
			
		||||
        setSelectedSubscription(subscription);
 | 
			
		||||
        setPrefsOpen(false);
 | 
			
		||||
        navigate(subscriptionRoute(subscription));
 | 
			
		||||
    }
 | 
			
		||||
    const handleSubscribeSubmit = async (subscription) => {
 | 
			
		||||
        console.log(`[App] New subscription: ${subscription.id}`, subscription);
 | 
			
		||||
        setSelectedSubscription(subscription);
 | 
			
		||||
        navigate(subscriptionRoute(subscription));
 | 
			
		||||
        handleRequestPermission();
 | 
			
		||||
    };
 | 
			
		||||
    const handleUnsubscribe = async (subscriptionId) => {
 | 
			
		||||
        console.log(`[App] Unsubscribing from ${subscriptionId}`);
 | 
			
		||||
        const newSelected = await subscriptionManager.first(); // May be undefined
 | 
			
		||||
        setSelectedSubscription(newSelected);
 | 
			
		||||
        if (newSelected) {
 | 
			
		||||
            navigate(subscriptionRoute(newSelected));
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    const handleRequestPermission = () => {
 | 
			
		||||
        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
 | 
			
		||||
    // must be before the "saving" hooks.
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        poller.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 */]);
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        const handleNotification = async (subscriptionId, notification) => {
 | 
			
		||||
            try {
 | 
			
		||||
                const added = await subscriptionManager.addNotification(subscriptionId, notification);
 | 
			
		||||
                if (added) {
 | 
			
		||||
                    const defaultClickAction = (subscription) => setSelectedSubscription(subscription);
 | 
			
		||||
                    const defaultClickAction = (subscription) => navigate(subscriptionRoute(subscription));
 | 
			
		||||
                    await notificationManager.notify(subscriptionId, notification, defaultClickAction)
 | 
			
		||||
                }
 | 
			
		||||
            } catch (e) {
 | 
			
		||||
| 
						 | 
				
			
			@ -90,20 +88,12 @@ const App = () => {
 | 
			
		|||
            connectionManager.resetNotificationListener();
 | 
			
		||||
        }
 | 
			
		||||
    }, [/* initial render */]);
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        connectionManager.refresh(subscriptions, users); // Dangle
 | 
			
		||||
    }, [subscriptions, users]);
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        const subscriptionId = (selectedSubscription) ? selectedSubscription.id : "";
 | 
			
		||||
        prefs.setSelectedSubscriptionId(subscriptionId)
 | 
			
		||||
    }, [selectedSubscription]);
 | 
			
		||||
 | 
			
		||||
    useEffect(() => { connectionManager.refresh(subscriptions, users) }, [subscriptions, users]); // Dangle!
 | 
			
		||||
    return (
 | 
			
		||||
        <ThemeProvider theme={theme}>
 | 
			
		||||
            <CssBaseline/>
 | 
			
		||||
        <Box sx={{display: 'flex'}}>
 | 
			
		||||
            <CssBaseline/>
 | 
			
		||||
            <ActionBar
 | 
			
		||||
                subscriptions={subscriptions}
 | 
			
		||||
                selectedSubscription={selectedSubscription}
 | 
			
		||||
                onUnsubscribe={handleUnsubscribe}
 | 
			
		||||
                onMobileDrawerToggle={() => setMobileDrawerOpen(!mobileDrawerOpen)}
 | 
			
		||||
| 
						 | 
				
			
			@ -114,23 +104,21 @@ const App = () => {
 | 
			
		|||
                    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}
 | 
			
		||||
                    />
 | 
			
		||||
                <Routes>
 | 
			
		||||
                    <Route path="/" element={<NoTopics />} />
 | 
			
		||||
                    <Route path="settings" element={<Preferences />} />
 | 
			
		||||
                    <Route path=":topic" element={<Notifications subscriptions={subscriptions}/>} />
 | 
			
		||||
                </Routes>
 | 
			
		||||
            </Main>
 | 
			
		||||
        </Box>
 | 
			
		||||
        </ThemeProvider>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,8 +14,9 @@ import SubscribeDialog from "./SubscribeDialog";
 | 
			
		|||
import {Alert, AlertTitle, CircularProgress, ListSubheader} from "@mui/material";
 | 
			
		||||
import Button from "@mui/material/Button";
 | 
			
		||||
import Typography from "@mui/material/Typography";
 | 
			
		||||
import {topicShortUrl} from "../app/utils";
 | 
			
		||||
import {subscriptionRoute, topicShortUrl} from "../app/utils";
 | 
			
		||||
import {ConnectionState} from "../app/Connection";
 | 
			
		||||
import {useLocation, useNavigate} from "react-router-dom";
 | 
			
		||||
 | 
			
		||||
const navWidth = 240;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -53,6 +54,8 @@ const Navigation = (props) => {
 | 
			
		|||
Navigation.width = navWidth;
 | 
			
		||||
 | 
			
		||||
const NavList = (props) => {
 | 
			
		||||
    const navigate = useNavigate();
 | 
			
		||||
    const location = useLocation();
 | 
			
		||||
    const [subscribeDialogKey, setSubscribeDialogKey] = useState(0);
 | 
			
		||||
    const [subscribeDialogOpen, setSubscribeDialogOpen] = useState(false);
 | 
			
		||||
    const handleSubscribeReset = () => {
 | 
			
		||||
| 
						 | 
				
			
			@ -67,39 +70,24 @@ const NavList = (props) => {
 | 
			
		|||
    const showGrantPermissionsBox = props.subscriptions?.length > 0 && !props.notificationsGranted;
 | 
			
		||||
    return (
 | 
			
		||||
        <>
 | 
			
		||||
            <Toolbar sx={{
 | 
			
		||||
                display: { xs: 'none', sm: 'block' }
 | 
			
		||||
            }}/>
 | 
			
		||||
            <List component="nav" sx={{
 | 
			
		||||
                paddingTop: (showGrantPermissionsBox) ? '0' : ''
 | 
			
		||||
            }}>
 | 
			
		||||
            <Toolbar sx={{ display: { xs: 'none', sm: 'block' } }}/>
 | 
			
		||||
            <List component="nav" sx={{ paddingTop: (showGrantPermissionsBox) ? '0' : '' }}>
 | 
			
		||||
                {showGrantPermissionsBox && <PermissionAlert onRequestPermissionClick={props.onRequestPermissionClick}/>}
 | 
			
		||||
                {showSubscriptionsList &&
 | 
			
		||||
                    <>
 | 
			
		||||
                        <ListSubheader component="div" id="nested-list-subheader">
 | 
			
		||||
                            Subscribed topics
 | 
			
		||||
                        </ListSubheader>
 | 
			
		||||
                        <ListSubheader>Subscribed topics</ListSubheader>
 | 
			
		||||
                        <SubscriptionList
 | 
			
		||||
                            subscriptions={props.subscriptions}
 | 
			
		||||
                            selectedSubscription={props.selectedSubscription}
 | 
			
		||||
                            prefsOpen={props.prefsOpen}
 | 
			
		||||
                            onSubscriptionClick={props.onSubscriptionClick}
 | 
			
		||||
                        />
 | 
			
		||||
                        <Divider sx={{my: 1}}/>
 | 
			
		||||
                    </>}
 | 
			
		||||
                <ListItemButton
 | 
			
		||||
                    onClick={props.onPrefsClick}
 | 
			
		||||
                    selected={props.prefsOpen}
 | 
			
		||||
                >
 | 
			
		||||
                    <ListItemIcon>
 | 
			
		||||
                        <SettingsIcon/>
 | 
			
		||||
                    </ListItemIcon>
 | 
			
		||||
                <ListItemButton onClick={() => navigate("/settings")} selected={location.pathname === "/settings"}>
 | 
			
		||||
                    <ListItemIcon><SettingsIcon/></ListItemIcon>
 | 
			
		||||
                    <ListItemText primary="Settings"/>
 | 
			
		||||
                </ListItemButton>
 | 
			
		||||
                <ListItemButton onClick={() => setSubscribeDialogOpen(true)}>
 | 
			
		||||
                    <ListItemIcon>
 | 
			
		||||
                        <AddIcon/>
 | 
			
		||||
                    </ListItemIcon>
 | 
			
		||||
                    <ListItemIcon><AddIcon/></ListItemIcon>
 | 
			
		||||
                    <ListItemText primary="Add subscription"/>
 | 
			
		||||
                </ListItemButton>
 | 
			
		||||
            </List>
 | 
			
		||||
| 
						 | 
				
			
			@ -121,20 +109,20 @@ const SubscriptionList = (props) => {
 | 
			
		|||
                <SubscriptionItem
 | 
			
		||||
                    key={subscription.id}
 | 
			
		||||
                    subscription={subscription}
 | 
			
		||||
                    selected={props.selectedSubscription && !props.prefsOpen && props.selectedSubscription.id === subscription.id}
 | 
			
		||||
                    onClick={() => props.onSubscriptionClick(subscription.id)}
 | 
			
		||||
                    selected={props.selectedSubscription && props.selectedSubscription.id === subscription.id}
 | 
			
		||||
            />)}
 | 
			
		||||
        </>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const SubscriptionItem = (props) => {
 | 
			
		||||
    const navigate = useNavigate();
 | 
			
		||||
    const subscription = props.subscription;
 | 
			
		||||
    const icon = (subscription.state === ConnectionState.Connecting)
 | 
			
		||||
        ? <CircularProgress size="24px"/>
 | 
			
		||||
        : <ChatBubbleOutlineIcon/>;
 | 
			
		||||
    return (
 | 
			
		||||
        <ListItemButton onClick={props.onClick} selected={props.selected}>
 | 
			
		||||
        <ListItemButton onClick={() => navigate(subscriptionRoute(subscription))} selected={props.selected}>
 | 
			
		||||
            <ListItemIcon>{icon}</ListItemIcon>
 | 
			
		||||
            <ListItemText primary={topicShortUrl(subscription.baseUrl, subscription.topic)}/>
 | 
			
		||||
        </ListItemButton>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,12 +20,23 @@ import {useLiveQuery} from "dexie-react-hooks";
 | 
			
		|||
import Box from "@mui/material/Box";
 | 
			
		||||
import Button from "@mui/material/Button";
 | 
			
		||||
import subscriptionManager from "../app/SubscriptionManager";
 | 
			
		||||
import { useParams } from "react-router-dom";
 | 
			
		||||
 | 
			
		||||
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 notifications = useLiveQuery(() => {
 | 
			
		||||
        return subscriptionManager.getNotifications(subscription.id);
 | 
			
		||||
    }, [subscription]);
 | 
			
		||||
    const notifications = useLiveQuery(() => subscriptionManager.getNotifications(subscription.id), [subscription]);
 | 
			
		||||
    if (!notifications || notifications.length === 0) {
 | 
			
		||||
        return <NothingHereYet subscription={subscription}/>;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,7 +14,6 @@ import {
 | 
			
		|||
    useMediaQuery
 | 
			
		||||
} from "@mui/material";
 | 
			
		||||
import Typography from "@mui/material/Typography";
 | 
			
		||||
import Paper from "@mui/material/Paper";
 | 
			
		||||
import prefs from "../app/Prefs";
 | 
			
		||||
import {Paragraph} from "./styles";
 | 
			
		||||
import EditIcon from '@mui/icons-material/Edit';
 | 
			
		||||
| 
						 | 
				
			
			@ -33,7 +32,7 @@ import DialogContent from "@mui/material/DialogContent";
 | 
			
		|||
import DialogActions from "@mui/material/DialogActions";
 | 
			
		||||
import userManager from "../app/UserManager";
 | 
			
		||||
 | 
			
		||||
const Preferences = (props) => {
 | 
			
		||||
const Preferences = () => {
 | 
			
		||||
    return (
 | 
			
		||||
        <Container maxWidth="md" sx={{marginTop: 3, marginBottom: 3}}>
 | 
			
		||||
            <Stack spacing={3}>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue