import { Component, linkEvent } from "inferno";
import {
CommunityResponse,
- FollowCommunity,
GetSiteResponse,
ListCommunities,
ListCommunitiesResponse,
ListingType,
- 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 {
QueryParams,
- WithPromiseKeys,
+ RouteDataResponse,
+ editCommunity,
getPageFromString,
getQueryParams,
getQueryString,
- isBrowser,
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 CommunitiesData {
+type CommunitiesData = RouteDataResponse<{
listCommunitiesResponse: ListCommunitiesResponse;
-}
+}>;
interface CommunitiesState {
- listCommunitiesResponse?: ListCommunitiesResponse;
- loading: boolean;
+ listCommunitiesResponse: RequestState<ListCommunitiesResponse>;
siteRes: GetSiteResponse;
searchText: string;
+ isIsomorphic: boolean;
}
interface CommunitiesProps {
page: number;
}
-function getCommunitiesQueryParams() {
- return getQueryParams<CommunitiesProps>({
- listingType: getListingTypeFromQuery,
- page: getPageFromString,
- });
-}
-
function getListingTypeFromQuery(listingType?: string): ListingType {
return listingType ? (listingType as ListingType) : "Local";
}
-function toggleSubscribe(community_id: number, follow: boolean) {
- const auth = myAuth();
- if (auth) {
- const form: FollowCommunity = {
- community_id,
- follow,
- auth,
- };
-
- WebSocketService.Instance.send(wsClient.followCommunity(form));
- }
-}
-
-function refetch() {
- const { listingType, page } = getCommunitiesQueryParams();
-
- const listCommunitiesForm: ListCommunities = {
- type_: listingType,
- sort: "TopMonth",
- limit: communityLimit,
- page,
- auth: myAuth(false),
- };
-
- WebSocketService.Instance.send(wsClient.listCommunities(listCommunitiesForm));
-}
-
export class Communities extends Component<any, CommunitiesState> {
- private subscription?: Subscription;
private isoData = setIsoData<CommunitiesData>(this.context);
state: CommunitiesState = {
- loading: true,
+ 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) {
+ if (FirstLoadService.isFirstLoad) {
const { listCommunitiesResponse } = this.isoData.routeData;
this.state = {
...this.state,
listCommunitiesResponse,
- loading: false,
+ isIsomorphic: true,
};
- } else {
- refetch();
}
}
- componentWillUnmount() {
- if (isBrowser()) {
- this.subscription?.unsubscribe();
+ async componentDidMount() {
+ if (!this.state.isIsomorphic) {
+ await this.refetch();
}
}
}`;
}
- render() {
- const { listingType, page } = getCommunitiesQueryParams();
-
- 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">
</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 == "Subscribed" && (
- <button
- className="btn btn-link d-inline-block"
- onClick={linkEvent(
- cv.community.id,
- this.handleUnsubscribe
- )}
- >
- {i18n.t("unsubscribe")}
- </button>
- )}
- {cv.subscribed === "NotSubscribed" && (
- <button
- className="btn btn-link d-inline-block"
- onClick={linkEvent(
- cv.community.id,
- this.handleSubscribe
- )}
- >
- {i18n.t("subscribe")}
- </button>
- )}
- {cv.subscribed === "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={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({ listingType, page }: Partial<CommunitiesProps>) {
+ async updateUrl({ listingType, page }: Partial<CommunitiesProps>) {
const { listingType: urlListingType, page: urlPage } =
- getCommunitiesQueryParams();
+ this.getCommunitiesQueryParams();
const queryParams: QueryParams<CommunitiesProps> = {
listingType: listingType ?? urlListingType,
this.props.history.push(`/communities${getQueryString(queryParams)}`);
- refetch();
+ await this.refetch();
}
handlePageChange(page: number) {
});
}
- handleUnsubscribe(communityId: number) {
- toggleSubscribe(communityId, false);
- }
-
- handleSubscribe(communityId: number) {
- toggleSubscribe(communityId, true);
- }
-
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}`);
}
- static fetchInitialData({
+ static async fetchInitialData({
query: { listingType, page },
client,
auth,
}: InitialFetchRequest<
QueryParams<CommunitiesProps>
- >): WithPromiseKeys<CommunitiesData> {
+ >): Promise<CommunitiesData> {
const listCommunitiesForm: ListCommunities = {
type_: getListingTypeFromQuery(listingType),
sort: "TopMonth",
};
return {
- listCommunitiesResponse: client.listCommunities(listCommunitiesForm),
+ listCommunitiesResponse: await client.listCommunities(
+ listCommunitiesForm
+ ),
};
}
- parseMessage(msg: any) {
- const op = wsUserOp(msg);
- console.log(msg);
- if (msg.error) {
- toast(i18n.t(msg.error), "danger");
- } else if (op === UserOperation.ListCommunities) {
- const data = wsJsonToRes<ListCommunitiesResponse>(msg);
- this.setState({ listCommunitiesResponse: data, loading: false });
- window.scrollTo(0, 0);
- } else if (op === UserOperation.FollowCommunity) {
- const {
- community_view: {
- community,
- subscribed,
- counts: { subscribers },
- },
- } = wsJsonToRes<CommunityResponse>(msg);
- const res = this.state.listCommunitiesResponse;
- const found = res?.communities.find(
- ({ community: { id } }) => id == community.id
- );
-
- if (found) {
- found.subscribed = subscribed;
- found.counts.subscribers = subscribers;
- this.setState(this.state);
+ 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" } });
+
+ 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);
+ }
+
+ 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;
+ });
}
}