import { None } from "@sniptt/monads"; import { Component, createRef, linkEvent, RefObject } from "inferno"; import { NavLink } from "inferno-router"; import { CommentResponse, GetReportCount, GetReportCountResponse, GetSiteResponse, GetUnreadCount, GetUnreadCountResponse, GetUnreadRegistrationApplicationCount, GetUnreadRegistrationApplicationCountResponse, PrivateMessageResponse, UserOperation, wsJsonToRes, wsUserOp, } from "lemmy-js-client"; import { Subscription } from "rxjs"; import { i18n } from "../../i18next"; import { UserService, WebSocketService } from "../../services"; import { amAdmin, auth, canCreateCommunity, donateLemmyUrl, isBrowser, notifyComment, notifyPrivateMessage, numToSI, showAvatars, toast, wsClient, wsSubscribe, } from "../../utils"; import { Icon } from "../common/icon"; import { PictrsImage } from "../common/pictrs-image"; interface NavbarProps { siteRes: GetSiteResponse; } interface NavbarState { expanded: boolean; unreadInboxCount: number; unreadReportCount: number; unreadApplicationCount: number; searchParam: string; toggleSearch: boolean; showDropdown: boolean; onSiteBanner?(url: string): any; } export class Navbar extends Component { private wsSub: Subscription; private userSub: Subscription; private unreadInboxCountSub: Subscription; private unreadReportCountSub: Subscription; private unreadApplicationCountSub: Subscription; private searchTextField: RefObject; emptyState: NavbarState = { unreadInboxCount: 0, unreadReportCount: 0, unreadApplicationCount: 0, expanded: false, searchParam: "", toggleSearch: false, showDropdown: false, }; subscription: any; constructor(props: any, context: any) { super(props, context); this.state = this.emptyState; this.parseMessage = this.parseMessage.bind(this); this.subscription = wsSubscribe(this.parseMessage); } componentDidMount() { // Subscribe to jwt changes if (isBrowser()) { this.searchTextField = createRef(); // On the first load, check the unreads if (UserService.Instance.myUserInfo.isSome()) { this.requestNotificationPermission(); WebSocketService.Instance.send( wsClient.userJoin({ auth: auth().unwrap(), }) ); this.fetchUnreads(); } this.requestNotificationPermission(); // Subscribe to unread count changes this.unreadInboxCountSub = UserService.Instance.unreadInboxCountSub.subscribe(res => { this.setState({ unreadInboxCount: res }); }); // Subscribe to unread report count changes this.unreadReportCountSub = UserService.Instance.unreadReportCountSub.subscribe(res => { this.setState({ unreadReportCount: res }); }); // Subscribe to unread application count this.unreadApplicationCountSub = UserService.Instance.unreadApplicationCountSub.subscribe(res => { this.setState({ unreadApplicationCount: res }); }); } } componentWillUnmount() { this.wsSub.unsubscribe(); this.userSub.unsubscribe(); this.unreadInboxCountSub.unsubscribe(); this.unreadReportCountSub.unsubscribe(); this.unreadApplicationCountSub.unsubscribe(); } updateUrl() { const searchParam = this.state.searchParam; this.setState({ searchParam: "" }); this.setState({ toggleSearch: false }); this.setState({ showDropdown: false, expanded: false }); if (searchParam === "") { this.context.router.history.push(`/search/`); } else { const searchParamEncoded = encodeURIComponent(searchParam); this.context.router.history.push( `/search/q/${searchParamEncoded}/type/All/sort/TopAll/listing_type/All/community_id/0/creator_id/0/page/1` ); } } render() { return this.navbar(); } // TODO class active corresponding to current page navbar() { let siteView = this.props.siteRes.site_view; return ( ); } get moderatesSomething(): boolean { return ( UserService.Instance.myUserInfo.map(m => m.moderates).unwrapOr([]) .length > 0 ); } handleToggleExpandNavbar(i: Navbar) { i.setState({ expanded: !i.state.expanded }); } handleHideExpandNavbar(i: Navbar) { i.setState({ expanded: false, showDropdown: false }); } handleSearchParam(i: Navbar, event: any) { i.setState({ searchParam: event.target.value }); } handleSearchSubmit(i: Navbar, event: any) { event.preventDefault(); i.updateUrl(); } handleSearchBtn(i: Navbar, event: any) { event.preventDefault(); i.setState({ toggleSearch: true }); i.searchTextField.current.focus(); const offsetWidth = i.searchTextField.current.offsetWidth; if (i.state.searchParam && offsetWidth > 100) { i.updateUrl(); } } handleSearchBlur(i: Navbar, event: any) { if (!(event.relatedTarget && event.relatedTarget.name !== "search-btn")) { i.setState({ toggleSearch: false }); } } handleLogoutClick(i: Navbar) { i.setState({ showDropdown: false, expanded: false }); UserService.Instance.logout(); } handleToggleDropdown(i: Navbar) { i.setState({ showDropdown: !i.state.showDropdown }); } parseMessage(msg: any) { let op = wsUserOp(msg); console.log(msg); if (msg.error) { if (msg.error == "not_logged_in") { UserService.Instance.logout(); } return; } else if (msg.reconnect) { console.log(i18n.t("websocket_reconnected")); if (UserService.Instance.myUserInfo.isSome()) { WebSocketService.Instance.send( wsClient.userJoin({ auth: auth().unwrap(), }) ); this.fetchUnreads(); } } else if (op == UserOperation.GetUnreadCount) { let data = wsJsonToRes( msg, GetUnreadCountResponse ); this.setState({ unreadInboxCount: data.replies + data.mentions + data.private_messages, }); this.sendUnreadCount(); } else if (op == UserOperation.GetReportCount) { let data = wsJsonToRes( msg, GetReportCountResponse ); this.setState({ unreadReportCount: data.post_reports + data.comment_reports + data.private_message_reports.unwrapOr(0), }); this.sendReportUnread(); } else if (op == UserOperation.GetUnreadRegistrationApplicationCount) { let data = wsJsonToRes( msg, GetUnreadRegistrationApplicationCountResponse ); this.setState({ unreadApplicationCount: data.registration_applications }); this.sendApplicationUnread(); } else if (op == UserOperation.CreateComment) { let data = wsJsonToRes(msg, CommentResponse); UserService.Instance.myUserInfo.match({ some: mui => { if (data.recipient_ids.includes(mui.local_user_view.local_user.id)) { this.setState({ unreadInboxCount: this.state.unreadInboxCount + 1, }); this.sendUnreadCount(); notifyComment(data.comment_view, this.context.router); } }, none: void 0, }); } else if (op == UserOperation.CreatePrivateMessage) { let data = wsJsonToRes( msg, PrivateMessageResponse ); UserService.Instance.myUserInfo.match({ some: mui => { if ( data.private_message_view.recipient.id == mui.local_user_view.person.id ) { this.setState({ unreadInboxCount: this.state.unreadInboxCount + 1, }); this.sendUnreadCount(); notifyPrivateMessage( data.private_message_view, this.context.router ); } }, none: void 0, }); } } fetchUnreads() { console.log("Fetching inbox unreads..."); let unreadForm = new GetUnreadCount({ auth: auth().unwrap(), }); WebSocketService.Instance.send(wsClient.getUnreadCount(unreadForm)); console.log("Fetching reports..."); let reportCountForm = new GetReportCount({ community_id: None, auth: auth().unwrap(), }); WebSocketService.Instance.send(wsClient.getReportCount(reportCountForm)); if (amAdmin()) { console.log("Fetching applications..."); let applicationCountForm = new GetUnreadRegistrationApplicationCount({ auth: auth().unwrap(), }); WebSocketService.Instance.send( wsClient.getUnreadRegistrationApplicationCount(applicationCountForm) ); } } get currentLocation() { return this.context.router.history.location.pathname; } sendUnreadCount() { UserService.Instance.unreadInboxCountSub.next(this.state.unreadInboxCount); } sendReportUnread() { UserService.Instance.unreadReportCountSub.next( this.state.unreadReportCount ); } sendApplicationUnread() { UserService.Instance.unreadApplicationCountSub.next( this.state.unreadApplicationCount ); } requestNotificationPermission() { if (UserService.Instance.myUserInfo.isSome()) { document.addEventListener("DOMContentLoaded", function () { if (!Notification) { toast(i18n.t("notifications_error"), "danger"); return; } if (Notification.permission !== "granted") Notification.requestPermission(); }); } } }