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';
6 import { UserOperation, GetRepliesForm, GetRepliesResponse, SortType, GetSiteResponse, Comment} from '../interfaces';
7 import { msgOp } from '../utils';
8 import { version } from '../version';
9 import { i18n } from '../i18next';
10 import { T } from 'inferno-i18next';
12 interface NavbarState {
15 expandUserDropdown: boolean;
16 replies: Array<Comment>,
22 export class Navbar extends Component<any, NavbarState> {
23 private wsSub: Subscription;
24 private userSub: Subscription;
25 emptyState: NavbarState = {
26 isLoggedIn: (UserService.Instance.user !== undefined),
31 expandUserDropdown: false,
35 constructor(props: any, context: any) {
36 super(props, context);
37 this.state = this.emptyState;
38 this.handleOverviewClick = this.handleOverviewClick.bind(this);
40 this.keepFetchingReplies();
42 // Subscribe to user changes
43 this.userSub = UserService.Instance.sub.subscribe(user => {
44 this.state.isLoggedIn = user.user !== undefined;
45 this.state.unreadCount = user.unreadCount;
46 this.requestNotificationPermission();
47 this.setState(this.state);
50 this.wsSub = WebSocketService.Instance.subject
51 .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
53 (msg) => this.parseMessage(msg),
54 (err) => console.error(err),
55 () => console.log('complete')
58 if (this.state.isLoggedIn) {
59 this.requestNotificationPermission();
62 WebSocketService.Instance.getSite();
67 <div>{this.navbar()}</div>
71 componentWillUnmount() {
72 this.wsSub.unsubscribe();
73 this.userSub.unsubscribe();
76 // TODO class active corresponding to current page
79 <nav class="container-fluid navbar navbar-expand-md navbar-light shadow p-0 px-3">
80 <Link title={version} class="navbar-brand" to="/">
83 <button class="navbar-toggler" type="button" onClick={linkEvent(this, this.expandNavbar)}>
84 <span class="navbar-toggler-icon"></span>
86 <div className={`${!this.state.expanded && 'collapse'} navbar-collapse`}>
87 <ul class="navbar-nav mr-auto">
89 <Link class="nav-link" to="/communities"><T i18nKey="communities">#</T></Link>
92 <Link class="nav-link" to="/search"><T i18nKey="search">#</T></Link>
95 <Link class="nav-link" to={{pathname: '/create_post', state: { prevPath: this.currentLocation }}}><T i18nKey="create_post">#</T></Link>
98 <Link class="nav-link" to="/create_community"><T i18nKey="create_community">#</T></Link>
101 <ul class="navbar-nav ml-auto mr-2">
102 {this.state.isLoggedIn ?
105 <li className="nav-item">
106 <Link class="nav-link" to="/inbox">
107 <svg class="icon"><use xlinkHref="#icon-mail"></use></svg>
108 {this.state.unreadCount> 0 && <span class="ml-1 badge badge-light">{this.state.unreadCount}</span>}
112 <li className={`nav-item dropdown ${this.state.expandUserDropdown && 'show'}`}>
113 <a class="pointer nav-link dropdown-toggle" onClick={linkEvent(this, this.expandUserDropdown)} role="button">
114 {UserService.Instance.user.username}
116 <div className={`dropdown-menu dropdown-menu-right ${this.state.expandUserDropdown && 'show'}`}>
117 <a role="button" class="dropdown-item pointer" onClick={linkEvent(this, this.handleOverviewClick)}><T i18nKey="overview">#</T></a>
118 <a role="button" class="dropdown-item pointer" onClick={ linkEvent(this, this.handleLogoutClick) }><T i18nKey="logout">#</T></a>
123 <Link class="nav-link" to="/login"><T i18nKey="login_sign_up">#</T></Link>
131 expandUserDropdown(i: Navbar) {
132 i.state.expandUserDropdown = !i.state.expandUserDropdown;
136 handleLogoutClick(i: Navbar) {
137 i.state.expandUserDropdown = false;
138 UserService.Instance.logout();
139 i.context.router.history.push('/');
142 handleOverviewClick(i: Navbar) {
143 i.state.expandUserDropdown = false;
145 let userPage = `/u/${UserService.Instance.user.username}`;
146 i.context.router.history.push(userPage);
149 expandNavbar(i: Navbar) {
150 i.state.expanded = !i.state.expanded;
154 parseMessage(msg: any) {
155 let op: UserOperation = msgOp(msg);
157 if (msg.error == "not_logged_in") {
158 UserService.Instance.logout();
162 } else if (op == UserOperation.GetReplies) {
163 let res: GetRepliesResponse = msg;
164 let unreadReplies = res.replies.filter(r => !r.read);
165 if (unreadReplies.length > 0 && this.state.fetchCount > 1 &&
166 (JSON.stringify(this.state.replies) !== JSON.stringify(unreadReplies))) {
167 this.notify(unreadReplies);
170 this.state.replies = unreadReplies;
171 this.sendRepliesCount(res);
172 } else if (op == UserOperation.GetSite) {
173 let res: GetSiteResponse = msg;
176 this.state.siteName = res.site.name;
177 WebSocketService.Instance.site = res.site;
178 this.setState(this.state);
183 keepFetchingReplies() {
185 setInterval(() => this.fetchReplies(), 30000);
189 if (this.state.isLoggedIn) {
190 let repliesForm: GetRepliesForm = {
191 sort: SortType[SortType.New],
196 WebSocketService.Instance.getReplies(repliesForm);
197 this.state.fetchCount++;
201 get currentLocation() {
202 return this.context.router.history.location.pathname;
205 sendRepliesCount(res: GetRepliesResponse) {
206 UserService.Instance.sub.next({user: UserService.Instance.user, unreadCount: res.replies.filter(r => !r.read).length});
209 requestNotificationPermission() {
210 if (UserService.Instance.user) {
211 document.addEventListener('DOMContentLoaded', function () {
213 alert(i18n.t('notifications_error'));
217 if (Notification.permission !== 'granted')
218 Notification.requestPermission();
223 notify(replies: Array<Comment>) {
224 let recentReply = replies[0];
225 if (Notification.permission !== 'granted')
226 Notification.requestPermission();
228 var notification = new Notification(`${replies.length} ${i18n.t('unread_messages')}`, {
229 icon: `${window.location.protocol}//${window.location.host}/static/assets/apple-touch-icon.png`,
230 body: `${recentReply.creator_name}: ${recentReply.content}`
233 notification.onclick = () => {
234 this.context.router.history.push(`/post/${recentReply.post_id}/comment/${recentReply.id}`);