2022-12-29 15:36:40 +00:00
|
|
|
import "./Layout.css";
|
2023-02-07 20:04:50 +00:00
|
|
|
import { useEffect, useMemo, useState } from "react";
|
2022-12-27 23:46:13 +00:00
|
|
|
import { useDispatch, useSelector } from "react-redux";
|
2023-02-06 22:29:17 +00:00
|
|
|
import { Outlet, useLocation, useNavigate } from "react-router-dom";
|
2023-03-28 14:34:01 +00:00
|
|
|
import { FormattedMessage } from "react-intl";
|
|
|
|
|
|
|
|
import messages from "./messages";
|
2023-02-10 19:23:52 +00:00
|
|
|
|
2023-03-02 17:47:02 +00:00
|
|
|
import Icon from "Icons/Icon";
|
2023-01-20 11:11:50 +00:00
|
|
|
import { RootState } from "State/Store";
|
2023-04-08 12:48:57 +00:00
|
|
|
import { setShow, reset } from "State/NoteCreator";
|
2023-02-20 23:14:15 +00:00
|
|
|
import { System } from "System";
|
2023-01-20 11:11:50 +00:00
|
|
|
import useLoginFeed from "Feed/LoginFeed";
|
|
|
|
import { totalUnread } from "Pages/MessagesPage";
|
2023-01-28 21:43:56 +00:00
|
|
|
import useModeration from "Hooks/useModeration";
|
2023-02-05 12:32:34 +00:00
|
|
|
import { NoteCreator } from "Element/NoteCreator";
|
2023-03-03 14:30:31 +00:00
|
|
|
import { db } from "Db";
|
2023-03-28 14:34:01 +00:00
|
|
|
import useEventPublisher from "Feed/EventPublisher";
|
|
|
|
import SubDebug from "Element/SubDebug";
|
2023-03-29 12:10:22 +00:00
|
|
|
import { preload } from "Cache";
|
|
|
|
import { useDmCache } from "Hooks/useDmsCache";
|
2023-04-13 18:43:43 +00:00
|
|
|
import { mapPlanName } from "./subscribe";
|
2023-04-14 11:33:19 +00:00
|
|
|
import useLogin from "Hooks/useLogin";
|
2023-04-14 12:08:42 +00:00
|
|
|
import Avatar from "Element/Avatar";
|
|
|
|
import { useUserProfile } from "Hooks/useUserProfile";
|
|
|
|
import { profileLink } from "Util";
|
2023-01-28 21:43:56 +00:00
|
|
|
|
2023-01-16 18:14:08 +00:00
|
|
|
export default function Layout() {
|
2023-02-07 20:04:50 +00:00
|
|
|
const location = useLocation();
|
2023-04-08 12:48:57 +00:00
|
|
|
const replyTo = useSelector((s: RootState) => s.noteCreator.replyTo);
|
|
|
|
const isNoteCreatorShowing = useSelector((s: RootState) => s.noteCreator.show);
|
|
|
|
const isReplyNoteCreatorShowing = replyTo && isNoteCreatorShowing;
|
2023-02-07 20:04:50 +00:00
|
|
|
const dispatch = useDispatch();
|
|
|
|
const navigate = useNavigate();
|
2023-04-14 11:33:19 +00:00
|
|
|
const { publicKey, relays, preferences, currentSubscription } = useLogin();
|
2023-02-09 18:05:45 +00:00
|
|
|
const [pageClass, setPageClass] = useState("page");
|
2023-02-07 20:04:50 +00:00
|
|
|
const pub = useEventPublisher();
|
|
|
|
useLoginFeed();
|
|
|
|
|
2023-04-08 12:48:57 +00:00
|
|
|
const handleNoteCreatorButtonClick = () => {
|
|
|
|
if (replyTo) {
|
|
|
|
dispatch(reset());
|
|
|
|
}
|
|
|
|
dispatch(setShow(true));
|
|
|
|
};
|
|
|
|
|
2023-02-07 20:04:50 +00:00
|
|
|
const shouldHideNoteCreator = useMemo(() => {
|
2023-04-13 18:43:43 +00:00
|
|
|
const hideOn = ["/settings", "/messages", "/new", "/login", "/donate", "/p/", "/e", "/subscribe"];
|
2023-04-08 12:48:57 +00:00
|
|
|
return isReplyNoteCreatorShowing || hideOn.some(a => location.pathname.startsWith(a));
|
|
|
|
}, [location, isReplyNoteCreatorShowing]);
|
2023-02-09 18:05:45 +00:00
|
|
|
|
|
|
|
const shouldHideHeader = useMemo(() => {
|
2023-02-12 12:31:48 +00:00
|
|
|
const hideOn = ["/login", "/new"];
|
2023-02-09 18:05:45 +00:00
|
|
|
return hideOn.some(a => location.pathname.startsWith(a));
|
|
|
|
}, [location]);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (location.pathname.startsWith("/login")) {
|
|
|
|
setPageClass("");
|
|
|
|
} else {
|
|
|
|
setPageClass("page");
|
|
|
|
}
|
2023-02-07 20:04:50 +00:00
|
|
|
}, [location]);
|
|
|
|
|
|
|
|
useEffect(() => {
|
2023-04-14 15:02:15 +00:00
|
|
|
if (pub) {
|
|
|
|
System.HandleAuth = pub.nip42Auth;
|
|
|
|
}
|
2023-02-07 20:04:50 +00:00
|
|
|
}, [pub]);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (relays) {
|
2023-03-28 14:34:01 +00:00
|
|
|
(async () => {
|
2023-04-14 11:33:19 +00:00
|
|
|
for (const [k, v] of Object.entries(relays.item)) {
|
2023-03-28 14:34:01 +00:00
|
|
|
await System.ConnectToRelay(k, v);
|
2023-01-20 17:07:14 +00:00
|
|
|
}
|
2023-03-28 14:34:01 +00:00
|
|
|
for (const [k, c] of System.Sockets) {
|
2023-04-14 11:33:19 +00:00
|
|
|
if (!relays.item[k] && !c.Ephemeral) {
|
2023-03-28 14:34:01 +00:00
|
|
|
System.DisconnectRelay(k);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})();
|
2023-02-07 20:04:50 +00:00
|
|
|
}
|
|
|
|
}, [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(() => {
|
2023-02-07 19:47:57 +00:00
|
|
|
const osTheme = window.matchMedia("(prefers-color-scheme: light)");
|
2023-02-07 20:04:50 +00:00
|
|
|
setTheme(
|
2023-02-09 12:26:54 +00:00
|
|
|
preferences.theme === "system" && osTheme.matches ? "light" : preferences.theme === "light" ? "light" : "dark"
|
2023-02-07 20:04:50 +00:00
|
|
|
);
|
|
|
|
|
2023-02-09 12:26:54 +00:00
|
|
|
osTheme.onchange = e => {
|
2023-02-07 20:04:50 +00:00
|
|
|
if (preferences.theme === "system") {
|
|
|
|
setTheme(e.matches ? "light" : "dark");
|
|
|
|
}
|
|
|
|
};
|
|
|
|
return () => {
|
|
|
|
osTheme.onchange = null;
|
|
|
|
};
|
|
|
|
}, [preferences.theme]);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
// check DB support then init
|
2023-03-03 14:30:31 +00:00
|
|
|
db.isAvailable().then(async a => {
|
|
|
|
db.ready = a;
|
|
|
|
if (a) {
|
2023-03-29 12:10:22 +00:00
|
|
|
await preload();
|
2023-02-07 20:04:50 +00:00
|
|
|
}
|
2023-03-03 14:30:31 +00:00
|
|
|
console.debug(`Using db: ${a ? "IndexedDB" : "In-Memory"}`);
|
2023-01-29 19:44:53 +00:00
|
|
|
|
|
|
|
try {
|
|
|
|
if ("registerProtocolHandler" in window.navigator) {
|
2023-02-14 11:08:25 +00:00
|
|
|
window.navigator.registerProtocolHandler(
|
|
|
|
"web+nostr",
|
2023-03-31 09:51:50 +00:00
|
|
|
`${window.location.protocol}//${window.location.host}/%s`
|
2023-02-14 11:08:25 +00:00
|
|
|
);
|
|
|
|
console.info("Registered protocol handler for 'web+nostr'");
|
2023-01-29 19:44:53 +00:00
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
console.error("Failed to register protocol handler", e);
|
|
|
|
}
|
2023-02-07 20:04:50 +00:00
|
|
|
});
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
return (
|
2023-02-09 18:05:45 +00:00
|
|
|
<div className={pageClass}>
|
|
|
|
{!shouldHideHeader && (
|
2023-04-18 11:52:58 +00:00
|
|
|
<header className="main-content mt5">
|
2023-04-04 18:07:41 +00:00
|
|
|
<div className="logo" onClick={() => navigate("/")}>
|
2023-04-13 18:43:43 +00:00
|
|
|
<h1>Snort</h1>
|
2023-04-14 11:33:19 +00:00
|
|
|
{currentSubscription && (
|
2023-04-13 18:43:43 +00:00
|
|
|
<small className="flex">
|
|
|
|
<Icon name="diamond" size={10} className="mr5" />
|
2023-04-14 11:33:19 +00:00
|
|
|
{mapPlanName(currentSubscription.type)}
|
2023-04-13 18:43:43 +00:00
|
|
|
</small>
|
|
|
|
)}
|
2023-02-09 18:05:45 +00:00
|
|
|
</div>
|
2023-04-13 18:43:43 +00:00
|
|
|
|
2023-02-09 18:05:45 +00:00
|
|
|
<div>
|
|
|
|
{publicKey ? (
|
2023-03-28 14:34:01 +00:00
|
|
|
<AccountHeader />
|
2023-02-09 18:05:45 +00:00
|
|
|
) : (
|
|
|
|
<button type="button" onClick={() => navigate("/login")}>
|
|
|
|
<FormattedMessage {...messages.Login} />
|
|
|
|
</button>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</header>
|
|
|
|
)}
|
2023-02-07 20:04:50 +00:00
|
|
|
<Outlet />
|
|
|
|
|
|
|
|
{!shouldHideNoteCreator && (
|
|
|
|
<>
|
2023-04-08 12:48:57 +00:00
|
|
|
<button className="note-create-button" type="button" onClick={handleNoteCreatorButtonClick}>
|
2023-03-02 18:40:46 +00:00
|
|
|
<Icon name="plus" size={16} />
|
2023-02-07 20:04:50 +00:00
|
|
|
</button>
|
2023-04-08 12:48:57 +00:00
|
|
|
<NoteCreator />
|
2023-02-07 20:04:50 +00:00
|
|
|
</>
|
|
|
|
)}
|
2023-03-28 14:34:01 +00:00
|
|
|
{window.localStorage.getItem("debug") && <SubDebug />}
|
2023-02-07 20:04:50 +00:00
|
|
|
</div>
|
|
|
|
);
|
2023-01-19 23:19:09 +00:00
|
|
|
}
|
2023-03-28 14:34:01 +00:00
|
|
|
|
|
|
|
const AccountHeader = () => {
|
|
|
|
const navigate = useNavigate();
|
|
|
|
|
|
|
|
const { isMuted } = useModeration();
|
2023-04-14 11:33:19 +00:00
|
|
|
const { publicKey, latestNotification, readNotifications } = useLogin();
|
2023-03-29 12:10:22 +00:00
|
|
|
const dms = useDmCache();
|
2023-04-14 12:08:42 +00:00
|
|
|
const profile = useUserProfile(publicKey);
|
2023-03-28 14:34:01 +00:00
|
|
|
|
|
|
|
const hasNotifications = useMemo(
|
|
|
|
() => latestNotification > readNotifications,
|
|
|
|
[latestNotification, readNotifications]
|
|
|
|
);
|
|
|
|
const unreadDms = useMemo(
|
|
|
|
() =>
|
|
|
|
publicKey
|
|
|
|
? totalUnread(
|
|
|
|
dms.filter(a => !isMuted(a.pubkey)),
|
|
|
|
publicKey
|
|
|
|
)
|
|
|
|
: 0,
|
|
|
|
[dms, publicKey]
|
|
|
|
);
|
|
|
|
|
|
|
|
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");
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div className="header-actions">
|
|
|
|
<div className="btn btn-rnd" onClick={() => navigate("/wallet")}>
|
2023-04-14 12:08:42 +00:00
|
|
|
<Icon name="wallet" />
|
2023-03-28 14:34:01 +00:00
|
|
|
</div>
|
|
|
|
<div className="btn btn-rnd" onClick={() => navigate("/search")}>
|
|
|
|
<Icon name="search" />
|
|
|
|
</div>
|
|
|
|
<div className="btn btn-rnd" onClick={() => navigate("/messages")}>
|
|
|
|
<Icon name="envelope" />
|
|
|
|
{unreadDms > 0 && <span className="has-unread"></span>}
|
|
|
|
</div>
|
|
|
|
<div className="btn btn-rnd" onClick={goToNotifications}>
|
|
|
|
<Icon name="bell" />
|
|
|
|
{hasNotifications && <span className="has-unread"></span>}
|
|
|
|
</div>
|
2023-04-14 15:02:15 +00:00
|
|
|
<Avatar
|
|
|
|
user={profile}
|
|
|
|
onClick={() => {
|
|
|
|
if (profile) {
|
|
|
|
navigate(profileLink(profile.pubkey));
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
/>
|
2023-03-28 14:34:01 +00:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|