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