import "./Layout.css"; import { useEffect, useMemo, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; import { Outlet, useLocation, useNavigate } from "react-router-dom"; import Envelope from "Icons/Envelope"; import Bell from "Icons/Bell"; import Search from "Icons/Search"; import { RootState } from "State/Store"; import { init, setRelays } from "State/Login"; import { System } from "Nostr/System"; import ProfileImage from "Element/ProfileImage"; import useLoginFeed from "Feed/LoginFeed"; import { totalUnread } from "Pages/MessagesPage"; import { SearchRelays, SnortPubKey } from "Const"; import useEventPublisher from "Feed/EventPublisher"; import useModeration from "Hooks/useModeration"; import { IndexedUDB, useDb } from "State/Users/Db"; import { db } from "Db"; import { bech32ToHex } from "Util"; import { NoteCreator } from "Element/NoteCreator"; import Plus from "Icons/Plus"; import { RelaySettings } from "Nostr/Connection"; import { FormattedMessage } from "react-intl"; import messages from './messages' export default function Layout() { const location = useLocation(); const [show, setShow] = useState(false); const dispatch = useDispatch(); const navigate = useNavigate(); const { loggedOut, publicKey, relays, latestNotification, readNotifications, dms, preferences, newUserKey, } = useSelector((s: RootState) => s.login); const { isMuted } = useModeration(); const usingDb = useDb(); const pub = useEventPublisher(); useLoginFeed(); const shouldHideNoteCreator = useMemo(() => { const hideNoteCreator = ["/settings", "/messages", "/new"]; return hideNoteCreator.some((a) => location.pathname.startsWith(a)); }, [location]); const hasNotifications = useMemo( () => latestNotification > readNotifications, [latestNotification, readNotifications] ); const unreadDms = useMemo( () => publicKey ? totalUnread( dms.filter((a) => !isMuted(a.pubkey)), publicKey ) : 0, [dms, publicKey] ); useEffect(() => { System.nip42Auth = pub.nip42Auth; }, [pub]); useEffect(() => { System.UserDb = usingDb; }, [usingDb]); useEffect(() => { if (relays) { for (const [k, v] of Object.entries(relays)) { System.ConnectToRelay(k, v); } for (const [k] of System.Sockets) { if (!relays[k] && !SearchRelays.has(k)) { System.DisconnectRelay(k); } } } }, [relays]); function setTheme(theme: "light" | "dark") { const elm = document.documentElement; if (theme === "light" && !elm.classList.contains("light")) { elm.classList.add("light"); } else if (theme === "dark" && elm.classList.contains("light")) { elm.classList.remove("light"); } } useEffect(() => { const osTheme = window.matchMedia("(prefers-color-scheme: light)"); setTheme( preferences.theme === "system" && osTheme.matches ? "light" : preferences.theme === "light" ? "light" : "dark" ); osTheme.onchange = (e) => { if (preferences.theme === "system") { setTheme(e.matches ? "light" : "dark"); } }; return () => { osTheme.onchange = null; }; }, [preferences.theme]); useEffect(() => { // check DB support then init IndexedUDB.isAvailable().then(async (a) => { const dbType = a ? "indexdDb" : "redux"; // cleanup on load if (dbType === "indexdDb") { await db.feeds.clear(); const now = Math.floor(new Date().getTime() / 1000); const cleanupEvents = await db.events .where("created_at") .above(now - 60 * 60) .primaryKeys(); console.debug(`Cleanup ${cleanupEvents.length} events`); await db.events.bulkDelete(cleanupEvents); } console.debug(`Using db: ${dbType}`); dispatch(init(dbType)); }); }, []); async function handleNewUser() { let newRelays: Record = {}; try { const rsp = await fetch("https://api.nostr.watch/v1/online"); if (rsp.ok) { const online: string[] = await rsp.json(); const pickRandom = online .sort(() => (Math.random() >= 0.5 ? 1 : -1)) .slice(0, 4); // pick 4 random relays const relayObjects = pickRandom.map((a) => [ a, { read: true, write: true }, ]); newRelays = Object.fromEntries(relayObjects); dispatch( setRelays({ relays: newRelays, createdAt: 1, }) ); } } catch (e) { console.warn(e); } const ev = await pub.addFollow(bech32ToHex(SnortPubKey), newRelays); pub.broadcast(ev); } useEffect(() => { if (newUserKey === true) { handleNewUser().catch(console.warn); } }, [newUserKey]); async function goToNotifications(e: React.MouseEvent) { e.stopPropagation(); // request permissions to send notifications if ("Notification" in window) { try { if (Notification.permission !== "granted") { const res = await Notification.requestPermission(); console.debug(res); } } catch (e) { console.error(e); } } navigate("/notifications"); } function accountHeader() { return (
navigate("/search")}>
navigate("/messages")}> {unreadDms > 0 && }
{hasNotifications && }
); } if (typeof loggedOut !== "boolean") { return null; } return (
navigate("/")}> Snort
{publicKey ? ( accountHeader() ) : ( )}
{!shouldHideNoteCreator && ( <> )}
); }