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';
31 interface NavbarState {
34 replies: Array<Comment>;
35 mentions: Array<Comment>;
36 messages: Array<PrivateMessage>;
42 export class Navbar extends Component<any, NavbarState> {
43 private wsSub: Subscription;
44 private userSub: Subscription;
45 emptyState: NavbarState = {
46 isLoggedIn: UserService.Instance.user !== undefined,
56 constructor(props: any, context: any) {
57 super(props, context);
58 this.state = this.emptyState;
60 this.keepFetchingUnreads();
62 // Subscribe to user changes
63 this.userSub = UserService.Instance.sub.subscribe(user => {
64 this.state.isLoggedIn = user.user !== undefined;
65 this.state.unreadCount = user.unreadCount;
66 this.requestNotificationPermission();
67 this.setState(this.state);
70 this.wsSub = WebSocketService.Instance.subject
71 .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
73 msg => this.parseMessage(msg),
74 err => console.error(err),
75 () => console.log('complete')
78 if (this.state.isLoggedIn) {
79 this.requestNotificationPermission();
82 WebSocketService.Instance.getSite();
89 componentWillUnmount() {
90 this.wsSub.unsubscribe();
91 this.userSub.unsubscribe();
94 // TODO class active corresponding to current page
97 <nav class="container-fluid navbar navbar-expand-md navbar-light shadow p-0 px-3">
98 <Link title={version} class="navbar-brand" to="/">
102 class="navbar-toggler"
105 onClick={linkEvent(this, this.expandNavbar)}
107 <span class="navbar-toggler-icon"></span>
110 className={`${!this.state.expanded && 'collapse'} navbar-collapse`}
112 <ul class="navbar-nav mr-auto">
113 <li class="nav-item">
114 <Link class="nav-link" to="/communities">
115 {i18n.t('communities')}
118 <li class="nav-item">
119 <Link class="nav-link" to="/search">
123 <li class="nav-item">
127 pathname: '/create_post',
128 state: { prevPath: this.currentLocation },
131 {i18n.t('create_post')}
134 <li class="nav-item">
135 <Link class="nav-link" to="/create_community">
136 {i18n.t('create_community')}
139 <li className="nav-item">
143 title={i18n.t('donate_to_lemmy')}
146 <use xlinkHref="#icon-coffee"></use>
151 <ul class="navbar-nav ml-auto">
152 {this.state.isLoggedIn ? (
154 <li className="nav-item mt-1">
155 <Link class="nav-link" to="/inbox">
157 <use xlinkHref="#icon-mail"></use>
159 {this.state.unreadCount > 0 && (
160 <span class="ml-1 badge badge-light">
161 {this.state.unreadCount}
166 <li className="nav-item">
169 to={`/u/${UserService.Instance.user.username}`}
172 {UserService.Instance.user.avatar && showAvatars() && (
174 src={pictshareAvatarThumbnail(
175 UserService.Instance.user.avatar
179 class="rounded-circle mr-2"
182 {UserService.Instance.user.username}
188 <Link class="nav-link" to="/login">
189 {i18n.t('login_sign_up')}
198 expandNavbar(i: Navbar) {
199 i.state.expanded = !i.state.expanded;
203 parseMessage(msg: WebSocketJsonResponse) {
204 let res = wsJsonToRes(msg);
206 if (msg.error == 'not_logged_in') {
207 UserService.Instance.logout();
211 } else if (res.op == UserOperation.GetReplies) {
212 let data = res.data as GetRepliesResponse;
213 let unreadReplies = data.replies.filter(r => !r.read);
215 unreadReplies.length > 0 &&
216 this.state.fetchCount > 1 &&
217 JSON.stringify(this.state.replies) !== JSON.stringify(unreadReplies)
219 this.notify(unreadReplies);
222 this.state.replies = unreadReplies;
223 this.setState(this.state);
224 this.sendUnreadCount();
225 } else if (res.op == UserOperation.GetUserMentions) {
226 let data = res.data as GetUserMentionsResponse;
227 let unreadMentions = data.mentions.filter(r => !r.read);
229 unreadMentions.length > 0 &&
230 this.state.fetchCount > 1 &&
231 JSON.stringify(this.state.mentions) !== JSON.stringify(unreadMentions)
233 this.notify(unreadMentions);
236 this.state.mentions = unreadMentions;
237 this.setState(this.state);
238 this.sendUnreadCount();
239 } else if (res.op == UserOperation.GetPrivateMessages) {
240 let data = res.data as PrivateMessagesResponse;
241 let unreadMessages = data.messages.filter(r => !r.read);
243 unreadMessages.length > 0 &&
244 this.state.fetchCount > 1 &&
245 JSON.stringify(this.state.messages) !== JSON.stringify(unreadMessages)
247 this.notify(unreadMessages);
250 this.state.messages = unreadMessages;
251 this.setState(this.state);
252 this.sendUnreadCount();
253 } else if (res.op == UserOperation.GetSite) {
254 let data = res.data as GetSiteResponse;
257 this.state.siteName = data.site.name;
258 WebSocketService.Instance.site = data.site;
259 this.setState(this.state);
264 keepFetchingUnreads() {
266 setInterval(() => this.fetchUnreads(), 15000);
270 if (this.state.isLoggedIn) {
271 let repliesForm: GetRepliesForm = {
272 sort: SortType[SortType.New],
278 let userMentionsForm: GetUserMentionsForm = {
279 sort: SortType[SortType.New],
285 let privateMessagesForm: GetPrivateMessagesForm = {
291 if (this.currentLocation !== '/inbox') {
292 WebSocketService.Instance.getReplies(repliesForm);
293 WebSocketService.Instance.getUserMentions(userMentionsForm);
294 WebSocketService.Instance.getPrivateMessages(privateMessagesForm);
295 this.state.fetchCount++;
300 get currentLocation() {
301 return this.context.router.history.location.pathname;
305 UserService.Instance.sub.next({
306 user: UserService.Instance.user,
307 unreadCount: this.unreadCount,
313 this.state.replies.filter(r => !r.read).length +
314 this.state.mentions.filter(r => !r.read).length +
315 this.state.messages.filter(r => !r.read).length
319 requestNotificationPermission() {
320 if (UserService.Instance.user) {
321 document.addEventListener('DOMContentLoaded', function() {
323 toast(i18n.t('notifications_error'), 'danger');
327 if (Notification.permission !== 'granted')
328 Notification.requestPermission();
333 notify(replies: Array<Comment | PrivateMessage>) {
334 let recentReply = replies[0];
335 if (Notification.permission !== 'granted') Notification.requestPermission();
337 var notification = new Notification(
338 `${replies.length} ${i18n.t('unread_messages')}`,
340 icon: recentReply.creator_avatar
341 ? recentReply.creator_avatar
342 : `${window.location.protocol}//${window.location.host}/static/assets/apple-touch-icon.png`,
343 body: `${recentReply.creator_name}: ${recentReply.content}`,
347 notification.onclick = () => {
348 this.context.router.history.push(
349 isCommentType(recentReply)
350 ? `/post/${recentReply.post_id}/comment/${recentReply.id}`