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)}
109 <span class="navbar-toggler-icon"></span>
112 className={`${!this.state.expanded && 'collapse'} navbar-collapse`}
114 <ul class="navbar-nav mr-auto">
115 <li class="nav-item">
116 <Link class="nav-link" to="/communities">
117 {i18n.t('communities')}
120 <li class="nav-item">
121 <Link class="nav-link" to="/search">
125 <li class="nav-item">
129 pathname: '/create_post',
130 state: { prevPath: this.currentLocation },
133 {i18n.t('create_post')}
136 <li class="nav-item">
137 <Link class="nav-link" to="/create_community">
138 {i18n.t('create_community')}
141 <li className="nav-item">
145 title={i18n.t('donate_to_lemmy')}
148 <use xlinkHref="#icon-coffee"></use>
153 <ul class="navbar-nav ml-auto">
154 {this.state.isLoggedIn ? (
156 <li className="nav-item mt-1">
157 <Link class="nav-link" to="/inbox">
159 <use xlinkHref="#icon-mail"></use>
161 {this.state.unreadCount > 0 && (
162 <span class="ml-1 badge badge-light">
163 {this.state.unreadCount}
168 <li className="nav-item">
171 to={`/u/${UserService.Instance.user.username}`}
174 {UserService.Instance.user.avatar && showAvatars() && (
176 src={pictshareAvatarThumbnail(
177 UserService.Instance.user.avatar
181 class="rounded-circle mr-2"
184 {UserService.Instance.user.username}
190 <Link class="nav-link" to="/login">
191 {i18n.t('login_sign_up')}
200 expandNavbar(i: Navbar) {
201 i.state.expanded = !i.state.expanded;
205 parseMessage(msg: WebSocketJsonResponse) {
206 let res = wsJsonToRes(msg);
208 if (msg.error == 'not_logged_in') {
209 UserService.Instance.logout();
213 } else if (msg.reconnect) {
215 } else if (res.op == UserOperation.GetReplies) {
216 let data = res.data as GetRepliesResponse;
217 let unreadReplies = data.replies.filter(r => !r.read);
219 this.state.replies = unreadReplies;
220 this.state.unreadCount = this.calculateUnreadCount();
221 this.setState(this.state);
222 this.sendUnreadCount();
223 } else if (res.op == UserOperation.GetUserMentions) {
224 let data = res.data as GetUserMentionsResponse;
225 let unreadMentions = data.mentions.filter(r => !r.read);
227 this.state.mentions = unreadMentions;
228 this.state.unreadCount = this.calculateUnreadCount();
229 this.setState(this.state);
230 this.sendUnreadCount();
231 } else if (res.op == UserOperation.GetPrivateMessages) {
232 let data = res.data as PrivateMessagesResponse;
233 let unreadMessages = data.messages.filter(r => !r.read);
235 this.state.messages = unreadMessages;
236 this.state.unreadCount = this.calculateUnreadCount();
237 this.setState(this.state);
238 this.sendUnreadCount();
239 } else if (res.op == UserOperation.CreateComment) {
240 let data = res.data as CommentResponse;
242 if (this.state.isLoggedIn) {
243 if (data.recipient_ids.includes(UserService.Instance.user.id)) {
244 this.state.replies.push(data.comment);
245 this.state.unreadCount++;
246 this.setState(this.state);
247 this.sendUnreadCount();
248 this.notify(data.comment);
251 } else if (res.op == UserOperation.CreatePrivateMessage) {
252 let data = res.data as PrivateMessageResponse;
254 if (this.state.isLoggedIn) {
255 if (data.message.recipient_id == UserService.Instance.user.id) {
256 this.state.messages.push(data.message);
257 this.state.unreadCount++;
258 this.setState(this.state);
259 this.sendUnreadCount();
260 this.notify(data.message);
263 } else if (res.op == UserOperation.GetSite) {
264 let data = res.data as GetSiteResponse;
267 this.state.siteName = data.site.name;
268 WebSocketService.Instance.site = data.site;
269 this.setState(this.state);
275 if (this.state.isLoggedIn) {
276 let repliesForm: GetRepliesForm = {
277 sort: SortType[SortType.New],
283 let userMentionsForm: GetUserMentionsForm = {
284 sort: SortType[SortType.New],
290 let privateMessagesForm: GetPrivateMessagesForm = {
296 if (this.currentLocation !== '/inbox') {
297 WebSocketService.Instance.getReplies(repliesForm);
298 WebSocketService.Instance.getUserMentions(userMentionsForm);
299 WebSocketService.Instance.getPrivateMessages(privateMessagesForm);
304 get currentLocation() {
305 return this.context.router.history.location.pathname;
309 UserService.Instance.user.unreadCount = this.state.unreadCount;
310 UserService.Instance.sub.next({
311 user: UserService.Instance.user,
315 calculateUnreadCount(): number {
317 this.state.replies.filter(r => !r.read).length +
318 this.state.mentions.filter(r => !r.read).length +
319 this.state.messages.filter(r => !r.read).length
323 requestNotificationPermission() {
324 if (UserService.Instance.user) {
325 document.addEventListener('DOMContentLoaded', function() {
327 toast(i18n.t('notifications_error'), 'danger');
331 if (Notification.permission !== 'granted')
332 Notification.requestPermission();
337 notify(reply: Comment | PrivateMessage) {
338 if (Notification.permission !== 'granted') Notification.requestPermission();
340 var notification = new Notification(reply.creator_name, {
341 icon: reply.creator_avatar
342 ? reply.creator_avatar
343 : `${window.location.protocol}//${window.location.host}/static/assets/apple-touch-icon.png`,
344 body: `${reply.content}`,
347 notification.onclick = () => {
348 this.context.router.history.push(
350 ? `/post/${reply.post_id}/comment/${reply.id}`