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,
31 import { version } from '../version';
32 import { i18n } from '../i18next';
34 interface NavbarState {
37 replies: Array<Comment>;
38 mentions: Array<Comment>;
39 messages: Array<PrivateMessage>;
44 export class Navbar extends Component<any, NavbarState> {
45 private wsSub: Subscription;
46 private userSub: Subscription;
47 emptyState: NavbarState = {
48 isLoggedIn: UserService.Instance.user !== undefined,
57 constructor(props: any, context: any) {
58 super(props, context);
59 this.state = this.emptyState;
61 // Subscribe to user changes
62 this.userSub = UserService.Instance.sub.subscribe(user => {
63 this.state.isLoggedIn = user.user !== undefined;
64 if (this.state.isLoggedIn) {
65 this.state.unreadCount = user.user.unreadCount;
66 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();
81 // TODO couldn't get re-logging in to re-fetch unreads
85 WebSocketService.Instance.getSite();
96 componentWillUnmount() {
97 this.wsSub.unsubscribe();
98 this.userSub.unsubscribe();
101 // TODO class active corresponding to current page
104 <nav class="container-fluid navbar navbar-expand-md navbar-light shadow p-0 px-3">
105 <Link title={version} class="navbar-brand" to="/">
106 {this.state.siteName}
109 class="navbar-toggler"
112 onClick={linkEvent(this, this.expandNavbar)}
113 data-tippy-content={i18n.t('expand_here')}
115 <span class="navbar-toggler-icon"></span>
118 className={`${!this.state.expanded && 'collapse'} navbar-collapse`}
120 <ul class="navbar-nav mr-auto">
121 <li class="nav-item">
125 title={i18n.t('communities')}
127 {i18n.t('communities')}
130 <li class="nav-item">
131 <Link class="nav-link" to="/search" title={i18n.t('search')}>
135 <li class="nav-item">
139 pathname: '/create_post',
140 state: { prevPath: this.currentLocation },
142 title={i18n.t('create_post')}
144 {i18n.t('create_post')}
147 <li class="nav-item">
150 to="/create_community"
151 title={i18n.t('create_community')}
153 {i18n.t('create_community')}
156 <li className="nav-item">
160 title={i18n.t('donate_to_lemmy')}
163 <use xlinkHref="#icon-coffee"></use>
168 <ul class="navbar-nav ml-auto">
169 {this.state.isLoggedIn ? (
171 <li className="nav-item mt-1">
172 <Link class="nav-link" to="/inbox" title={i18n.t('inbox')}>
174 <use xlinkHref="#icon-bell"></use>
176 {this.state.unreadCount > 0 && (
177 <span class="ml-1 badge badge-light">
178 {this.state.unreadCount}
183 <li className="nav-item">
186 to={`/u/${UserService.Instance.user.username}`}
187 title={i18n.t('settings')}
190 {UserService.Instance.user.avatar && showAvatars() && (
192 src={pictshareAvatarThumbnail(
193 UserService.Instance.user.avatar
197 class="rounded-circle mr-2"
200 {UserService.Instance.user.username}
209 title={i18n.t('login_sign_up')}
211 {i18n.t('login_sign_up')}
220 expandNavbar(i: Navbar) {
221 i.state.expanded = !i.state.expanded;
225 parseMessage(msg: WebSocketJsonResponse) {
226 let res = wsJsonToRes(msg);
228 if (msg.error == 'not_logged_in') {
229 UserService.Instance.logout();
233 } else if (msg.reconnect) {
235 } else if (res.op == UserOperation.GetReplies) {
236 let data = res.data as GetRepliesResponse;
237 let unreadReplies = data.replies.filter(r => !r.read);
239 this.state.replies = unreadReplies;
240 this.state.unreadCount = this.calculateUnreadCount();
241 this.setState(this.state);
242 this.sendUnreadCount();
243 } else if (res.op == UserOperation.GetUserMentions) {
244 let data = res.data as GetUserMentionsResponse;
245 let unreadMentions = data.mentions.filter(r => !r.read);
247 this.state.mentions = unreadMentions;
248 this.state.unreadCount = this.calculateUnreadCount();
249 this.setState(this.state);
250 this.sendUnreadCount();
251 } else if (res.op == UserOperation.GetPrivateMessages) {
252 let data = res.data as PrivateMessagesResponse;
253 let unreadMessages = data.messages.filter(r => !r.read);
255 this.state.messages = unreadMessages;
256 this.state.unreadCount = this.calculateUnreadCount();
257 this.setState(this.state);
258 this.sendUnreadCount();
259 } else if (res.op == UserOperation.CreateComment) {
260 let data = res.data as CommentResponse;
262 if (this.state.isLoggedIn) {
263 if (data.recipient_ids.includes(UserService.Instance.user.id)) {
264 this.state.replies.push(data.comment);
265 this.state.unreadCount++;
266 this.setState(this.state);
267 this.sendUnreadCount();
268 this.notify(data.comment);
271 } else if (res.op == UserOperation.CreatePrivateMessage) {
272 let data = res.data as PrivateMessageResponse;
274 if (this.state.isLoggedIn) {
275 if (data.message.recipient_id == UserService.Instance.user.id) {
276 this.state.messages.push(data.message);
277 this.state.unreadCount++;
278 this.setState(this.state);
279 this.sendUnreadCount();
280 this.notify(data.message);
283 } else if (res.op == UserOperation.GetSite) {
284 let data = res.data as GetSiteResponse;
287 this.state.siteName = data.site.name;
288 WebSocketService.Instance.site = data.site;
289 this.setState(this.state);
295 if (this.state.isLoggedIn) {
296 let repliesForm: GetRepliesForm = {
297 sort: SortType[SortType.New],
303 let userMentionsForm: GetUserMentionsForm = {
304 sort: SortType[SortType.New],
310 let privateMessagesForm: GetPrivateMessagesForm = {
316 if (this.currentLocation !== '/inbox') {
317 WebSocketService.Instance.getReplies(repliesForm);
318 WebSocketService.Instance.getUserMentions(userMentionsForm);
319 WebSocketService.Instance.getPrivateMessages(privateMessagesForm);
324 get currentLocation() {
325 return this.context.router.history.location.pathname;
329 UserService.Instance.user.unreadCount = this.state.unreadCount;
330 UserService.Instance.sub.next({
331 user: UserService.Instance.user,
335 calculateUnreadCount(): number {
337 this.state.replies.filter(r => !r.read).length +
338 this.state.mentions.filter(r => !r.read).length +
339 this.state.messages.filter(r => !r.read).length
343 requestNotificationPermission() {
344 if (UserService.Instance.user) {
345 document.addEventListener('DOMContentLoaded', function() {
347 toast(i18n.t('notifications_error'), 'danger');
351 if (Notification.permission !== 'granted')
352 Notification.requestPermission();
357 notify(reply: Comment | PrivateMessage) {
358 if (Notification.permission !== 'granted') Notification.requestPermission();
360 var notification = new Notification(reply.creator_name, {
361 icon: reply.creator_avatar
362 ? reply.creator_avatar
363 : `${window.location.protocol}//${window.location.host}/static/assets/apple-touch-icon.png`,
364 body: `${reply.content}`,
367 notification.onclick = () => {
368 this.context.router.history.push(
370 ? `/post/${reply.post_id}/comment/${reply.id}`