Reworking some UI. Adding proper trending communities with hot rank.
- Breaking out subscribed and all into radios. Fixes #142
This commit is contained in:
parent
17a4a508cd
commit
1eb587d233
13 changed files with 110 additions and 35 deletions
|
@ -46,7 +46,8 @@ Each lemmy server can set its own moderation policy; appointing site-wide admins
|
||||||
|
|
||||||
## Why's it called Lemmy?
|
## Why's it called Lemmy?
|
||||||
- Lead singer from [motorhead](https://invidio.us/watch?v=pWB5JZRGl0U).
|
- Lead singer from [motorhead](https://invidio.us/watch?v=pWB5JZRGl0U).
|
||||||
- The old school [video game](https://en.wikipedia.org/wiki/Lemmings_(video_game)).
|
- The old school [video game](<https://en.wikipedia.org/wiki/Lemmings_(video_game)>).
|
||||||
|
- The [Koopa from Super Mario](https://www.mariowiki.com/Lemmy_Koopa).
|
||||||
- The [furry rodents](http://sunchild.fpwc.org/lemming-the-little-giant-of-the-north/).
|
- The [furry rodents](http://sunchild.fpwc.org/lemming-the-little-giant-of-the-north/).
|
||||||
|
|
||||||
Made with [Rust](https://www.rust-lang.org), [Actix](https://actix.rs/), [Inferno](https://www.infernojs.org), [Typescript](https://www.typescriptlang.org/) and [Diesel](http://diesel.rs/).
|
Made with [Rust](https://www.rust-lang.org), [Actix](https://actix.rs/), [Inferno](https://www.infernojs.org), [Typescript](https://www.typescriptlang.org/) and [Diesel](http://diesel.rs/).
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
drop view community_view;
|
||||||
|
create view community_view as
|
||||||
|
with all_community as
|
||||||
|
(
|
||||||
|
select *,
|
||||||
|
(select name from user_ u where c.creator_id = u.id) as creator_name,
|
||||||
|
(select name from category ct where c.category_id = ct.id) as category_name,
|
||||||
|
(select count(*) from community_follower cf where cf.community_id = c.id) as number_of_subscribers,
|
||||||
|
(select count(*) from post p where p.community_id = c.id) as number_of_posts,
|
||||||
|
(select count(*) from comment co, post p where c.id = p.community_id and p.id = co.post_id) as number_of_comments
|
||||||
|
from community c
|
||||||
|
)
|
||||||
|
|
||||||
|
select
|
||||||
|
ac.*,
|
||||||
|
u.id as user_id,
|
||||||
|
(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.id = cf.community_id) as subscribed
|
||||||
|
from user_ u
|
||||||
|
cross join all_community ac
|
||||||
|
|
||||||
|
union all
|
||||||
|
|
||||||
|
select
|
||||||
|
ac.*,
|
||||||
|
null as user_id,
|
||||||
|
null as subscribed
|
||||||
|
from all_community ac
|
||||||
|
;
|
|
@ -0,0 +1,29 @@
|
||||||
|
drop view community_view;
|
||||||
|
create view community_view as
|
||||||
|
with all_community as
|
||||||
|
(
|
||||||
|
select *,
|
||||||
|
(select name from user_ u where c.creator_id = u.id) as creator_name,
|
||||||
|
(select name from category ct where c.category_id = ct.id) as category_name,
|
||||||
|
(select count(*) from community_follower cf where cf.community_id = c.id) as number_of_subscribers,
|
||||||
|
(select count(*) from post p where p.community_id = c.id) as number_of_posts,
|
||||||
|
(select count(*) from comment co, post p where c.id = p.community_id and p.id = co.post_id) as number_of_comments,
|
||||||
|
hot_rank((select count(*) from community_follower cf where cf.community_id = c.id), c.published) as hot_rank
|
||||||
|
from community c
|
||||||
|
)
|
||||||
|
|
||||||
|
select
|
||||||
|
ac.*,
|
||||||
|
u.id as user_id,
|
||||||
|
(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.id = cf.community_id) as subscribed
|
||||||
|
from user_ u
|
||||||
|
cross join all_community ac
|
||||||
|
|
||||||
|
union all
|
||||||
|
|
||||||
|
select
|
||||||
|
ac.*,
|
||||||
|
null as user_id,
|
||||||
|
null as subscribed
|
||||||
|
from all_community ac
|
||||||
|
;
|
|
@ -21,6 +21,7 @@ table! {
|
||||||
number_of_subscribers -> BigInt,
|
number_of_subscribers -> BigInt,
|
||||||
number_of_posts -> BigInt,
|
number_of_posts -> BigInt,
|
||||||
number_of_comments -> BigInt,
|
number_of_comments -> BigInt,
|
||||||
|
hot_rank -> Int4,
|
||||||
user_id -> Nullable<Int4>,
|
user_id -> Nullable<Int4>,
|
||||||
subscribed -> Nullable<Bool>,
|
subscribed -> Nullable<Bool>,
|
||||||
}
|
}
|
||||||
|
@ -92,6 +93,7 @@ pub struct CommunityView {
|
||||||
pub number_of_subscribers: i64,
|
pub number_of_subscribers: i64,
|
||||||
pub number_of_posts: i64,
|
pub number_of_posts: i64,
|
||||||
pub number_of_comments: i64,
|
pub number_of_comments: i64,
|
||||||
|
pub hot_rank: i32,
|
||||||
pub user_id: Option<i32>,
|
pub user_id: Option<i32>,
|
||||||
pub subscribed: Option<bool>,
|
pub subscribed: Option<bool>,
|
||||||
}
|
}
|
||||||
|
@ -127,6 +129,7 @@ impl CommunityView {
|
||||||
|
|
||||||
// The view lets you pass a null user_id, if you're not logged in
|
// The view lets you pass a null user_id, if you're not logged in
|
||||||
match sort {
|
match sort {
|
||||||
|
SortType::Hot => query = query.order_by(hot_rank.desc()).filter(user_id.is_null()),
|
||||||
SortType::New => query = query.order_by(published.desc()).filter(user_id.is_null()),
|
SortType::New => query = query.order_by(published.desc()).filter(user_id.is_null()),
|
||||||
SortType::TopAll => {
|
SortType::TopAll => {
|
||||||
match from_user_id {
|
match from_user_id {
|
||||||
|
|
|
@ -573,6 +573,7 @@ impl ChatServer {
|
||||||
fn check_rate_limit_full(&mut self, addr: usize, rate: i32, per: i32) -> Result<(), Error> {
|
fn check_rate_limit_full(&mut self, addr: usize, rate: i32, per: i32) -> Result<(), Error> {
|
||||||
if let Some(info) = self.sessions.get(&addr) {
|
if let Some(info) = self.sessions.get(&addr) {
|
||||||
if let Some(rate_limit) = self.rate_limits.get_mut(&info.ip) {
|
if let Some(rate_limit) = self.rate_limits.get_mut(&info.ip) {
|
||||||
|
// The initial value
|
||||||
if rate_limit.allowance == -2f64 {
|
if rate_limit.allowance == -2f64 {
|
||||||
rate_limit.allowance = rate as f64;
|
rate_limit.allowance = rate as f64;
|
||||||
};
|
};
|
||||||
|
@ -625,7 +626,7 @@ impl Handler<Connect> for ChatServer {
|
||||||
|
|
||||||
// register session with random id
|
// register session with random id
|
||||||
let id = self.rng.gen::<usize>();
|
let id = self.rng.gen::<usize>();
|
||||||
println!("{} Joined", &msg.ip);
|
println!("{} joined", &msg.ip);
|
||||||
|
|
||||||
self.sessions.insert(id, SessionInfo {
|
self.sessions.insert(id, SessionInfo {
|
||||||
addr: msg.addr,
|
addr: msg.addr,
|
||||||
|
|
|
@ -66,17 +66,17 @@ export class Communities extends Component<any, CommunitiesState> {
|
||||||
{this.state.loading ?
|
{this.state.loading ?
|
||||||
<h5 class=""><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h5> :
|
<h5 class=""><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h5> :
|
||||||
<div>
|
<div>
|
||||||
<h5>Communities</h5>
|
<h5>List of communities</h5>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table id="community_table" class="table table-sm table-hover">
|
<table id="community_table" class="table table-sm table-hover">
|
||||||
<thead class="pointer">
|
<thead class="pointer">
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Title</th>
|
<th class="d-none d-lg-table-cell">Title</th>
|
||||||
<th>Category</th>
|
<th>Category</th>
|
||||||
<th class="text-right d-none d-md-table-cell">Subscribers</th>
|
<th class="text-right">Subscribers</th>
|
||||||
<th class="text-right d-none d-md-table-cell">Posts</th>
|
<th class="text-right d-none d-lg-table-cell">Posts</th>
|
||||||
<th class="text-right d-none d-md-table-cell">Comments</th>
|
<th class="text-right d-none d-lg-table-cell">Comments</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -84,11 +84,11 @@ export class Communities extends Component<any, CommunitiesState> {
|
||||||
{this.state.communities.map(community =>
|
{this.state.communities.map(community =>
|
||||||
<tr>
|
<tr>
|
||||||
<td><Link to={`/c/${community.name}`}>{community.name}</Link></td>
|
<td><Link to={`/c/${community.name}`}>{community.name}</Link></td>
|
||||||
<td>{community.title}</td>
|
<td class="d-none d-lg-table-cell">{community.title}</td>
|
||||||
<td>{community.category_name}</td>
|
<td>{community.category_name}</td>
|
||||||
<td class="text-right d-none d-md-table-cell">{community.number_of_subscribers}</td>
|
<td class="text-right">{community.number_of_subscribers}</td>
|
||||||
<td class="text-right d-none d-md-table-cell">{community.number_of_posts}</td>
|
<td class="text-right d-none d-lg-table-cell">{community.number_of_posts}</td>
|
||||||
<td class="text-right d-none d-md-table-cell">{community.number_of_comments}</td>
|
<td class="text-right d-none d-lg-table-cell">{community.number_of_comments}</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
{community.subscribed ?
|
{community.subscribed ?
|
||||||
<span class="pointer btn-link" onClick={linkEvent(community.id, this.handleUnsubscribe)}>Unsubscribe</span> :
|
<span class="pointer btn-link" onClick={linkEvent(community.id, this.handleUnsubscribe)}>Unsubscribe</span> :
|
||||||
|
|
|
@ -124,7 +124,7 @@ export class Community extends Component<any, State> {
|
||||||
selects() {
|
selects() {
|
||||||
return (
|
return (
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
<select value={this.state.sort} onChange={linkEvent(this, this.handleSortChange)} class="custom-select w-auto">
|
<select value={this.state.sort} onChange={linkEvent(this, this.handleSortChange)} class="custom-select custom-select-sm w-auto">
|
||||||
<option disabled>Sort Type</option>
|
<option disabled>Sort Type</option>
|
||||||
<option value={SortType.Hot}>Hot</option>
|
<option value={SortType.Hot}>Hot</option>
|
||||||
<option value={SortType.New}>New</option>
|
<option value={SortType.New}>New</option>
|
||||||
|
|
|
@ -80,12 +80,12 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
selects() {
|
selects() {
|
||||||
return (
|
return (
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
<select value={this.state.unreadType} onChange={linkEvent(this, this.handleUnreadTypeChange)} class="custom-select w-auto">
|
<select value={this.state.unreadType} onChange={linkEvent(this, this.handleUnreadTypeChange)} class="custom-select custom-select-sm w-auto">
|
||||||
<option disabled>Type</option>
|
<option disabled>Type</option>
|
||||||
<option value={UnreadType.Unread}>Unread</option>
|
<option value={UnreadType.Unread}>Unread</option>
|
||||||
<option value={UnreadType.All}>All</option>
|
<option value={UnreadType.All}>All</option>
|
||||||
</select>
|
</select>
|
||||||
<select value={this.state.sort} onChange={linkEvent(this, this.handleSortChange)} class="custom-select w-auto ml-2">
|
<select value={this.state.sort} onChange={linkEvent(this, this.handleSortChange)} class="custom-select custom-select-sm w-auto ml-2">
|
||||||
<option disabled>Sort Type</option>
|
<option disabled>Sort Type</option>
|
||||||
<option value={SortType.New}>New</option>
|
<option value={SortType.New}>New</option>
|
||||||
<option value={SortType.TopDay}>Top Day</option>
|
<option value={SortType.TopDay}>Top Day</option>
|
||||||
|
|
|
@ -95,7 +95,7 @@ export class Login extends Component<any, State> {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
Forgot your password or deleted your account? Reset your password. TODO
|
{/* Forgot your password or deleted your account? Reset your password. TODO */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -161,7 +161,6 @@ export class Login extends Component<any, State> {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
i.state.registerLoading = true;
|
i.state.registerLoading = true;
|
||||||
i.setState(i.state);
|
i.setState(i.state);
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
let endTimer = new Date().getTime();
|
let endTimer = new Date().getTime();
|
||||||
let elapsed = endTimer - i.state.registerForm.spam_timeri;
|
let elapsed = endTimer - i.state.registerForm.spam_timeri;
|
||||||
|
@ -209,14 +208,14 @@ export class Login extends Component<any, State> {
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
if (op == UserOperation.Login) {
|
if (op == UserOperation.Login) {
|
||||||
this.state.loginLoading = false;
|
this.state = this.emptyState;
|
||||||
this.state.registerLoading = false;
|
this.setState(this.state);
|
||||||
let res: LoginResponse = msg;
|
let res: LoginResponse = msg;
|
||||||
UserService.Instance.login(res);
|
UserService.Instance.login(res);
|
||||||
this.props.history.push('/');
|
this.props.history.push('/');
|
||||||
} else if (op == UserOperation.Register) {
|
} else if (op == UserOperation.Register) {
|
||||||
this.state.loginLoading = false;
|
this.state = this.emptyState;
|
||||||
this.state.registerLoading = false;
|
this.setState(this.state);
|
||||||
let res: LoginResponse = msg;
|
let res: LoginResponse = msg;
|
||||||
UserService.Instance.login(res);
|
UserService.Instance.login(res);
|
||||||
this.props.history.push('/communities');
|
this.props.history.push('/communities');
|
||||||
|
|
|
@ -87,7 +87,7 @@ export class Main extends Component<any, MainState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let listCommunitiesForm: ListCommunitiesForm = {
|
let listCommunitiesForm: ListCommunitiesForm = {
|
||||||
sort: SortType[SortType.New],
|
sort: SortType[SortType.Hot],
|
||||||
limit: 6
|
limit: 6
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,7 +247,29 @@ export class Main extends Component<any, MainState> {
|
||||||
selects() {
|
selects() {
|
||||||
return (
|
return (
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
<select value={this.state.sort} onChange={linkEvent(this, this.handleSortChange)} class="custom-select w-auto">
|
<div class="btn-group btn-group-toggle">
|
||||||
|
<label className={`btn btn-sm btn-secondary
|
||||||
|
${this.state.type_ == ListingType.Subscribed && 'active'}
|
||||||
|
${UserService.Instance.user == undefined ? 'disabled' : 'pointer'}
|
||||||
|
`}>
|
||||||
|
<input type="radio"
|
||||||
|
value={ListingType.Subscribed}
|
||||||
|
checked={this.state.type_ == ListingType.Subscribed}
|
||||||
|
onChange={linkEvent(this, this.handleTypeChange)}
|
||||||
|
disabled={UserService.Instance.user == undefined}
|
||||||
|
/>
|
||||||
|
Subscribed
|
||||||
|
</label>
|
||||||
|
<label className={`pointer btn btn-sm btn-secondary ${this.state.type_ == ListingType.All && 'active'}`}>
|
||||||
|
<input type="radio"
|
||||||
|
value={ListingType.All}
|
||||||
|
checked={this.state.type_ == ListingType.All}
|
||||||
|
onChange={linkEvent(this, this.handleTypeChange)}
|
||||||
|
/>
|
||||||
|
All
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<select value={this.state.sort} onChange={linkEvent(this, this.handleSortChange)} class="ml-2 custom-select custom-select-sm w-auto">
|
||||||
<option disabled>Sort Type</option>
|
<option disabled>Sort Type</option>
|
||||||
<option value={SortType.Hot}>Hot</option>
|
<option value={SortType.Hot}>Hot</option>
|
||||||
<option value={SortType.New}>New</option>
|
<option value={SortType.New}>New</option>
|
||||||
|
@ -258,14 +280,6 @@ export class Main extends Component<any, MainState> {
|
||||||
<option value={SortType.TopYear}>Year</option>
|
<option value={SortType.TopYear}>Year</option>
|
||||||
<option value={SortType.TopAll}>All</option>
|
<option value={SortType.TopAll}>All</option>
|
||||||
</select>
|
</select>
|
||||||
{ UserService.Instance.user &&
|
|
||||||
<select value={this.state.type_} onChange={linkEvent(this, this.handleTypeChange)} class="ml-2 custom-select w-auto">
|
|
||||||
<option disabled>Type</option>
|
|
||||||
<option value={ListingType.All}>All</option>
|
|
||||||
<option value={ListingType.Subscribed}>Subscribed</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,13 +97,13 @@ export class Search extends Component<any, SearchState> {
|
||||||
selects() {
|
selects() {
|
||||||
return (
|
return (
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
<select value={this.state.type_} onChange={linkEvent(this, this.handleTypeChange)} class="custom-select w-auto">
|
<select value={this.state.type_} onChange={linkEvent(this, this.handleTypeChange)} class="custom-select custom-select-sm w-auto">
|
||||||
<option disabled>Type</option>
|
<option disabled>Type</option>
|
||||||
<option value={SearchType.Both}>Both</option>
|
<option value={SearchType.Both}>Both</option>
|
||||||
<option value={SearchType.Comments}>Comments</option>
|
<option value={SearchType.Comments}>Comments</option>
|
||||||
<option value={SearchType.Posts}>Posts</option>
|
<option value={SearchType.Posts}>Posts</option>
|
||||||
</select>
|
</select>
|
||||||
<select value={this.state.sort} onChange={linkEvent(this, this.handleSortChange)} class="custom-select w-auto ml-2">
|
<select value={this.state.sort} onChange={linkEvent(this, this.handleSortChange)} class="custom-select custom-select-sm w-auto ml-2">
|
||||||
<option disabled>Sort Type</option>
|
<option disabled>Sort Type</option>
|
||||||
<option value={SortType.New}>New</option>
|
<option value={SortType.New}>New</option>
|
||||||
<option value={SortType.TopDay}>Top Day</option>
|
<option value={SortType.TopDay}>Top Day</option>
|
||||||
|
|
|
@ -141,14 +141,14 @@ export class User extends Component<any, UserState> {
|
||||||
selects() {
|
selects() {
|
||||||
return (
|
return (
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
<select value={this.state.view} onChange={linkEvent(this, this.handleViewChange)} class="custom-select w-auto">
|
<select value={this.state.view} onChange={linkEvent(this, this.handleViewChange)} class="custom-select custom-select-sm w-auto">
|
||||||
<option disabled>View</option>
|
<option disabled>View</option>
|
||||||
<option value={View.Overview}>Overview</option>
|
<option value={View.Overview}>Overview</option>
|
||||||
<option value={View.Comments}>Comments</option>
|
<option value={View.Comments}>Comments</option>
|
||||||
<option value={View.Posts}>Posts</option>
|
<option value={View.Posts}>Posts</option>
|
||||||
<option value={View.Saved}>Saved</option>
|
<option value={View.Saved}>Saved</option>
|
||||||
</select>
|
</select>
|
||||||
<select value={this.state.sort} onChange={linkEvent(this, this.handleSortChange)} class="custom-select w-auto ml-2">
|
<select value={this.state.sort} onChange={linkEvent(this, this.handleSortChange)} class="custom-select custom-select-sm w-auto ml-2">
|
||||||
<option disabled>Sort Type</option>
|
<option disabled>Sort Type</option>
|
||||||
<option value={SortType.New}>New</option>
|
<option value={SortType.New}>New</option>
|
||||||
<option value={SortType.TopDay}>Top Day</option>
|
<option value={SortType.TopDay}>Top Day</option>
|
||||||
|
|
|
@ -36,7 +36,7 @@ class Index extends Component<any, any> {
|
||||||
return (
|
return (
|
||||||
<HashRouter>
|
<HashRouter>
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<div class="mt-3 p-0">
|
<div class="mt-1 p-0">
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path={`/home/type/:type/sort/:sort/page/:page`} component={Main} />
|
<Route path={`/home/type/:type/sort/:sort/page/:page`} component={Main} />
|
||||||
<Route exact path={`/`} component={Main} />
|
<Route exact path={`/`} component={Main} />
|
||||||
|
|
Reference in a new issue