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 { UserOperation, CommunityUser, GetFollowedCommunitiesResponse, ListCommunitiesForm, ListCommunitiesResponse, Community, SortType, GetSiteResponse, ListingType, SiteResponse, GetPostsResponse, CreatePostLikeResponse, Post, GetPostsForm } from '../interfaces';
6 import { WebSocketService, UserService } from '../services';
7 import { PostListings } from './post-listings';
8 import { SiteForm } from './site-form';
9 import { msgOp, repoUrl, mdToHtml, fetchLimit, routeSortTypeToEnum, routeListingTypeToEnum } from '../utils';
10 import { i18n } from '../i18next';
11 import { T } from 'inferno-i18next';
14 subscribedCommunities: Array<CommunityUser>;
15 trendingCommunities: Array<Community>;
16 site: GetSiteResponse;
17 showEditSite: boolean;
25 export class Main extends Component<any, MainState> {
27 private subscription: Subscription;
28 private emptyState: MainState = {
29 subscribedCommunities: [],
30 trendingCommunities: [],
39 number_of_users: null,
40 number_of_posts: null,
41 number_of_comments: null,
42 number_of_communities: null,
50 type_: this.getListingTypeFromProps(this.props),
51 sort: this.getSortTypeFromProps(this.props),
52 page: this.getPageFromProps(this.props),
55 getListingTypeFromProps(props: any): ListingType {
56 return (props.match.params.type) ?
57 routeListingTypeToEnum(props.match.params.type) :
58 UserService.Instance.user ?
59 ListingType.Subscribed :
63 getSortTypeFromProps(props: any): SortType {
64 return (props.match.params.sort) ?
65 routeSortTypeToEnum(props.match.params.sort) :
69 getPageFromProps(props: any): number {
70 return (props.match.params.page) ? Number(props.match.params.page) : 1;
73 constructor(props: any, context: any) {
74 super(props, context);
76 this.state = this.emptyState;
77 this.handleEditCancel = this.handleEditCancel.bind(this);
79 this.subscription = WebSocketService.Instance.subject
80 .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
82 (msg) => this.parseMessage(msg),
83 (err) => console.error(err),
84 () => console.log('complete')
87 WebSocketService.Instance.getSite();
89 if (UserService.Instance.user) {
90 WebSocketService.Instance.getFollowedCommunities();
93 let listCommunitiesForm: ListCommunitiesForm = {
94 sort: SortType[SortType.Hot],
98 WebSocketService.Instance.listCommunities(listCommunitiesForm);
103 componentWillUnmount() {
104 this.subscription.unsubscribe();
107 // Necessary for back button for some reason
108 componentWillReceiveProps(nextProps: any) {
109 if (nextProps.history.action == 'POP') {
110 this.state = this.emptyState;
111 this.state.type_ = this.getListingTypeFromProps(nextProps);
112 this.state.sort = this.getSortTypeFromProps(nextProps);
113 this.state.page = this.getPageFromProps(nextProps);
120 <div class="container">
122 <div class="col-12 col-md-8">
125 <div class="col-12 col-md-4">
136 {!this.state.loading &&
138 <div class="card border-secondary mb-3">
139 <div class="card-body">
140 {this.trendingCommunities()}
141 {UserService.Instance.user && this.state.subscribedCommunities.length > 0 &&
144 <T i18nKey="subscribed_to_communities">#<Link class="text-white" to="/communities">#</Link></T>
146 <ul class="list-inline">
147 {this.state.subscribedCommunities.map(community =>
148 <li class="list-inline-item"><Link to={`/c/${community.community_name}`}>{community.community_name}</Link></li>
153 <Link class="btn btn-sm btn-secondary btn-block"
154 to="/create_community">
155 <T i18nKey="create_a_community">#</T>
167 trendingCommunities() {
171 <T i18nKey="trending_communities">#<Link class="text-white" to="/communities">#</Link></T>
173 <ul class="list-inline">
174 {this.state.trendingCommunities.map(community =>
175 <li class="list-inline-item"><Link to={`/c/${community.name}`}>{community.name}</Link></li>
185 {!this.state.showEditSite ?
188 site={this.state.site.site}
189 onCancel={this.handleEditCancel}
197 let typeStr = ListingType[this.state.type_].toLowerCase();
198 let sortStr = SortType[this.state.sort].toLowerCase();
199 this.props.history.push(`/home/type/${typeStr}/sort/${sortStr}/page/${this.state.page}`);
205 <div class="card border-secondary mb-3">
206 <div class="card-body">
207 <h5 class="mb-0">{`${this.state.site.site.name}`}</h5>
209 <ul class="list-inline mb-1 text-muted small font-weight-bold">
210 <li className="list-inline-item">
211 <span class="pointer" onClick={linkEvent(this, this.handleEditClick)}>
212 <T i18nKey="edit">#</T>
217 <ul class="my-2 list-inline">
218 <li className="list-inline-item badge badge-secondary">
219 <T i18nKey="number_of_users" interpolation={{count: this.state.site.site.number_of_users}}>#</T>
221 <li className="list-inline-item badge badge-secondary">
222 <T i18nKey="number_of_communities" interpolation={{count: this.state.site.site.number_of_communities}}>#</T>
224 <li className="list-inline-item badge badge-secondary">
225 <T i18nKey="number_of_posts" interpolation={{count: this.state.site.site.number_of_posts}}>#</T>
227 <li className="list-inline-item badge badge-secondary">
228 <T i18nKey="number_of_comments" interpolation={{count: this.state.site.site.number_of_comments}}>#</T>
230 <li className="list-inline-item">
231 <Link className="badge badge-secondary" to="/modlog">
232 <T i18nKey="modlog">#</T>
236 <ul class="mt-1 list-inline small mb-0">
237 <li class="list-inline-item">
238 <T i18nKey="admins" class="d-inline">#</T>:
240 {this.state.site.admins.map(admin =>
241 <li class="list-inline-item"><Link class="text-info" to={`/u/${admin.name}`}>{admin.name}</Link></li>
246 {this.state.site.site.description &&
247 <div class="card border-secondary mb-3">
248 <div class="card-body">
249 <div className="md-div" dangerouslySetInnerHTML={mdToHtml(this.state.site.site.description)} />
259 <div class="card border-secondary">
260 <div class="card-body">
262 <T i18nKey="powered_by" class="d-inline">#</T>
263 <svg class="icon mx-2"><use xlinkHref="#icon-mouse">#</use></svg>
264 <a href={repoUrl}>Lemmy<sup>beta</sup></a>
267 <T i18nKey="landing_0">#<a href="https://en.wikipedia.org/wiki/Link_aggregation">#</a><a href="https://en.wikipedia.org/wiki/Fediverse">#</a><br></br><code>#</code><br></br><b>#</b><br></br><a href={repoUrl}>#</a><br></br><a href="https://www.rust-lang.org">#</a><a href="https://actix.rs/">#</a><a href="https://infernojs.org">#</a><a href="https://www.typescriptlang.org/">#</a>
278 {this.state.loading ?
279 <h5><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h5> :
282 <PostListings posts={this.state.posts} showCommunity />
292 <div className="mb-3">
293 <div class="btn-group btn-group-toggle">
294 <label className={`btn btn-sm btn-secondary
295 ${this.state.type_ == ListingType.Subscribed && 'active'}
296 ${UserService.Instance.user == undefined ? 'disabled' : 'pointer'}
299 value={ListingType.Subscribed}
300 checked={this.state.type_ == ListingType.Subscribed}
301 onChange={linkEvent(this, this.handleTypeChange)}
302 disabled={UserService.Instance.user == undefined}
304 {i18n.t('subscribed')}
306 <label className={`pointer btn btn-sm btn-secondary ${this.state.type_ == ListingType.All && 'active'}`}>
308 value={ListingType.All}
309 checked={this.state.type_ == ListingType.All}
310 onChange={linkEvent(this, this.handleTypeChange)}
315 <select value={this.state.sort} onChange={linkEvent(this, this.handleSortChange)} class="ml-2 custom-select custom-select-sm w-auto">
316 <option disabled><T i18nKey="sort_type">#</T></option>
317 <option value={SortType.Hot}><T i18nKey="hot">#</T></option>
318 <option value={SortType.New}><T i18nKey="new">#</T></option>
319 <option disabled>─────</option>
320 <option value={SortType.TopDay}><T i18nKey="top_day">#</T></option>
321 <option value={SortType.TopWeek}><T i18nKey="week">#</T></option>
322 <option value={SortType.TopMonth}><T i18nKey="month">#</T></option>
323 <option value={SortType.TopYear}><T i18nKey="year">#</T></option>
324 <option value={SortType.TopAll}><T i18nKey="all">#</T></option>
333 {this.state.page > 1 &&
334 <button class="btn btn-sm btn-secondary mr-1" onClick={linkEvent(this, this.prevPage)}><T i18nKey="prev">#</T></button>
336 <button class="btn btn-sm btn-secondary" onClick={linkEvent(this, this.nextPage)}><T i18nKey="next">#</T></button>
341 get canAdmin(): boolean {
342 return UserService.Instance.user && this.state.site.admins.map(a => a.id).includes(UserService.Instance.user.id);
345 handleEditClick(i: Main) {
346 i.state.showEditSite = true;
351 this.state.showEditSite = false;
352 this.setState(this.state);
369 handleSortChange(i: Main, event: any) {
370 i.state.sort = Number(event.target.value);
377 handleTypeChange(i: Main, event: any) {
378 i.state.type_ = Number(event.target.value);
386 let getPostsForm: GetPostsForm = {
387 page: this.state.page,
389 sort: SortType[this.state.sort],
390 type_: ListingType[this.state.type_]
392 WebSocketService.Instance.getPosts(getPostsForm);
395 parseMessage(msg: any) {
397 let op: UserOperation = msgOp(msg);
399 alert(i18n.t(msg.error));
401 } else if (op == UserOperation.GetFollowedCommunities) {
402 let res: GetFollowedCommunitiesResponse = msg;
403 this.state.subscribedCommunities = res.communities;
404 this.setState(this.state);
405 } else if (op == UserOperation.ListCommunities) {
406 let res: ListCommunitiesResponse = msg;
407 this.state.trendingCommunities = res.communities;
408 this.setState(this.state);
409 } else if (op == UserOperation.GetSite) {
410 let res: GetSiteResponse = msg;
412 // This means it hasn't been set up yet
414 this.context.router.history.push("/setup");
416 this.state.site.admins = res.admins;
417 this.state.site.site = res.site;
418 this.state.site.banned = res.banned;
419 this.setState(this.state);
420 document.title = `${WebSocketService.Instance.site.name}`;
422 } else if (op == UserOperation.EditSite) {
423 let res: SiteResponse = msg;
424 this.state.site.site = res.site;
425 this.state.showEditSite = false;
426 this.setState(this.state);
427 } else if (op == UserOperation.GetPosts) {
428 let res: GetPostsResponse = msg;
429 this.state.posts = res.posts;
430 this.state.loading = false;
431 window.scrollTo(0,0);
432 this.setState(this.state);
433 } else if (op == UserOperation.CreatePostLike) {
434 let res: CreatePostLikeResponse = msg;
435 let found = this.state.posts.find(c => c.id == res.post.id);
436 found.my_vote = res.post.my_vote;
437 found.score = res.post.score;
438 found.upvotes = res.post.upvotes;
439 found.downvotes = res.post.downvotes;
440 this.setState(this.state);