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,
19 PrivateMessageResponse,
20 WebSocketJsonResponse,
21 } from '../interfaces';
24 pictshareAvatarThumbnail,
30 import { version } from '../version';
31 import { i18n } from '../i18next';
33 interface NavbarState {
36 replies: Array<Comment>;
37 mentions: Array<Comment>;
38 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,
56 constructor(props: any, context: any) {
57 super(props, context);
58 this.state = this.emptyState;
60 // Subscribe to user changes
61 this.userSub = UserService.Instance.sub.subscribe(user => {
62 this.state.isLoggedIn = user.user !== undefined;
63 if (this.state.isLoggedIn) {
64 this.state.unreadCount = user.user.unreadCount;
65 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();
80 // TODO couldn't get re-logging in to re-fetch unreads
84 WebSocketService.Instance.getSite();
91 componentWillUnmount() {
92 this.wsSub.unsubscribe();
93 this.userSub.unsubscribe();
96 // TODO class active corresponding to current page
99 <nav class="container-fluid navbar navbar-expand-md navbar-light shadow p-0 px-3">
100 <Link title={version} class="navbar-brand" to="/">
101 {this.state.siteName}
104 class="navbar-toggler"
107 onClick={linkEvent(this, this.expandNavbar)}
108 data-tippy-content={i18n.t('expand_here')}
110 <span class="navbar-toggler-icon"></span>
113 className={`${!this.state.expanded && 'collapse'} navbar-collapse`}
115 <ul class="navbar-nav mr-auto">
116 <li class="nav-item">
120 title={i18n.t('communities')}
122 {i18n.t('communities')}
125 <li class="nav-item">
126 <Link class="nav-link" to="/search" title={i18n.t('search')}>
130 <li class="nav-item">
134 pathname: '/create_post',
135 state: { prevPath: this.currentLocation },
137 title={i18n.t('create_post')}
139 {i18n.t('create_post')}
142 <li class="nav-item">
145 to="/create_community"
146 title={i18n.t('create_community')}
148 {i18n.t('create_community')}
151 <li className="nav-item">
155 title={i18n.t('donate_to_lemmy')}
158 <use xlinkHref="#icon-coffee"></use>
163 <ul class="navbar-nav ml-auto">
164 {this.state.isLoggedIn ? (
166 <li className="nav-item mt-1">
167 <Link class="nav-link" to="/inbox" title={i18n.t('inbox')}>
169 <use xlinkHref="#icon-bell"></use>
171 {this.state.unreadCount > 0 && (
172 <span class="ml-1 badge badge-light">
173 {this.state.unreadCount}
178 <li className="nav-item">
181 to={`/u/${UserService.Instance.user.username}`}
182 title={i18n.t('settings')}
185 {UserService.Instance.user.avatar && showAvatars() && (
187 src={pictshareAvatarThumbnail(
188 UserService.Instance.user.avatar
192 class="rounded-circle mr-2"
195 {UserService.Instance.user.username}
204 title={i18n.t('login_sign_up')}
206 {i18n.t('login_sign_up')}
215 expandNavbar(i: Navbar) {
216 i.state.expanded = !i.state.expanded;
220 parseMessage(msg: WebSocketJsonResponse) {
221 let res = wsJsonToRes(msg);
223 if (msg.error == 'not_logged_in') {
224 UserService.Instance.logout();
228 } else if (msg.reconnect) {
230 } else if (res.op == UserOperation.GetReplies) {
231 let data = res.data as GetRepliesResponse;
232 let unreadReplies = data.replies.filter(r => !r.read);
234 this.state.replies = unreadReplies;
235 this.state.unreadCount = this.calculateUnreadCount();
236 this.setState(this.state);
237 this.sendUnreadCount();
238 } else if (res.op == UserOperation.GetUserMentions) {
239 let data = res.data as GetUserMentionsResponse;
240 let unreadMentions = data.mentions.filter(r => !r.read);
242 this.state.mentions = unreadMentions;
243 this.state.unreadCount = this.calculateUnreadCount();
244 this.setState(this.state);
245 this.sendUnreadCount();
246 } else if (res.op == UserOperation.GetPrivateMessages) {
247 let data = res.data as PrivateMessagesResponse;
248 let unreadMessages = data.messages.filter(r => !r.read);
250 this.state.messages = unreadMessages;
251 this.state.unreadCount = this.calculateUnreadCount();
252 this.setState(this.state);
253 this.sendUnreadCount();
254 } else if (res.op == UserOperation.CreateComment) {
255 let data = res.data as CommentResponse;
257 if (this.state.isLoggedIn) {
258 if (data.recipient_ids.includes(UserService.Instance.user.id)) {
259 this.state.replies.push(data.comment);
260 this.state.unreadCount++;
261 this.setState(this.state);
262 this.sendUnreadCount();
263 this.notify(data.comment);
266 } else if (res.op == UserOperation.CreatePrivateMessage) {
267 let data = res.data as PrivateMessageResponse;
269 if (this.state.isLoggedIn) {
270 if (data.message.recipient_id == UserService.Instance.user.id) {
271 this.state.messages.push(data.message);
272 this.state.unreadCount++;
273 this.setState(this.state);
274 this.sendUnreadCount();
275 this.notify(data.message);
278 } else if (res.op == UserOperation.GetSite) {
279 let data = res.data as GetSiteResponse;
281 if (data.site && !this.state.siteName) {
282 this.state.siteName = data.site.name;
283 WebSocketService.Instance.site = data.site;
284 this.setState(this.state);
290 if (this.state.isLoggedIn) {
291 let repliesForm: GetRepliesForm = {
292 sort: SortType[SortType.New],
298 let userMentionsForm: GetUserMentionsForm = {
299 sort: SortType[SortType.New],
305 let privateMessagesForm: GetPrivateMessagesForm = {
311 if (this.currentLocation !== '/inbox') {
312 WebSocketService.Instance.getReplies(repliesForm);
313 WebSocketService.Instance.getUserMentions(userMentionsForm);
314 WebSocketService.Instance.getPrivateMessages(privateMessagesForm);
319 get currentLocation() {
320 return this.context.router.history.location.pathname;
324 UserService.Instance.user.unreadCount = this.state.unreadCount;
325 UserService.Instance.sub.next({
326 user: UserService.Instance.user,
330 calculateUnreadCount(): number {
332 this.state.replies.filter(r => !r.read).length +
333 this.state.mentions.filter(r => !r.read).length +
334 this.state.messages.filter(r => !r.read).length
338 requestNotificationPermission() {
339 if (UserService.Instance.user) {
340 document.addEventListener('DOMContentLoaded', function() {
342 toast(i18n.t('notifications_error'), 'danger');
346 if (Notification.permission !== 'granted')
347 Notification.requestPermission();
352 notify(reply: Comment | PrivateMessage) {
353 if (Notification.permission !== 'granted') Notification.requestPermission();
355 var notification = new Notification(reply.creator_name, {
356 icon: reply.creator_avatar
357 ? reply.creator_avatar
358 : `${window.location.protocol}//${window.location.host}/static/assets/apple-touch-icon.png`,
359 body: `${reply.content}`,
362 notification.onclick = () => {
363 this.context.router.history.push(
365 ? `/post/${reply.post_id}/comment/${reply.id}`