]> Untitled Git - lemmy.git/blob - ui/src/components/navbar.tsx
Adding emoji support
[lemmy.git] / ui / src / components / navbar.tsx
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
10 interface NavbarState {
11   isLoggedIn: boolean;
12   expanded: boolean;
13   expandUserDropdown: boolean;
14   replies: Array<Comment>,
15   fetchCount: number,
16   unreadCount: number;
17   siteName: string;
18 }
19
20 export class Navbar extends Component<any, NavbarState> {
21   private wsSub: Subscription;
22   private userSub: Subscription;
23   emptyState: NavbarState = {
24     isLoggedIn: (UserService.Instance.user !== undefined),
25     unreadCount: 0,
26     fetchCount: 0,
27     replies: [],
28     expanded: false,
29     expandUserDropdown: false,
30     siteName: undefined
31   }
32
33   constructor(props: any, context: any) {
34     super(props, context);
35     this.state = this.emptyState;
36     this.handleOverviewClick = this.handleOverviewClick.bind(this);
37
38     this.keepFetchingReplies();
39
40     // Subscribe to user changes
41     this.userSub = UserService.Instance.sub.subscribe(user => {
42       this.state.isLoggedIn = user.user !== undefined;
43       this.state.unreadCount = user.unreadCount;
44       this.requestNotificationPermission();
45       this.setState(this.state);
46     });
47
48     this.wsSub = WebSocketService.Instance.subject
49     .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
50     .subscribe(
51       (msg) => this.parseMessage(msg),
52         (err) => console.error(err),
53         () => console.log('complete')
54     );
55
56     if (this.state.isLoggedIn) {
57       this.requestNotificationPermission();
58     }
59
60     WebSocketService.Instance.getSite();
61   }
62
63   render() {
64     return (
65       <div>{this.navbar()}</div>
66     )
67   }
68
69   componentWillUnmount() {
70     this.wsSub.unsubscribe();
71     this.userSub.unsubscribe();
72   }
73
74   // TODO class active corresponding to current page
75   navbar() {
76     return (
77       <nav class="container navbar navbar-expand-md navbar-light navbar-bg p-0 px-3">
78         <Link title={version} class="navbar-brand" to="/">
79           <svg class="icon mr-2 mouse-icon"><use xlinkHref="#icon-mouse"></use></svg>
80           {this.state.siteName}
81         </Link>
82         <button class="navbar-toggler" type="button" onClick={linkEvent(this, this.expandNavbar)}>
83           <span class="navbar-toggler-icon"></span>
84         </button>
85         <div className={`${!this.state.expanded && 'collapse'} navbar-collapse`}>
86           <ul class="navbar-nav mr-auto">
87             <li class="nav-item">
88               <Link class="nav-link" to="/communities">Communities</Link>
89             </li>
90             <li class="nav-item">
91               <Link class="nav-link" to="/search">Search</Link>
92             </li>
93             <li class="nav-item">
94               <Link class="nav-link" to={{pathname: '/create_post', state: { prevPath: this.currentLocation }}}>Create Post</Link>
95             </li>
96             <li class="nav-item">
97               <Link class="nav-link" to="/create_community">Create Community</Link>
98             </li>
99           </ul>
100           <ul class="navbar-nav ml-auto mr-2">
101             {this.state.isLoggedIn ? 
102             <>
103               {
104                 <li className="nav-item">
105                   <Link class="inbox nav-link" to="/inbox">
106                     <svg class="icon"><use xlinkHref="#icon-mail"></use></svg>
107                     {this.state.unreadCount> 0 && <span class="ml-1 badge badge-light">{this.state.unreadCount}</span>}
108                   </Link>
109                 </li>
110               }
111               <li className={`nav-item dropdown ${this.state.expandUserDropdown && 'show'}`}>
112                 <a class="pointer nav-link dropdown-toggle" onClick={linkEvent(this, this.expandUserDropdown)} role="button">
113                   {UserService.Instance.user.username}
114                 </a>
115                 <div className={`dropdown-menu dropdown-menu-right ${this.state.expandUserDropdown && 'show'}`}>
116                   <a role="button" class="dropdown-item pointer" onClick={linkEvent(this, this.handleOverviewClick)}>Overview</a>
117                   <a role="button" class="dropdown-item pointer" onClick={ linkEvent(this, this.handleLogoutClick) }>Logout</a>
118                 </div>
119               </li> 
120             </>
121               : 
122               <Link class="nav-link" to="/login">Login / Sign up</Link>
123             }
124           </ul>
125         </div>
126       </nav>
127     );
128   }
129
130   expandUserDropdown(i: Navbar) {
131     i.state.expandUserDropdown = !i.state.expandUserDropdown;
132     i.setState(i.state);
133   }
134
135   handleLogoutClick(i: Navbar) {
136     i.state.expandUserDropdown = false;
137     UserService.Instance.logout();
138     i.context.router.history.push('/');
139   }
140
141   handleOverviewClick(i: Navbar) {
142     i.state.expandUserDropdown = false;
143     i.setState(i.state);
144     let userPage = `/u/${UserService.Instance.user.username}`;
145     i.context.router.history.push(userPage);
146   }
147
148   expandNavbar(i: Navbar) {
149     i.state.expanded = !i.state.expanded;
150     i.setState(i.state);
151   }
152
153   parseMessage(msg: any) {
154     let op: UserOperation = msgOp(msg);
155     if (msg.error) {
156       if (msg.error == "Not logged in.") {
157         UserService.Instance.logout();
158         location.reload();
159       }
160       return;
161     } else if (op == UserOperation.GetReplies) {
162       let res: GetRepliesResponse = msg;
163       let unreadReplies = res.replies.filter(r => !r.read);
164       if (unreadReplies.length > 0 && this.state.fetchCount > 1 && 
165           (JSON.stringify(this.state.replies) !== JSON.stringify(unreadReplies))) {
166         this.notify(unreadReplies);
167       }
168
169       this.state.replies = unreadReplies;
170       this.sendRepliesCount(res);
171     } else if (op == UserOperation.GetSite) {
172       let res: GetSiteResponse = msg;
173
174       if (res.site) {
175         this.state.siteName = res.site.name;
176         WebSocketService.Instance.site = res.site;
177         this.setState(this.state);
178       }
179     } 
180   }
181
182   keepFetchingReplies() {
183     this.fetchReplies();
184     setInterval(() => this.fetchReplies(), 30000);
185   }
186
187   fetchReplies() {
188     if (this.state.isLoggedIn) {
189       let repliesForm: GetRepliesForm = {
190         sort: SortType[SortType.New],
191         unread_only: true,
192         page: 1,
193         limit: 9999,
194       };
195       WebSocketService.Instance.getReplies(repliesForm);
196       this.state.fetchCount++;
197     }
198   }
199
200   get currentLocation() {
201     return this.context.router.history.location.pathname;
202   }
203
204   sendRepliesCount(res: GetRepliesResponse) {
205     UserService.Instance.sub.next({user: UserService.Instance.user, unreadCount: res.replies.filter(r => !r.read).length});
206   }
207
208   requestNotificationPermission() {
209     if (UserService.Instance.user) {
210     document.addEventListener('DOMContentLoaded', function () {
211       if (!Notification) {
212         alert('Desktop notifications not available in your browser. Try Chromium.'); 
213         return;
214       }
215
216       if (Notification.permission !== 'granted')
217         Notification.requestPermission();
218     });
219     }
220   }
221
222   notify(replies: Array<Comment>) {
223     let recentReply = replies[0];
224     if (Notification.permission !== 'granted')
225       Notification.requestPermission();
226     else {
227       var notification = new Notification(`${replies.length} Unread Messages`, {
228         icon: `${window.location.protocol}//${window.location.host}/static/assets/apple-touch-icon.png`,
229         body: `${recentReply.creator_name}: ${recentReply.content}`
230       });
231
232       notification.onclick = () => {
233         this.context.router.history.push(`/post/${recentReply.post_id}/comment/${recentReply.id}`);
234       };
235
236     }
237   }
238 }