1 import { Component, linkEvent } from 'inferno';
2 import { Link } from 'inferno-router';
3 import { Subscription } from 'rxjs';
4 import { retryWhen, delay, take } from 'rxjs/operators';
5 import { WebSocketService, UserService } from '../services';
11 GetUserMentionsResponse,
12 GetPrivateMessagesForm,
13 PrivateMessagesResponse,
18 WebSocketJsonResponse,
19 } from '../interfaces';
22 pictshareAvatarThumbnail,
28 import { version } from '../version';
29 import { i18n } from '../i18next';
30 import { T } from 'inferno-i18next';
32 interface NavbarState {
35 replies: Array<Comment>;
36 mentions: Array<Comment>;
37 messages: Array<PrivateMessage>;
43 export class Navbar extends Component<any, NavbarState> {
44 private wsSub: Subscription;
45 private userSub: Subscription;
46 emptyState: NavbarState = {
47 isLoggedIn: UserService.Instance.user !== undefined,
57 constructor(props: any, context: any) {
58 super(props, context);
59 this.state = this.emptyState;
61 this.keepFetchingUnreads();
63 // Subscribe to user changes
64 this.userSub = UserService.Instance.sub.subscribe(user => {
65 this.state.isLoggedIn = user.user !== undefined;
66 this.state.unreadCount = user.unreadCount;
67 this.requestNotificationPermission();
68 this.setState(this.state);
71 this.wsSub = WebSocketService.Instance.subject
72 .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
74 msg => this.parseMessage(msg),
75 err => console.error(err),
76 () => console.log('complete')
79 if (this.state.isLoggedIn) {
80 this.requestNotificationPermission();
83 WebSocketService.Instance.getSite();
90 componentWillUnmount() {
91 this.wsSub.unsubscribe();
92 this.userSub.unsubscribe();
95 // TODO class active corresponding to current page
98 <nav class="container-fluid navbar navbar-expand-md navbar-light shadow p-0 px-3">
99 <Link title={version} class="navbar-brand" to="/">
100 {this.state.siteName}
103 class="navbar-toggler"
106 onClick={linkEvent(this, this.expandNavbar)}
108 <span class="navbar-toggler-icon"></span>
111 className={`${!this.state.expanded && 'collapse'} navbar-collapse`}
113 <ul class="navbar-nav mr-auto">
114 <li class="nav-item">
115 <Link class="nav-link" to="/communities">
116 { i18n.t('communities') }
119 <li class="nav-item">
120 <Link class="nav-link" to="/search">
124 <li class="nav-item">
128 pathname: '/create_post',
129 state: { prevPath: this.currentLocation },
132 { i18n.t('create_post') }
135 <li class="nav-item">
136 <Link class="nav-link" to="/create_community">
137 { i18n.t('create_community') }
140 <li className="nav-item">
142 class="nav-link ml-2"
144 title={i18n.t('donate_to_lemmy')}
147 <use xlinkHref="#icon-coffee"></use>
152 <ul class="navbar-nav ml-auto">
153 {this.state.isLoggedIn ? (
155 <li className="nav-item mt-1">
156 <Link class="nav-link" to="/inbox">
158 <use xlinkHref="#icon-mail"></use>
160 {this.state.unreadCount > 0 && (
161 <span class="ml-1 badge badge-light">
162 {this.state.unreadCount}
167 <li className="nav-item">
170 to={`/u/${UserService.Instance.user.username}`}
173 {UserService.Instance.user.avatar && showAvatars() && (
175 src={pictshareAvatarThumbnail(
176 UserService.Instance.user.avatar
180 class="rounded-circle mr-2"
183 {UserService.Instance.user.username}
189 <Link class="nav-link" to="/login">
190 { i18n.t('login_sign_up') }
199 expandNavbar(i: Navbar) {
200 i.state.expanded = !i.state.expanded;
204 parseMessage(msg: WebSocketJsonResponse) {
205 let res = wsJsonToRes(msg);
207 if (msg.error == 'not_logged_in') {
208 UserService.Instance.logout();
212 } else if (res.op == UserOperation.GetReplies) {
213 let data = res.data as GetRepliesResponse;
214 let unreadReplies = data.replies.filter(r => !r.read);
216 unreadReplies.length > 0 &&
217 this.state.fetchCount > 1 &&
218 JSON.stringify(this.state.replies) !== JSON.stringify(unreadReplies)
220 this.notify(unreadReplies);
223 this.state.replies = unreadReplies;
224 this.setState(this.state);
225 this.sendUnreadCount();
226 } else if (res.op == UserOperation.GetUserMentions) {
227 let data = res.data as GetUserMentionsResponse;
228 let unreadMentions = data.mentions.filter(r => !r.read);
230 unreadMentions.length > 0 &&
231 this.state.fetchCount > 1 &&
232 JSON.stringify(this.state.mentions) !== JSON.stringify(unreadMentions)
234 this.notify(unreadMentions);
237 this.state.mentions = unreadMentions;
238 this.setState(this.state);
239 this.sendUnreadCount();
240 } else if (res.op == UserOperation.GetPrivateMessages) {
241 let data = res.data as PrivateMessagesResponse;
242 let unreadMessages = data.messages.filter(r => !r.read);
244 unreadMessages.length > 0 &&
245 this.state.fetchCount > 1 &&
246 JSON.stringify(this.state.messages) !== JSON.stringify(unreadMessages)
248 this.notify(unreadMessages);
251 this.state.messages = unreadMessages;
252 this.setState(this.state);
253 this.sendUnreadCount();
254 } else if (res.op == UserOperation.GetSite) {
255 let data = res.data as GetSiteResponse;
258 this.state.siteName = data.site.name;
259 WebSocketService.Instance.site = data.site;
260 this.setState(this.state);
265 keepFetchingUnreads() {
267 setInterval(() => this.fetchUnreads(), 15000);
271 if (this.state.isLoggedIn) {
272 let repliesForm: GetRepliesForm = {
273 sort: SortType[SortType.New],
279 let userMentionsForm: GetUserMentionsForm = {
280 sort: SortType[SortType.New],
286 let privateMessagesForm: GetPrivateMessagesForm = {
292 if (this.currentLocation !== '/inbox') {
293 WebSocketService.Instance.getReplies(repliesForm);
294 WebSocketService.Instance.getUserMentions(userMentionsForm);
295 WebSocketService.Instance.getPrivateMessages(privateMessagesForm);
296 this.state.fetchCount++;
301 get currentLocation() {
302 return this.context.router.history.location.pathname;
306 UserService.Instance.sub.next({
307 user: UserService.Instance.user,
308 unreadCount: this.unreadCount,
314 this.state.replies.filter(r => !r.read).length +
315 this.state.mentions.filter(r => !r.read).length +
316 this.state.messages.filter(r => !r.read).length
320 requestNotificationPermission() {
321 if (UserService.Instance.user) {
322 document.addEventListener('DOMContentLoaded', function() {
324 toast(i18n.t('notifications_error'), 'danger');
328 if (Notification.permission !== 'granted')
329 Notification.requestPermission();
334 notify(replies: Array<Comment | PrivateMessage>) {
335 let recentReply = replies[0];
336 if (Notification.permission !== 'granted') Notification.requestPermission();
338 var notification = new Notification(
339 `${replies.length} ${i18n.t('unread_messages')}`,
341 icon: recentReply.creator_avatar
342 ? recentReply.creator_avatar
343 : `${window.location.protocol}//${window.location.host}/static/assets/apple-touch-icon.png`,
344 body: `${recentReply.creator_name}: ${recentReply.content}`,
348 notification.onclick = () => {
349 this.context.router.history.push(
350 isCommentType(recentReply)
351 ? `/post/${recentReply.post_id}/comment/${recentReply.id}`