import { Component, linkEvent } from "inferno";
import {
CommunityResponse,
- FollowCommunity,
GetSiteResponse,
ListCommunities,
ListCommunitiesResponse,
ListingType,
- SortType,
- SubscribedType,
- UserOperation,
- wsJsonToRes,
- wsUserOp,
} from "lemmy-js-client";
-import { Subscription } from "rxjs";
-import { InitialFetchRequest } from "shared/interfaces";
import { i18n } from "../../i18next";
-import { WebSocketService } from "../../services";
+import { InitialFetchRequest } from "../../interfaces";
+import { FirstLoadService } from "../../services/FirstLoadService";
+import { HttpService, RequestState } from "../../services/HttpService";
import {
- getListingTypeFromPropsNoDefault,
- getPageFromProps,
- isBrowser,
+ QueryParams,
+ editCommunity,
+ getPageFromString,
+ getQueryParams,
+ getQueryString,
myAuth,
+ myAuthRequired,
numToSI,
setIsoData,
showLocal,
- toast,
- wsClient,
- wsSubscribe,
} from "../../utils";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
const communityLimit = 50;
interface CommunitiesState {
- listCommunitiesResponse?: ListCommunitiesResponse;
- page: number;
- loading: boolean;
+ listCommunitiesResponse: RequestState<ListCommunitiesResponse>;
siteRes: GetSiteResponse;
searchText: string;
- listingType: ListingType;
+ isIsomorphic: boolean;
}
interface CommunitiesProps {
- listingType?: ListingType;
- page?: number;
+ listingType: ListingType;
+ page: number;
+}
+
+function getListingTypeFromQuery(listingType?: string): ListingType {
+ return listingType ? (listingType as ListingType) : "Local";
}
export class Communities extends Component<any, CommunitiesState> {
- private subscription?: Subscription;
private isoData = setIsoData(this.context);
state: CommunitiesState = {
- loading: true,
- page: getPageFromProps(this.props),
- listingType: getListingTypeFromPropsNoDefault(this.props),
+ listCommunitiesResponse: { state: "empty" },
siteRes: this.isoData.site_res,
searchText: "",
+ isIsomorphic: false,
};
constructor(props: any, context: any) {
this.handlePageChange = this.handlePageChange.bind(this);
this.handleListingTypeChange = this.handleListingTypeChange.bind(this);
- this.parseMessage = this.parseMessage.bind(this);
- this.subscription = wsSubscribe(this.parseMessage);
-
// Only fetch the data if coming from another route
- if (this.isoData.path == this.context.router.route.match.url) {
- let listRes = this.isoData.routeData[0] as ListCommunitiesResponse;
+ if (FirstLoadService.isFirstLoad) {
this.state = {
...this.state,
- listCommunitiesResponse: listRes,
- loading: false,
+ listCommunitiesResponse: this.isoData.routeData[0],
+ isIsomorphic: true,
};
- } else {
- this.refetch();
}
}
- componentWillUnmount() {
- if (isBrowser()) {
- this.subscription?.unsubscribe();
- }
- }
-
- static getDerivedStateFromProps(props: any): CommunitiesProps {
- return {
- listingType: getListingTypeFromPropsNoDefault(props),
- page: getPageFromProps(props),
- };
- }
-
- componentDidUpdate(_: any, lastState: CommunitiesState) {
- if (
- lastState.page !== this.state.page ||
- lastState.listingType !== this.state.listingType
- ) {
- this.setState({ loading: true });
- this.refetch();
+ async componentDidMount() {
+ if (!this.state.isIsomorphic) {
+ await this.refetch();
}
}
}`;
}
- render() {
- return (
- <div className="container-lg">
- <HtmlTags
- title={this.documentTitle}
- path={this.context.router.route.match.url}
- />
- {this.state.loading ? (
+ renderListings() {
+ switch (this.state.listCommunitiesResponse.state) {
+ case "loading":
+ return (
<h5>
<Spinner large />
</h5>
- ) : (
+ );
+ case "success": {
+ const { listingType, page } = this.getCommunitiesQueryParams();
+ return (
<div>
<div className="row">
<div className="col-md-6">
<h4>{i18n.t("list_of_communities")}</h4>
<span className="mb-2">
<ListingTypeSelect
- type_={this.state.listingType}
+ type_={listingType}
showLocal={showLocal(this.isoData)}
showSubscribed
onChange={this.handleListingTypeChange}
</tr>
</thead>
<tbody>
- {this.state.listCommunitiesResponse?.communities.map(cv => (
- <tr key={cv.community.id}>
- <td>
- <CommunityLink community={cv.community} />
- </td>
- <td className="text-right">
- {numToSI(cv.counts.subscribers)}
- </td>
- <td className="text-right">
- {numToSI(cv.counts.users_active_month)}
- </td>
- <td className="text-right d-none d-lg-table-cell">
- {numToSI(cv.counts.posts)}
- </td>
- <td className="text-right d-none d-lg-table-cell">
- {numToSI(cv.counts.comments)}
- </td>
- <td className="text-right">
- {cv.subscribed == SubscribedType.Subscribed && (
- <button
- className="btn btn-link d-inline-block"
- onClick={linkEvent(
- cv.community.id,
- this.handleUnsubscribe
- )}
- >
- {i18n.t("unsubscribe")}
- </button>
- )}
- {cv.subscribed == SubscribedType.NotSubscribed && (
- <button
- className="btn btn-link d-inline-block"
- onClick={linkEvent(
- cv.community.id,
- this.handleSubscribe
- )}
- >
- {i18n.t("subscribe")}
- </button>
- )}
- {cv.subscribed == SubscribedType.Pending && (
- <div className="text-warning d-inline-block">
- {i18n.t("subscribe_pending")}
- </div>
- )}
- </td>
- </tr>
- ))}
+ {this.state.listCommunitiesResponse.data.communities.map(
+ cv => (
+ <tr key={cv.community.id}>
+ <td>
+ <CommunityLink community={cv.community} />
+ </td>
+ <td className="text-right">
+ {numToSI(cv.counts.subscribers)}
+ </td>
+ <td className="text-right">
+ {numToSI(cv.counts.users_active_month)}
+ </td>
+ <td className="text-right d-none d-lg-table-cell">
+ {numToSI(cv.counts.posts)}
+ </td>
+ <td className="text-right d-none d-lg-table-cell">
+ {numToSI(cv.counts.comments)}
+ </td>
+ <td className="text-right">
+ {cv.subscribed == "Subscribed" && (
+ <button
+ className="btn btn-link d-inline-block"
+ onClick={linkEvent(
+ {
+ i: this,
+ communityId: cv.community.id,
+ follow: false,
+ },
+ this.handleFollow
+ )}
+ >
+ {i18n.t("unsubscribe")}
+ </button>
+ )}
+ {cv.subscribed === "NotSubscribed" && (
+ <button
+ className="btn btn-link d-inline-block"
+ onClick={linkEvent(
+ {
+ i: this,
+ communityId: cv.community.id,
+ follow: true,
+ },
+ this.handleFollow
+ )}
+ >
+ {i18n.t("subscribe")}
+ </button>
+ )}
+ {cv.subscribed === "Pending" && (
+ <div className="text-warning d-inline-block">
+ {i18n.t("subscribe_pending")}
+ </div>
+ )}
+ </td>
+ </tr>
+ )
+ )}
</tbody>
</table>
</div>
- <Paginator
- page={this.state.page}
- onChange={this.handlePageChange}
- />
+ <Paginator page={page} onChange={this.handlePageChange} />
</div>
- )}
+ );
+ }
+ }
+ }
+
+ render() {
+ return (
+ <div className="container-lg">
+ <HtmlTags
+ title={this.documentTitle}
+ path={this.context.router.route.match.url}
+ />
+ {this.renderListings()}
</div>
);
}
);
}
- updateUrl(paramUpdates: CommunitiesProps) {
- const page = paramUpdates.page || this.state.page;
- const listingTypeStr = paramUpdates.listingType || this.state.listingType;
- this.props.history.push(
- `/communities/listing_type/${listingTypeStr}/page/${page}`
- );
+ async updateUrl({ listingType, page }: Partial<CommunitiesProps>) {
+ const { listingType: urlListingType, page: urlPage } =
+ this.getCommunitiesQueryParams();
+
+ const queryParams: QueryParams<CommunitiesProps> = {
+ listingType: listingType ?? urlListingType,
+ page: (page ?? urlPage)?.toString(),
+ };
+
+ this.props.history.push(`/communities${getQueryString(queryParams)}`);
+
+ await this.refetch();
}
handlePageChange(page: number) {
});
}
- handleUnsubscribe(communityId: number) {
- let auth = myAuth();
- if (auth) {
- let form: FollowCommunity = {
- community_id: communityId,
- follow: false,
- auth,
- };
- WebSocketService.Instance.send(wsClient.followCommunity(form));
- }
- }
-
- handleSubscribe(communityId: number) {
- let auth = myAuth();
- if (auth) {
- let form: FollowCommunity = {
- community_id: communityId,
- follow: true,
- auth,
- };
- WebSocketService.Instance.send(wsClient.followCommunity(form));
- }
- }
-
handleSearchChange(i: Communities, event: any) {
i.setState({ searchText: event.target.value });
}
- handleSearchSubmit(i: Communities) {
+ handleSearchSubmit(i: Communities, event: any) {
+ event.preventDefault();
const searchParamEncoded = encodeURIComponent(i.state.searchText);
- i.context.router.history.push(
- `/search/q/${searchParamEncoded}/type/Communities/sort/TopAll/listing_type/All/community_id/0/creator_id/0/page/1`
- );
+ i.context.router.history.push(`/search?q=${searchParamEncoded}`);
}
- refetch() {
- let listCommunitiesForm: ListCommunities = {
- type_: this.state.listingType,
- sort: SortType.TopMonth,
+ static fetchInitialData({
+ query: { listingType, page },
+ client,
+ auth,
+ }: InitialFetchRequest<QueryParams<CommunitiesProps>>): Promise<
+ RequestState<any>
+ >[] {
+ const listCommunitiesForm: ListCommunities = {
+ type_: getListingTypeFromQuery(listingType),
+ sort: "TopMonth",
limit: communityLimit,
- page: this.state.page,
- auth: myAuth(false),
+ page: getPageFromString(page),
+ auth: auth,
};
- WebSocketService.Instance.send(
- wsClient.listCommunities(listCommunitiesForm)
- );
+ return [client.listCommunities(listCommunitiesForm)];
}
- static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
- let pathSplit = req.path.split("/");
- let type_: ListingType = pathSplit[3]
- ? ListingType[pathSplit[3]]
- : ListingType.Local;
- let page = pathSplit[5] ? Number(pathSplit[5]) : 1;
- let listCommunitiesForm: ListCommunities = {
- type_,
- sort: SortType.TopMonth,
- limit: communityLimit,
- page,
- auth: req.auth,
- };
+ getCommunitiesQueryParams() {
+ return getQueryParams<CommunitiesProps>({
+ listingType: getListingTypeFromQuery,
+ page: getPageFromString,
+ });
+ }
+
+ async handleFollow(data: {
+ i: Communities;
+ communityId: number;
+ follow: boolean;
+ }) {
+ const res = await HttpService.client.followCommunity({
+ community_id: data.communityId,
+ follow: data.follow,
+ auth: myAuthRequired(),
+ });
+ data.i.findAndUpdateCommunity(res);
+ }
+
+ async refetch() {
+ this.setState({ listCommunitiesResponse: { state: "loading" } });
- return [req.client.listCommunities(listCommunitiesForm)];
+ const { listingType, page } = this.getCommunitiesQueryParams();
+
+ this.setState({
+ listCommunitiesResponse: await HttpService.client.listCommunities({
+ type_: listingType,
+ sort: "TopMonth",
+ limit: communityLimit,
+ page,
+ auth: myAuth(),
+ }),
+ });
+
+ window.scrollTo(0, 0);
}
- parseMessage(msg: any) {
- let op = wsUserOp(msg);
- console.log(msg);
- if (msg.error) {
- toast(i18n.t(msg.error), "danger");
- return;
- } else if (op == UserOperation.ListCommunities) {
- let data = wsJsonToRes<ListCommunitiesResponse>(msg);
- this.setState({ listCommunitiesResponse: data, loading: false });
- window.scrollTo(0, 0);
- } else if (op == UserOperation.FollowCommunity) {
- let data = wsJsonToRes<CommunityResponse>(msg);
- let res = this.state.listCommunitiesResponse;
- let found = res?.communities.find(
- c => c.community.id == data.community_view.community.id
- );
- if (found) {
- found.subscribed = data.community_view.subscribed;
- found.counts.subscribers = data.community_view.counts.subscribers;
- this.setState(this.state);
+ findAndUpdateCommunity(res: RequestState<CommunityResponse>) {
+ this.setState(s => {
+ if (
+ s.listCommunitiesResponse.state == "success" &&
+ res.state == "success"
+ ) {
+ s.listCommunitiesResponse.data.communities = editCommunity(
+ res.data.community_view,
+ s.listCommunitiesResponse.data.communities
+ );
}
- }
+ return s;
+ });
}
}