import * as React from 'react'; import {useEffect, useState} from 'react'; 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 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"; import notificationManager from "../app/NotificationManager"; import NoTopics from "./NoTopics"; 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"; // TODO make default server functional // TODO routing // TODO embed into ntfy server // TODO new notification indicator const App = () => { 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 users = useLiveQuery(() => userManager.all()); const handleSubscriptionClick = async (subscriptionId) => { const subscription = await subscriptionManager.get(subscriptionId); setSelectedSubscription(subscription); setPrefsOpen(false); } const handleSubscribeSubmit = async (subscription) => { console.log(`[App] New subscription: ${subscription.id}`, subscription); setSelectedSubscription(subscription); handleRequestPermission(); }; const handleUnsubscribe = async (subscriptionId) => { console.log(`[App] Unsubscribing from ${subscriptionId}`); const newSelected = await subscriptionManager.first(); // May be undefined setSelectedSubscription(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); await notificationManager.notify(subscriptionId, notification, defaultClickAction) } } catch (e) { console.error(`[App] Error handling notification`, e); } }; connectionManager.registerStateListener(subscriptionManager.updateState); connectionManager.registerNotificationListener(handleNotification); return () => { connectionManager.resetStateListener(); connectionManager.resetNotificationListener(); } }, [/* initial render */]); useEffect(() => { connectionManager.refresh(subscriptions, users); // Dangle }, [subscriptions, users]); useEffect(() => { const subscriptionId = (selectedSubscription) ? selectedSubscription.id : ""; prefs.setSelectedSubscriptionId(subscriptionId) }, [selectedSubscription]); return ( <ThemeProvider theme={theme}> <CssBaseline/> <Box sx={{display: 'flex'}}> <CssBaseline/> <ActionBar selectedSubscription={selectedSubscription} onUnsubscribe={handleUnsubscribe} onMobileDrawerToggle={() => setMobileDrawerOpen(!mobileDrawerOpen)} /> <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> </ThemeProvider> ); } const Main = (props) => { return ( <Box component="main" sx={{ display: 'flex', flexGrow: 1, flexDirection: 'column', padding: 3, width: {sm: `calc(100% - ${Navigation.width}px)`}, height: '100vh', overflow: 'auto', backgroundColor: (theme) => theme.palette.mode === 'light' ? theme.palette.grey[100] : theme.palette.grey[900] }} > {props.children} </Box> ); }; const Content = (props) => { if (props.prefsOpen) { return <Preferences/>; } if (props.subscription) { return <Notifications subscription={props.subscription}/>; } return <NoTopics/>; }; export default App;