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 this.state.unreadCount = user.unreadCount;
64 this.requestNotificationPermission();
65 this.setState(this.state);
68 this.wsSub = WebSocketService.Instance.subject
69 .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
71 msg => this.parseMessage(msg),
72 err => console.error(err),
73 () => console.log('complete')
76 if (this.state.isLoggedIn) {
77 this.requestNotificationPermission();
78 // TODO couldn't get re-logging in to re-fetch unreads
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 (msg.reconnect) {
213 } else if (res.op == UserOperation.GetReplies) {
214 let data = res.data as GetRepliesResponse;
215 let unreadReplies = data.replies.filter(r => !r.read);
217 this.state.replies = unreadReplies;
218 this.state.unreadCount = this.calculateUnreadCount();
219 this.setState(this.state);
220 this.sendUnreadCount();
221 } else if (res.op == UserOperation.GetUserMentions) {
222 let data = res.data as GetUserMentionsResponse;
223 let unreadMentions = data.mentions.filter(r => !r.read);
225 this.state.mentions = unreadMentions;
226 this.state.unreadCount = this.calculateUnreadCount();
227 this.setState(this.state);
228 this.sendUnreadCount();
229 } else if (res.op == UserOperation.GetPrivateMessages) {
230 let data = res.data as PrivateMessagesResponse;
231 let unreadMessages = data.messages.filter(r => !r.read);
233 this.state.messages = unreadMessages;
234 this.state.unreadCount = this.calculateUnreadCount();
235 this.setState(this.state);
236 this.sendUnreadCount();
237 } else if (res.op == UserOperation.CreateComment) {
238 let data = res.data as CommentResponse;
240 if (this.state.isLoggedIn) {
241 if (data.recipient_ids.includes(UserService.Instance.user.id)) {
242 this.state.replies.push(data.comment);
243 this.state.unreadCount++;
244 this.setState(this.state);
245 this.sendUnreadCount();
246 this.notify(data.comment);
249 } else if (res.op == UserOperation.CreatePrivateMessage) {
250 let data = res.data as PrivateMessageResponse;
252 if (this.state.isLoggedIn) {
253 if (data.message.recipient_id == UserService.Instance.user.id) {
254 this.state.messages.push(data.message);
255 this.state.unreadCount++;
256 this.setState(this.state);
257 this.sendUnreadCount();
258 this.notify(data.message);
261 } else if (res.op == UserOperation.GetSite) {
262 let data = res.data as GetSiteResponse;
265 this.state.siteName = data.site.name;
266 WebSocketService.Instance.site = data.site;
267 this.setState(this.state);
273 if (this.state.isLoggedIn) {
274 let repliesForm: GetRepliesForm = {
275 sort: SortType[SortType.New],
281 let userMentionsForm: GetUserMentionsForm = {
282 sort: SortType[SortType.New],
288 let privateMessagesForm: GetPrivateMessagesForm = {
294 if (this.currentLocation !== '/inbox') {
295 WebSocketService.Instance.getReplies(repliesForm);
296 WebSocketService.Instance.getUserMentions(userMentionsForm);
297 WebSocketService.Instance.getPrivateMessages(privateMessagesForm);
302 get currentLocation() {
303 return this.context.router.history.location.pathname;
307 UserService.Instance.sub.next({
308 user: UserService.Instance.user,
309 unreadCount: this.state.unreadCount,
313 calculateUnreadCount(): number {
315 this.state.replies.filter(r => !r.read).length +
316 this.state.mentions.filter(r => !r.read).length +
317 this.state.messages.filter(r => !r.read).length
321 requestNotificationPermission() {
322 if (UserService.Instance.user) {
323 document.addEventListener('DOMContentLoaded', function() {
325 toast(i18n.t('notifications_error'), 'danger');
329 if (Notification.permission !== 'granted')
330 Notification.requestPermission();
335 notify(reply: Comment | PrivateMessage) {
336 if (Notification.permission !== 'granted') Notification.requestPermission();
338 var notification = new Notification(reply.creator_name, {
339 icon: reply.creator_avatar
340 ? reply.creator_avatar
341 : `${window.location.protocol}//${window.location.host}/static/assets/apple-touch-icon.png`,
342 body: `${reply.content}`,
345 notification.onclick = () => {
346 this.context.router.history.push(
348 ? `/post/${reply.post_id}/comment/${reply.id}`