From: abias Date: Fri, 16 Jun 2023 02:08:14 +0000 (-0400) Subject: Merge branch 'main' into route-data-refactor X-Git-Url: http://these/git/%22%7Burl%7D/%22?a=commitdiff_plain;h=9265fc58948341856513c06ba44e1a0c0d5a4241;p=lemmy-ui.git Merge branch 'main' into route-data-refactor --- 9265fc58948341856513c06ba44e1a0c0d5a4241 diff --cc src/server/index.tsx index 4ab4f76,4302407..9806355 --- a/src/server/index.tsx +++ b/src/server/index.tsx @@@ -19,6 -19,7 +19,11 @@@ import IsoDataOptionalSite, } from "../shared/interfaces"; import { routes } from "../shared/routes"; -import { RequestState, wrapClient } from "../shared/services/HttpService"; ++import { ++ FailedRequestState, ++ RequestState, ++ wrapClient, ++} from "../shared/services/HttpService"; import { ErrorPageData, favIconPngUrl, @@@ -129,27 -136,30 +140,30 @@@ server.get("/*", async (req, res) => // This bypasses errors, so that the client can hit the error on its own, // in order to remove the jwt on the browser. Necessary for wrong jwts let site: GetSiteResponse | undefined = undefined; - let routeData: Record = {}; - let errorPageData: ErrorPageData | undefined; - try { - let try_site: any = await client.getSite(getSiteForm); - if (try_site.error == "not_logged_in") { - console.error( - "Incorrect JWT token, skipping auth so frontend can remove jwt cookie" - ); - getSiteForm.auth = undefined; - auth = undefined; - try_site = await client.getSite(getSiteForm); - } - const routeData: RequestState[] = []; ++ let routeData: Record> = {}; + let errorPageData: ErrorPageData | undefined = undefined; + let try_site = await client.getSite(getSiteForm); + if (try_site.state === "failed" && try_site.msg == "not_logged_in") { + console.error( + "Incorrect JWT token, skipping auth so frontend can remove jwt cookie" + ); + getSiteForm.auth = undefined; + auth = undefined; + try_site = await client.getSite(getSiteForm); + } - if (!auth && isAuthPath(path)) { - res.redirect("/login"); - return; - } + if (!auth && isAuthPath(path)) { + return res.redirect("/login"); + } - site = try_site; + if (try_site.state === "success") { + site = try_site.data; initializeSite(site); + if (path != "/setup" && !site.site_view.local_site.site_setup) { + return res.redirect("/setup"); + } + if (site) { const initialFetchReq: InitialFetchRequest = { client, @@@ -160,32 -170,25 +174,34 @@@ }; if (activeRoute?.fetchInitialData) { - routeData.push( - ...(await Promise.all([ - ...activeRoute.fetchInitialData(initialFetchReq), - ])) + const routeDataKeysAndVals = await Promise.all( + Object.entries(activeRoute.fetchInitialData(initialFetchReq)).map( + async ([key, val]) => [key, await val] + ) ); + + routeData = routeDataKeysAndVals.reduce((acc, [key, val]) => { + acc[key] = val; + + return acc; + }, {}); } } - } catch (error) { - errorPageData = getErrorPageData(error, site); + } else if (try_site.state === "failed") { + errorPageData = getErrorPageData(new Error(try_site.msg), site); } - const error = Object.values(routeData).find(val => val?.error)?.error; ++ const error = Object.values(routeData).find( ++ res => res.state === "failed" ++ ) as FailedRequestState | undefined; + // Redirect to the 404 if there's an API error - if (routeData[0] && routeData[0].state === "failed") { - const error = routeData[0].msg; - console.error(error); - if (error === "instance_is_private") { + if (error) { - console.error(error); - if (error === "instance_is_private") { ++ console.error(error.msg); ++ if (error.msg === "instance_is_private") { return res.redirect(`/signup`); } else { - errorPageData = getErrorPageData(error, site); - errorPageData = getErrorPageData(new Error(error), site); ++ errorPageData = getErrorPageData(new Error(error.msg), site); } } diff --cc src/shared/components/community/communities.tsx index 53e0e96,6232694..3eb7bd3 --- a/src/shared/components/community/communities.tsx +++ b/src/shared/components/community/communities.tsx @@@ -6,17 -5,14 +5,15 @@@ import 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, @@@ -37,15 -30,11 +31,15 @@@ import { CommunityLink } from "./commun const communityLimit = 50; - interface CommunitiesData { ++type CommunitiesData = RouteDataResponse<{ + listCommunitiesResponse: ListCommunitiesResponse; - } ++}>; + interface CommunitiesState { - listCommunitiesResponse?: ListCommunitiesResponse; - loading: boolean; + listCommunitiesResponse: RequestState; siteRes: GetSiteResponse; searchText: string; + isIsomorphic: boolean; } interface CommunitiesProps { @@@ -64,40 -46,13 +51,13 @@@ function getListingTypeFromQuery(listin 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 { - private subscription?: Subscription; - private isoData = setIsoData(this.context); + private isoData = setIsoData(this.context); state: CommunitiesState = { - loading: true, + listCommunitiesResponse: { state: "empty" }, siteRes: this.isoData.site_res, searchText: "", + isIsomorphic: false, }; constructor(props: any, context: any) { @@@ -105,20 -60,13 +65,15 @@@ 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: this.isoData.routeData[0], + listCommunitiesResponse, - loading: false, + isIsomorphic: true, }; - } else { - refetch(); } } @@@ -314,13 -274,13 +281,13 @@@ i.context.router.history.push(`/search?q=${searchParamEncoded}`); } -- static fetchInitialData({ ++ static async fetchInitialData({ query: { listingType, page }, client, auth, - }: InitialFetchRequest>): Promise< - RequestState - >[] { + }: InitialFetchRequest< + QueryParams - >): WithPromiseKeys { ++ >): Promise { const listCommunitiesForm: ListCommunities = { type_: getListingTypeFromQuery(listingType), sort: "TopMonth", @@@ -329,38 -289,59 +296,63 @@@ auth: auth, }; - return [client.listCommunities(listCommunitiesForm)]; + 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(msg); - this.setState({ listCommunitiesResponse: data, loading: false }); - window.scrollTo(0, 0); - } else if (op === UserOperation.FollowCommunity) { - const { - community_view: { - community, - subscribed, - counts: { subscribers }, - }, - } = wsJsonToRes(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({ + 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) { + 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; + }); } } diff --cc src/shared/components/community/community.tsx index f3ab1e2,7dc150f..e8412e7 --- a/src/shared/components/community/community.tsx +++ b/src/shared/components/community/community.tsx @@@ -30,16 -58,16 +58,17 @@@ import DataType, InitialFetchRequest, } from "../../interfaces"; - import { UserService, WebSocketService } from "../../services"; + import { UserService } from "../../services"; + import { FirstLoadService } from "../../services/FirstLoadService"; + import { HttpService, RequestState } from "../../services/HttpService"; import { QueryParams, - WithPromiseKeys, ++ RouteDataResponse, commentsToFlatNodes, communityRSSUrl, - createCommentLikeRes, - createPostLikeFindRes, - editCommentRes, - editPostFindRes, + editComment, + editPost, + editWith, enableDownvotes, enableNsfw, fetchLimit, @@@ -77,19 -100,14 +101,20 @@@ import { SiteSidebar } from "../home/si import { PostListings } from "../post/post-listings"; import { CommunityLink } from "./community-link"; - interface CommunityData { ++type CommunityData = RouteDataResponse<{ + communityResponse: GetCommunityResponse; + postsResponse?: GetPostsResponse; + commentsResponse?: GetCommentsResponse; - } ++}>; + interface State { - communityRes?: GetCommunityResponse; - communityLoading: boolean; - listingsLoading: boolean; - posts: PostView[]; - comments: CommentView[]; + communityRes: RequestState; + postsRes: RequestState; + commentsRes: RequestState; + siteRes: GetSiteResponse; showSidebarMobile: boolean; + finished: Map; + isIsomorphic: boolean; } interface CommunityProps { @@@ -122,14 -140,15 +147,15 @@@ export class Community extends Componen RouteComponentProps<{ name: string }>, State > { - private isoData = setIsoData(this.context); + private isoData = setIsoData(this.context); - private subscription?: Subscription; state: State = { - communityLoading: true, - listingsLoading: true, - posts: [], - comments: [], + communityRes: { state: "empty" }, + postsRes: { state: "empty" }, + commentsRes: { state: "empty" }, + siteRes: this.isoData.site_res, showSidebarMobile: false, + finished: new Map(), + isIsomorphic: false, }; constructor(props: RouteComponentProps<{ name: string }>, context: any) { @@@ -139,35 -158,50 +165,73 @@@ this.handleDataTypeChange = this.handleDataTypeChange.bind(this); this.handlePageChange = this.handlePageChange.bind(this); - this.parseMessage = this.parseMessage.bind(this); - this.subscription = wsSubscribe(this.parseMessage); + // All of the action binds + this.handleDeleteCommunity = this.handleDeleteCommunity.bind(this); + this.handleEditCommunity = this.handleEditCommunity.bind(this); + this.handleFollow = this.handleFollow.bind(this); + this.handleRemoveCommunity = this.handleRemoveCommunity.bind(this); + this.handleCreateComment = this.handleCreateComment.bind(this); + this.handleEditComment = this.handleEditComment.bind(this); + this.handleSaveComment = this.handleSaveComment.bind(this); + this.handleBlockCommunity = this.handleBlockCommunity.bind(this); + this.handleBlockPerson = this.handleBlockPerson.bind(this); + this.handleDeleteComment = this.handleDeleteComment.bind(this); + this.handleRemoveComment = this.handleRemoveComment.bind(this); + this.handleCommentVote = this.handleCommentVote.bind(this); + this.handleAddModToCommunity = this.handleAddModToCommunity.bind(this); + this.handleAddAdmin = this.handleAddAdmin.bind(this); + this.handlePurgePerson = this.handlePurgePerson.bind(this); + this.handlePurgeComment = this.handlePurgeComment.bind(this); + this.handleCommentReport = this.handleCommentReport.bind(this); + this.handleDistinguishComment = this.handleDistinguishComment.bind(this); + this.handleTransferCommunity = this.handleTransferCommunity.bind(this); + this.handleCommentReplyRead = this.handleCommentReplyRead.bind(this); + this.handlePersonMentionRead = this.handlePersonMentionRead.bind(this); + this.handleBanFromCommunity = this.handleBanFromCommunity.bind(this); + this.handleBanPerson = this.handleBanPerson.bind(this); + this.handlePostVote = this.handlePostVote.bind(this); + this.handlePostEdit = this.handlePostEdit.bind(this); + this.handlePostReport = this.handlePostReport.bind(this); + this.handleLockPost = this.handleLockPost.bind(this); + this.handleDeletePost = this.handleDeletePost.bind(this); + this.handleRemovePost = this.handleRemovePost.bind(this); + this.handleSavePost = this.handleSavePost.bind(this); + this.handlePurgePost = this.handlePurgePost.bind(this); + this.handleFeaturePost = this.handleFeaturePost.bind(this); // Only fetch the data if coming from another route - if (this.isoData.path == this.context.router.route.match.url) { - const { communityResponse, commentsResponse, postsResponse } = - this.isoData.routeData; + if (FirstLoadService.isFirstLoad) { - const [communityRes, postsRes, commentsRes] = this.isoData.routeData; ++ const { ++ communityResponse: communityRes, ++ commentsResponse: commentsRes, ++ postsResponse: postsRes, ++ } = this.isoData.routeData; + this.state = { ...this.state, - communityRes: communityResponse, - communityRes, - postsRes, - commentsRes, + isIsomorphic: true, }; + - if (postsResponse) { - this.state = { ...this.state, posts: postsResponse.posts }; ++ if (communityRes.state === "success") { ++ this.state = { ++ ...this.state, ++ communityRes, ++ }; + } + - if (commentsResponse) { - this.state = { ...this.state, comments: commentsResponse.comments }; ++ if (postsRes?.state === "success") { ++ this.state = { ++ ...this.state, ++ postsRes, ++ }; + } + - this.state = { - ...this.state, - communityLoading: false, - listingsLoading: false, - }; - } else { - this.fetchCommunity(); - this.fetchData(); ++ if (commentsRes?.state === "success") { ++ this.state = { ++ ...this.state, ++ commentsRes, ++ }; ++ } } } @@@ -185,18 -225,18 +255,17 @@@ componentWillUnmount() { saveScrollPosition(this.context); - this.subscription?.unsubscribe(); } -- static fetchInitialData({ ++ static async fetchInitialData({ client, path, query: { dataType: urlDataType, page: urlPage, sort: urlSort }, auth, - }: InitialFetchRequest< - QueryParams - >): WithPromiseKeys { + }: InitialFetchRequest>): Promise< - RequestState - >[] { ++ Promise ++ > { const pathSplit = path.split("/"); - const promises: Promise>[] = []; const communityName = pathSplit[2]; const communityForm: GetCommunity = { @@@ -210,9 -251,6 +279,10 @@@ const page = getPageFromString(urlPage); - let postsResponse: Promise | undefined = undefined; - let commentsResponse: Promise | undefined = undefined; ++ let postsResponse: RequestState | undefined = undefined; ++ let commentsResponse: RequestState | undefined = ++ undefined; + if (dataType === DataType.Post) { const getPostsForm: GetPosts = { community_name: communityName, @@@ -223,8 -261,8 +293,8 @@@ saved_only: false, auth, }; - promises.push(client.getPosts(getPostsForm)); - promises.push(Promise.resolve({ state: "empty" })); + - postsResponse = client.getPosts(getPostsForm); ++ postsResponse = await client.getPosts(getPostsForm); } else { const getCommentsForm: GetComments = { community_name: communityName, @@@ -235,15 -273,11 +305,15 @@@ saved_only: false, auth, }; - promises.push(Promise.resolve({ state: "empty" })); - promises.push(client.getComments(getCommentsForm)); + - commentsResponse = client.getComments(getCommentsForm); ++ commentsResponse = await client.getComments(getCommentsForm); } - return promises; + return { - communityResponse: client.getCommunity(communityForm), ++ communityResponse: await client.getCommunity(communityForm), + commentsResponse, + postsResponse, + }; } get documentTitle(): string { diff --cc src/shared/components/home/admin-settings.tsx index 4419cf3,9b7256d..11be725 --- a/src/shared/components/home/admin-settings.tsx +++ b/src/shared/components/home/admin-settings.tsx @@@ -5,21 -8,16 +8,17 @@@ import GetFederatedInstancesResponse, GetSiteResponse, PersonView, - SiteResponse, - UserOperation, - wsJsonToRes, - wsUserOp, } from "lemmy-js-client"; - import { Subscription } from "rxjs"; import { i18n } from "../../i18next"; import { InitialFetchRequest } from "../../interfaces"; - import { WebSocketService } from "../../services"; + import { FirstLoadService } from "../../services/FirstLoadService"; + import { HttpService, RequestState } from "../../services/HttpService"; import { - WithPromiseKeys, ++ RouteDataResponse, capitalizeFirstLetter, - isBrowser, - myAuth, - randomStr, + fetchThemeList, + myAuthRequired, + removeFromEmojiDataModel, setIsoData, showLocal, toast, @@@ -35,23 -32,19 +33,24 @@@ import RateLimitForm from "./rate-limit import { SiteForm } from "./site-form"; import { TaglineForm } from "./tagline-form"; - interface AdminSettingsData { ++type AdminSettingsData = RouteDataResponse<{ + bannedPersonsResponse: BannedPersonsResponse; + federatedInstancesResponse: GetFederatedInstancesResponse; - } ++}>; + interface AdminSettingsState { siteRes: GetSiteResponse; - instancesRes?: GetFederatedInstancesResponse; banned: PersonView[]; - loading: boolean; - leaveAdminTeamLoading: boolean; + currentTab: string; + instancesRes: RequestState; + bannedRes: RequestState; + leaveAdminTeamRes: RequestState; + themeList: string[]; + isIsomorphic: boolean; } export class AdminSettings extends Component { - private siteConfigTextAreaId = `site-config-${randomStr()}`; - private isoData = setIsoData(this.context); + private isoData = setIsoData(this.context); - private subscription?: Subscription; state: AdminSettingsState = { siteRes: this.isoData.site_res, banned: [], @@@ -62,57 -59,67 +65,44 @@@ constructor(props: any, context: any) { super(props, context); - this.parseMessage = this.parseMessage.bind(this); - this.subscription = wsSubscribe(this.parseMessage); + this.handleEditSite = this.handleEditSite.bind(this); + this.handleEditEmoji = this.handleEditEmoji.bind(this); + this.handleDeleteEmoji = this.handleDeleteEmoji.bind(this); + this.handleCreateEmoji = this.handleCreateEmoji.bind(this); // Only fetch the data if coming from another route - if (this.isoData.path == this.context.router.route.match.url) { - const { bannedPersonsResponse, federatedInstancesResponse } = - this.isoData.routeData; + if (FirstLoadService.isFirstLoad) { - const [bannedRes, instancesRes] = this.isoData.routeData; ++ const { ++ bannedPersonsResponse: bannedRes, ++ federatedInstancesResponse: instancesRes, ++ } = this.isoData.routeData; + this.state = { ...this.state, - banned: bannedPersonsResponse.banned, - instancesRes: federatedInstancesResponse, - loading: false, + bannedRes, + instancesRes, + isIsomorphic: true, }; - } else { - let cAuth = myAuth(); - if (cAuth) { - WebSocketService.Instance.send( - wsClient.getBannedPersons({ - auth: cAuth, - }) - ); - WebSocketService.Instance.send( - wsClient.getFederatedInstances({ auth: cAuth }) - ); - } } } - async fetchData() { - this.setState({ - bannedRes: { state: "loading" }, - instancesRes: { state: "loading" }, - themeList: [], - }); - - const auth = myAuthRequired(); - - const [bannedRes, instancesRes, themeList] = await Promise.all([ - HttpService.client.getBannedPersons({ auth }), - HttpService.client.getFederatedInstances({ auth }), - fetchThemeList(), - ]); - - this.setState({ - bannedRes, - instancesRes, - themeList, - }); - } - -- static fetchInitialData({ ++ static async fetchInitialData({ auth, client, - }: InitialFetchRequest): WithPromiseKeys { - }: InitialFetchRequest): Promise[] { - const promises: Promise>[] = []; - - if (auth) { - promises.push(client.getBannedPersons({ auth })); - promises.push(client.getFederatedInstances({ auth })); - } else { - promises.push( - Promise.resolve({ state: "empty" }), - Promise.resolve({ state: "empty" }) - ); - } - - return promises; ++ }: InitialFetchRequest): Promise { + return { - bannedPersonsResponse: client.getBannedPersons({ auth: auth as string }), - federatedInstancesResponse: client.getFederatedInstances({ ++ bannedPersonsResponse: await client.getBannedPersons({ + auth: auth as string, - }) as Promise, ++ }), ++ federatedInstancesResponse: await client.getFederatedInstances({ ++ auth: auth as string, ++ }), + }; } - componentDidMount() { - if (isBrowser()) { - var textarea: any = document.getElementById(this.siteConfigTextAreaId); - autosize(textarea); - } - } - - componentWillUnmount() { - if (isBrowser()) { - this.subscription?.unsubscribe(); + async componentDidMount() { + if (!this.state.isIsomorphic) { + await this.fetchData(); } } @@@ -195,6 -208,6 +191,28 @@@ ); } ++ async fetchData() { ++ this.setState({ ++ bannedRes: { state: "loading" }, ++ instancesRes: { state: "loading" }, ++ themeList: [], ++ }); ++ ++ const auth = myAuthRequired(); ++ ++ const [bannedRes, instancesRes, themeList] = await Promise.all([ ++ HttpService.client.getBannedPersons({ auth }), ++ HttpService.client.getFederatedInstances({ auth }), ++ fetchThemeList(), ++ ]); ++ ++ this.setState({ ++ bannedRes, ++ instancesRes, ++ themeList, ++ }); ++ } ++ admins() { return ( <> diff --cc src/shared/components/home/home.tsx index e85c3e6,8be9830..cc9dd51 --- a/src/shared/components/home/home.tsx +++ b/src/shared/components/home/home.tsx @@@ -61,7 -77,6 +77,7 @@@ import QueryParams, relTags, restoreScrollPosition, - saveCommentRes, ++ RouteDataResponse, saveScrollPosition, setIsoData, setupTippy, @@@ -104,12 -117,6 +118,12 @@@ interface HomeProps page: number; } - interface HomeData { ++type HomeData = RouteDataResponse<{ + postsResponse?: GetPostsResponse; + commentsResponse?: GetCommentsResponse; + trendingResponse: ListCommunitiesResponse; - } ++}>; + function getDataTypeFromQuery(type?: string): DataType { return type ? DataType[type] : DataType.Post; } @@@ -210,44 -175,12 +182,12 @@@ const LinkButton = ( ); - function getRss(listingType: ListingType) { - const { sort } = getHomeQueryParams(); - const auth = myAuth(false); - - let rss: string | undefined = undefined; - - switch (listingType) { - case "All": { - rss = `/feeds/all.xml?sort=${sort}`; - break; - } - case "Local": { - rss = `/feeds/local.xml?sort=${sort}`; - break; - } - case "Subscribed": { - rss = auth ? `/feeds/front/${auth}.xml?sort=${sort}` : undefined; - break; - } - } - - return ( - rss && ( - <> - - - - - - ) - ); - } - export class Home extends Component { - private isoData = setIsoData(this.context); + private isoData = setIsoData(this.context); - private subscription?: Subscription; state: HomeState = { - trendingCommunities: [], + postsRes: { state: "empty" }, + commentsRes: { state: "empty" }, + trendingCommunitiesRes: { state: "empty" }, siteRes: this.isoData.site_res, showSubscribedMobile: false, showTrendingMobile: false, @@@ -266,37 -198,48 +205,63 @@@ this.handleDataTypeChange = this.handleDataTypeChange.bind(this); this.handlePageChange = this.handlePageChange.bind(this); - this.parseMessage = this.parseMessage.bind(this); - this.subscription = wsSubscribe(this.parseMessage); + this.handleCreateComment = this.handleCreateComment.bind(this); + this.handleEditComment = this.handleEditComment.bind(this); + this.handleSaveComment = this.handleSaveComment.bind(this); + this.handleBlockPerson = this.handleBlockPerson.bind(this); + this.handleDeleteComment = this.handleDeleteComment.bind(this); + this.handleRemoveComment = this.handleRemoveComment.bind(this); + this.handleCommentVote = this.handleCommentVote.bind(this); + this.handleAddModToCommunity = this.handleAddModToCommunity.bind(this); + this.handleAddAdmin = this.handleAddAdmin.bind(this); + this.handlePurgePerson = this.handlePurgePerson.bind(this); + this.handlePurgeComment = this.handlePurgeComment.bind(this); + this.handleCommentReport = this.handleCommentReport.bind(this); + this.handleDistinguishComment = this.handleDistinguishComment.bind(this); + this.handleTransferCommunity = this.handleTransferCommunity.bind(this); + this.handleCommentReplyRead = this.handleCommentReplyRead.bind(this); + this.handlePersonMentionRead = this.handlePersonMentionRead.bind(this); + this.handleBanFromCommunity = this.handleBanFromCommunity.bind(this); + this.handleBanPerson = this.handleBanPerson.bind(this); + this.handlePostEdit = this.handlePostEdit.bind(this); + this.handlePostVote = this.handlePostVote.bind(this); + this.handlePostReport = this.handlePostReport.bind(this); + this.handleLockPost = this.handleLockPost.bind(this); + this.handleDeletePost = this.handleDeletePost.bind(this); + this.handleRemovePost = this.handleRemovePost.bind(this); + this.handleSavePost = this.handleSavePost.bind(this); + this.handlePurgePost = this.handlePurgePost.bind(this); + this.handleFeaturePost = this.handleFeaturePost.bind(this); // Only fetch the data if coming from another route - if (this.isoData.path === this.context.router.route.match.url) { - const { trendingResponse, commentsResponse, postsResponse } = - this.isoData.routeData; + if (FirstLoadService.isFirstLoad) { - const [postsRes, commentsRes, trendingCommunitiesRes] = - this.isoData.routeData; ++ const { ++ trendingResponse: trendingCommunitiesRes, ++ commentsResponse: commentsRes, ++ postsResponse: postsRes, ++ } = this.isoData.routeData; - if (postsResponse) { - this.state = { ...this.state, posts: postsResponse.posts }; - } + this.state = { + ...this.state, - postsRes, - commentsRes, + trendingCommunitiesRes, + tagline: getRandomFromList(this.state?.siteRes?.taglines ?? []) + ?.content, + isIsomorphic: true, + }; + - if (commentsResponse) { - this.state = { ...this.state, comments: commentsResponse.comments }; ++ if (commentsRes?.state === "success") { ++ this.state = { ++ ...this.state, ++ commentsRes, ++ }; + } + - if (isBrowser()) { - WebSocketService.Instance.send( - wsClient.communityJoin({ community_id: 0 }) - ); ++ if (postsRes?.state === "success") { ++ this.state = { ++ ...this.state, ++ postsRes, ++ }; + } - const taglines = this.state?.siteRes?.taglines ?? []; - this.state = { - ...this.state, - trendingCommunities: trendingResponse?.communities ?? [], - loading: false, - tagline: getRandomFromList(taglines)?.content, - }; - } else { - fetchTrendingCommunities(); - fetchData(); } } @@@ -310,14 -252,15 +274,13 @@@ componentWillUnmount() { saveScrollPosition(this.context); - this.subscription?.unsubscribe(); } -- static fetchInitialData({ ++ static async fetchInitialData({ client, auth, query: { dataType: urlDataType, listingType, page: urlPage, sort: urlSort }, - }: InitialFetchRequest>): WithPromiseKeys { - }: InitialFetchRequest>): Promise< - RequestState - >[] { ++ }: InitialFetchRequest>): Promise { const dataType = getDataTypeFromQuery(urlDataType); // TODO figure out auth default_listingType, default_sort_type @@@ -326,10 -269,7 +289,9 @@@ const page = urlPage ? Number(urlPage) : 1; - const promises: Promise[] = []; - - let postsResponse: Promise | undefined = undefined; - let commentsResponse: Promise | undefined = undefined; - const promises: Promise>[] = []; ++ let postsResponse: RequestState | undefined = undefined; ++ let commentsResponse: RequestState | undefined = ++ undefined; if (dataType === DataType.Post) { const getPostsForm: GetPosts = { @@@ -341,7 -281,8 +303,7 @@@ auth, }; - postsResponse = client.getPosts(getPostsForm); - promises.push(client.getPosts(getPostsForm)); - promises.push(Promise.resolve({ state: "empty" })); ++ postsResponse = await client.getPosts(getPostsForm); } else { const getCommentsForm: GetComments = { page, @@@ -351,8 -292,8 +313,8 @@@ saved_only: false, auth, }; - promises.push(Promise.resolve({ state: "empty" })); - promises.push(client.getComments(getCommentsForm)); + - commentsResponse = client.getComments(getCommentsForm); ++ commentsResponse = await client.getComments(getCommentsForm); } const trendingCommunitiesForm: ListCommunities = { @@@ -361,13 -302,9 +323,12 @@@ limit: trendingFetchLimit, auth, }; -- promises.push(client.listCommunities(trendingCommunitiesForm)); - return promises; + return { - trendingResponse: client.listCommunities(trendingCommunitiesForm), ++ trendingResponse: await client.listCommunities(trendingCommunitiesForm), + commentsResponse, + postsResponse, + }; } get documentTitle(): string { diff --cc src/shared/components/home/instances.tsx index fd1ed61,30cb9de..bec472c --- a/src/shared/components/home/instances.tsx +++ b/src/shared/components/home/instances.tsx @@@ -3,69 -3,62 +3,68 @@@ import GetFederatedInstancesResponse, GetSiteResponse, Instance, - UserOperation, - wsJsonToRes, - wsUserOp, } from "lemmy-js-client"; - import { Subscription } from "rxjs"; import { i18n } from "../../i18next"; import { InitialFetchRequest } from "../../interfaces"; - import { WebSocketService } from "../../services"; - import { - WithPromiseKeys, - isBrowser, - relTags, - setIsoData, - toast, - wsClient, - wsSubscribe, - } from "../../utils"; + import { FirstLoadService } from "../../services/FirstLoadService"; + import { HttpService, RequestState } from "../../services/HttpService"; -import { relTags, setIsoData } from "../../utils"; ++import { RouteDataResponse, relTags, setIsoData } from "../../utils"; import { HtmlTags } from "../common/html-tags"; + import { Spinner } from "../common/icon"; - interface InstancesData { ++type InstancesData = RouteDataResponse<{ + federatedInstancesResponse: GetFederatedInstancesResponse; - } ++}>; + interface InstancesState { + instancesRes: RequestState; siteRes: GetSiteResponse; - instancesRes?: GetFederatedInstancesResponse; - loading: boolean; + isIsomorphic: boolean; } export class Instances extends Component { - private isoData = setIsoData(this.context); + private isoData = setIsoData(this.context); state: InstancesState = { + instancesRes: { state: "empty" }, siteRes: this.isoData.site_res, - loading: true, + isIsomorphic: false, }; - private subscription?: Subscription; constructor(props: any, context: any) { super(props, context); - 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) { this.state = { ...this.state, - instancesRes: this.isoData.routeData[0], + instancesRes: this.isoData.routeData.federatedInstancesResponse, - loading: false, + isIsomorphic: true, }; - } else { - WebSocketService.Instance.send(wsClient.getFederatedInstances({})); } } - static fetchInitialData({ - client, - }: InitialFetchRequest): WithPromiseKeys { + async componentDidMount() { + if (!this.state.isIsomorphic) { + await this.fetchInstances(); + } + } + + async fetchInstances() { + this.setState({ + instancesRes: { state: "loading" }, + }); + + this.setState({ + instancesRes: await HttpService.client.getFederatedInstances({}), + }); + } + - static fetchInitialData( ++ static async fetchInitialData( + req: InitialFetchRequest - ): Promise>[] { - return [req.client.getFederatedInstances({})]; ++ ): Promise { + return { - federatedInstancesResponse: client.getFederatedInstances( - {} - ) as Promise, ++ federatedInstancesResponse: await req.client.getFederatedInstances({}), + }; } get documentTitle(): string { diff --cc src/shared/components/modlog.tsx index cb4b37f,d917f5f..48be10b --- a/src/shared/components/modlog.tsx +++ b/src/shared/components/modlog.tsx @@@ -39,7 -34,6 +35,7 @@@ import { HttpService, RequestState } fr import { Choice, QueryParams, - WithPromiseKeys, ++ RouteDataResponse, amAdmin, amMod, debounce, @@@ -84,13 -74,6 +76,13 @@@ type View | AdminPurgePostView | AdminPurgeCommentView; - interface ModlogData { ++type ModlogData = RouteDataResponse<{ + modlogResponse: GetModlogResponse; + communityResponse?: GetCommunityResponse; + modUserResponse?: GetPersonDetailsResponse; + userResponse?: GetPersonDetailsResponse; - } ++}>; + interface ModlogType { id: number; type_: ModlogActionType; @@@ -650,11 -631,11 +640,11 @@@ export class Modlog extends Component RouteComponentProps<{ communityId?: string }>, ModlogState > { - private isoData = setIsoData(this.context); + private isoData = setIsoData(this.context); - private subscription?: Subscription; state: ModlogState = { - loadingModlog: true, + res: { state: "empty" }, + communityRes: { state: "empty" }, loadingModSearch: false, loadingUserSearch: false, userSearchOptions: [], @@@ -670,52 -651,29 +660,40 @@@ this.handleUserChange = this.handleUserChange.bind(this); this.handleModChange = this.handleModChange.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 [res, communityRes, filteredModRes, filteredUserRes] = - this.isoData.routeData; + const { - modlogResponse, - communityResponse, ++ modlogResponse: res, ++ communityResponse: communityRes, + modUserResponse, + userResponse, + } = this.isoData.routeData; + this.state = { ...this.state, - res: modlogResponse, + res, - communityRes, }; - // Getting the moderators - this.state = { - ...this.state, - communityMods: communityResponse?.moderators, - }; - - if (modUserResponse) { - if (filteredModRes.state === "success") { ++ if (communityRes?.state === "success") { this.state = { ...this.state, - modSearchOptions: [personToChoice(modUserResponse.person_view)], - modSearchOptions: [personToChoice(filteredModRes.data.person_view)], ++ communityRes, }; } - if (userResponse) { - if (filteredUserRes.state === "success") { ++ if (modUserResponse?.state === "success") { this.state = { ...this.state, - userSearchOptions: [personToChoice(userResponse.person_view)], - userSearchOptions: [personToChoice(filteredUserRes.data.person_view)], ++ modSearchOptions: [personToChoice(modUserResponse.data.person_view)], + }; + } + - this.state = { ...this.state, loadingModlog: false }; - } else { - this.refetch(); - } - } - - componentWillUnmount() { - if (isBrowser()) { - this.subscription?.unsubscribe(); ++ if (userResponse?.state === "success") { ++ this.state = { ++ ...this.state, ++ userSearchOptions: [personToChoice(userResponse.data.person_view)], + }; + } } } @@@ -988,16 -958,17 +978,14 @@@ } } -- static fetchInitialData({ ++ static async fetchInitialData({ client, path, query: { modId: urlModId, page, userId: urlUserId, actionType }, auth, site, - }: InitialFetchRequest< - QueryParams - >): WithPromiseKeys { - }: InitialFetchRequest>): Promise< - RequestState - >[] { ++ }: InitialFetchRequest>): Promise { const pathSplit = path.split("/"); - const promises: Promise>[] = []; const communityId = getIdFromString(pathSplit[2]); const modId = !site.site_view.local_site.hide_modlog_mod_names ? getIdFromString(urlModId) @@@ -1014,80 -985,40 +1002,47 @@@ auth, }; - let communityResponse: Promise | undefined = - promises.push(client.getModlog(modlogForm)); ++ let communityResponse: RequestState | undefined = + undefined; if (communityId) { const communityForm: GetCommunity = { id: communityId, auth, }; - promises.push(client.getCommunity(communityForm)); - } else { - promises.push(Promise.resolve({ state: "empty" })); + - communityResponse = client.getCommunity(communityForm); ++ communityResponse = await client.getCommunity(communityForm); } - let modUserResponse: Promise | undefined = ++ let modUserResponse: RequestState | undefined = + undefined; + if (modId) { const getPersonForm: GetPersonDetails = { person_id: modId, auth, }; - modUserResponse = client.getPersonDetails(getPersonForm); - promises.push(client.getPersonDetails(getPersonForm)); - } else { - promises.push(Promise.resolve({ state: "empty" })); ++ modUserResponse = await client.getPersonDetails(getPersonForm); } - let userResponse: Promise | undefined = undefined; ++ let userResponse: RequestState | undefined = ++ undefined; + if (userId) { const getPersonForm: GetPersonDetails = { person_id: userId, auth, }; - userResponse = client.getPersonDetails(getPersonForm); - promises.push(client.getPersonDetails(getPersonForm)); - } else { - promises.push(Promise.resolve({ state: "empty" })); ++ userResponse = await client.getPersonDetails(getPersonForm); } - return promises; + return { - modlogResponse: client.getModlog(modlogForm), ++ modlogResponse: await client.getModlog(modlogForm), + communityResponse, + modUserResponse, + userResponse, + }; } - - parseMessage(msg: any) { - const op = wsUserOp(msg); - console.log(msg); - - if (msg.error) { - toast(i18n.t(msg.error), "danger"); - } else { - switch (op) { - case UserOperation.GetModlog: { - const res = wsJsonToRes(msg); - window.scrollTo(0, 0); - this.setState({ res, loadingModlog: false }); - - break; - } - - case UserOperation.GetCommunity: { - const { - moderators, - community_view: { - community: { name }, - }, - } = wsJsonToRes(msg); - this.setState({ - communityMods: moderators, - communityName: name, - }); - - break; - } - } - } - } } diff --cc src/shared/components/person/inbox.tsx index 6393fc6,731667c..b0550f2 --- a/src/shared/components/person/inbox.tsx +++ b/src/shared/components/person/inbox.tsx @@@ -7,44 -14,59 +14,57 @@@ import CommentResponse, CommentSortType, CommentView, - GetPersonMentions, + CreateComment, + CreateCommentLike, + CreateCommentReport, + CreatePrivateMessage, + CreatePrivateMessageReport, + DeleteComment, + DeletePrivateMessage, + DistinguishComment, + EditComment, + EditPrivateMessage, - GetPersonMentions, GetPersonMentionsResponse, -- GetPrivateMessages, -- GetReplies, GetRepliesResponse, GetSiteResponse, + MarkCommentReplyAsRead, + MarkPersonMentionAsRead, + MarkPrivateMessageAsRead, PersonMentionResponse, PersonMentionView, - PostReportResponse, PrivateMessageReportResponse, PrivateMessageResponse, PrivateMessageView, PrivateMessagesResponse, - UserOperation, - wsJsonToRes, - wsUserOp, + PurgeComment, + PurgeItemResponse, + PurgePerson, + PurgePost, + RemoveComment, + SaveComment, + TransferCommunity, } from "lemmy-js-client"; - import { Subscription } from "rxjs"; import { i18n } from "../../i18next"; import { CommentViewType, InitialFetchRequest } from "../../interfaces"; - import { UserService, WebSocketService } from "../../services"; + import { UserService } from "../../services"; + import { FirstLoadService } from "../../services/FirstLoadService"; + import { HttpService, RequestState } from "../../services/HttpService"; import { - WithPromiseKeys, ++ RouteDataResponse, commentsToFlatNodes, - createCommentLikeRes, - editCommentRes, + editCommentReply, + editMention, + editPrivateMessage, + editWith, enableDownvotes, fetchLimit, - isBrowser, + getCommentParentId, myAuth, + myAuthRequired, relTags, - saveCommentRes, setIsoData, - setupTippy, toast, updatePersonBlock, - wsClient, - wsSubscribe, } from "../../utils"; import { CommentNodes } from "../comment/comment-nodes"; import { CommentSortSelect } from "../common/comment-sort-select"; @@@ -70,13 -92,6 +90,13 @@@ enum ReplyEnum Mention, Message, } + - interface InboxData { ++type InboxData = RouteDataResponse<{ + repliesResponse: GetRepliesResponse; + personMentionsResponse: GetPersonMentionsResponse; + privateMessagesResponse: PrivateMessagesResponse; - } ++}>; + type ReplyType = { id: number; type_: ReplyEnum; @@@ -98,8 -114,7 +119,7 @@@ interface InboxState } export class Inbox extends Component { - private isoData = setIsoData(this.context); + private isoData = setIsoData(this.context); - private subscription?: Subscription; state: InboxState = { unreadOrAll: UnreadOrAll.Unread, messageType: MessageType.All, @@@ -119,16 -135,34 +140,38 @@@ this.handleSortChange = this.handleSortChange.bind(this); this.handlePageChange = this.handlePageChange.bind(this); - this.parseMessage = this.parseMessage.bind(this); - this.subscription = wsSubscribe(this.parseMessage); + this.handleCreateComment = this.handleCreateComment.bind(this); + this.handleEditComment = this.handleEditComment.bind(this); + this.handleSaveComment = this.handleSaveComment.bind(this); + this.handleBlockPerson = this.handleBlockPerson.bind(this); + this.handleDeleteComment = this.handleDeleteComment.bind(this); + this.handleRemoveComment = this.handleRemoveComment.bind(this); + this.handleCommentVote = this.handleCommentVote.bind(this); + this.handleAddModToCommunity = this.handleAddModToCommunity.bind(this); + this.handleAddAdmin = this.handleAddAdmin.bind(this); + this.handlePurgePerson = this.handlePurgePerson.bind(this); + this.handlePurgeComment = this.handlePurgeComment.bind(this); + this.handleCommentReport = this.handleCommentReport.bind(this); + this.handleDistinguishComment = this.handleDistinguishComment.bind(this); + this.handleTransferCommunity = this.handleTransferCommunity.bind(this); + this.handleCommentReplyRead = this.handleCommentReplyRead.bind(this); + this.handlePersonMentionRead = this.handlePersonMentionRead.bind(this); + this.handleBanFromCommunity = this.handleBanFromCommunity.bind(this); + this.handleBanPerson = this.handleBanPerson.bind(this); + + this.handleDeleteMessage = this.handleDeleteMessage.bind(this); + this.handleMarkMessageAsRead = this.handleMarkMessageAsRead.bind(this); + this.handleMessageReport = this.handleMessageReport.bind(this); + this.handleCreateMessage = this.handleCreateMessage.bind(this); + this.handleEditMessage = this.handleEditMessage.bind(this); // Only fetch the data if coming from another route - if (this.isoData.path === this.context.router.route.match.url) { + if (FirstLoadService.isFirstLoad) { - const [repliesRes, mentionsRes, messagesRes] = this.isoData.routeData; + const { - personMentionsResponse, - privateMessagesResponse, - repliesResponse, ++ personMentionsResponse: mentionsRes, ++ privateMessagesResponse: messagesRes, ++ repliesResponse: repliesRes, + } = this.isoData.routeData; this.state = { ...this.state, @@@ -463,78 -643,105 +652,95 @@@ } messages() { - return ( -
- {this.state.messages.map(pmv => ( - - ))} -
- ); + switch (this.state.messagesRes.state) { + case "loading": + return ( +
+ +
+ ); + case "success": { + const messages = this.state.messagesRes.data.private_messages; + return ( +
+ {messages.map(pmv => ( + + ))} +
+ ); + } + } } - handlePageChange(page: number) { + async handlePageChange(page: number) { this.setState({ page }); - this.refetch(); + await this.refetch(); } - handleUnreadOrAllChange(i: Inbox, event: any) { + async handleUnreadOrAllChange(i: Inbox, event: any) { i.setState({ unreadOrAll: Number(event.target.value), page: 1 }); - i.refetch(); + await i.refetch(); } - handleMessageTypeChange(i: Inbox, event: any) { + async handleMessageTypeChange(i: Inbox, event: any) { i.setState({ messageType: Number(event.target.value), page: 1 }); - i.refetch(); + await i.refetch(); } -- static fetchInitialData({ - auth, ++ static async fetchInitialData({ client, - }: InitialFetchRequest): WithPromiseKeys { + auth, - }: InitialFetchRequest): Promise[] { - const promises: Promise>[] = []; - ++ }: InitialFetchRequest): Promise { const sort: CommentSortType = "New"; - // It can be /u/me, or /username/1 - const repliesForm: GetReplies = { - sort, - unread_only: true, - page: 1, - limit: fetchLimit, - auth: auth as string, - }; - if (auth) { - // It can be /u/me, or /username/1 - const repliesForm: GetReplies = { - sort, - unread_only: true, - page: 1, - limit: fetchLimit, - auth, - }; - promises.push(client.getReplies(repliesForm)); -- - const personMentionsForm: GetPersonMentions = { - sort, - unread_only: true, - page: 1, - limit: fetchLimit, - auth: auth as string, - }; - const personMentionsForm: GetPersonMentions = { - sort, - unread_only: true, - page: 1, - limit: fetchLimit, - auth, - }; - promises.push(client.getPersonMentions(personMentionsForm)); -- - const privateMessagesForm: GetPrivateMessages = { - unread_only: true, - page: 1, - limit: fetchLimit, - auth: auth as string, - }; - const privateMessagesForm: GetPrivateMessages = { - unread_only: true, - page: 1, - limit: fetchLimit, - auth, - }; - promises.push(client.getPrivateMessages(privateMessagesForm)); - } else { - promises.push( - Promise.resolve({ state: "empty" }), - Promise.resolve({ state: "empty" }), - Promise.resolve({ state: "empty" }) - ); - } -- - return promises; + return { - privateMessagesResponse: client.getPrivateMessages(privateMessagesForm), - personMentionsResponse: client.getPersonMentions(personMentionsForm), - repliesResponse: client.getReplies(repliesForm), ++ personMentionsResponse: auth ++ ? await client.getPersonMentions({ ++ sort, ++ unread_only: true, ++ page: 1, ++ limit: fetchLimit, ++ auth, ++ }) ++ : { state: "empty" }, ++ privateMessagesResponse: auth ++ ? await client.getPrivateMessages({ ++ unread_only: true, ++ page: 1, ++ limit: fetchLimit, ++ auth, ++ }) ++ : { state: "empty" }, ++ repliesResponse: auth ++ ? await client.getReplies({ ++ sort, ++ unread_only: true, ++ page: 1, ++ limit: fetchLimit, ++ auth, ++ }) ++ : { state: "empty" }, + }; } - refetch() { - const { sort, page, unreadOrAll } = this.state; - const unread_only = unreadOrAll === UnreadOrAll.Unread; + async refetch() { + const sort = this.state.sort; + const unread_only = this.state.unreadOrAll == UnreadOrAll.Unread; + const page = this.state.page; const limit = fetchLimit; - const auth = myAuth(); + const auth = myAuthRequired(); - if (auth) { - const repliesForm: GetReplies = { + this.setState({ repliesRes: { state: "loading" } }); + this.setState({ + repliesRes: await HttpService.client.getReplies({ sort, unread_only, page, diff --cc src/shared/components/person/profile.tsx index 00a4419,f80d5b9..5466bc5 --- a/src/shared/components/person/profile.tsx +++ b/src/shared/components/person/profile.tsx @@@ -15,27 -30,35 +30,36 @@@ import GetPersonDetails, GetPersonDetailsResponse, GetSiteResponse, + LockPost, + MarkCommentReplyAsRead, + MarkPersonMentionAsRead, + PersonView, PostResponse, + PurgeComment, PurgeItemResponse, + PurgePerson, + PurgePost, + RemoveComment, + RemovePost, + SaveComment, + SavePost, SortType, - UserOperation, - wsJsonToRes, - wsUserOp, + TransferCommunity, } from "lemmy-js-client"; import moment from "moment"; - import { Subscription } from "rxjs"; import { i18n } from "../../i18next"; import { InitialFetchRequest, PersonDetailsView } from "../../interfaces"; - import { UserService, WebSocketService } from "../../services"; + import { UserService } from "../../services"; + import { FirstLoadService } from "../../services/FirstLoadService"; + import { HttpService, RequestState } from "../../services/HttpService"; import { QueryParams, - WithPromiseKeys, ++ RouteDataResponse, canMod, capitalizeFirstLetter, - createCommentLikeRes, - createPostLikeFindRes, - editCommentRes, - editPostFindRes, + editComment, + editPost, + editWith, enableDownvotes, enableNsfw, fetchLimit, @@@ -68,13 -90,8 +91,12 @@@ import { CommunityLink } from "../commu import { PersonDetails } from "./person-details"; import { PersonListing } from "./person-listing"; - interface ProfileData { ++type ProfileData = RouteDataResponse<{ + personResponse: GetPersonDetailsResponse; - } ++}>; + interface ProfileState { - personRes?: GetPersonDetailsResponse; - loading: boolean; + personRes: RequestState; personBlocked: boolean; banReason?: string; banExpireDays?: number; @@@ -157,10 -156,9 +161,9 @@@ export class Profile extends Component RouteComponentProps<{ username: string }>, ProfileState > { - private isoData = setIsoData(this.context); + private isoData = setIsoData(this.context); - private subscription?: Subscription; state: ProfileState = { - loading: true, + personRes: { state: "empty" }, personBlocked: false, siteRes: this.isoData.site_res, showBanDialog: false, @@@ -173,18 -173,44 +178,44 @@@ this.handleSortChange = this.handleSortChange.bind(this); this.handlePageChange = this.handlePageChange.bind(this); - this.parseMessage = this.parseMessage.bind(this); - this.subscription = wsSubscribe(this.parseMessage); + this.handleBlockPerson = this.handleBlockPerson.bind(this); + this.handleUnblockPerson = this.handleUnblockPerson.bind(this); + + this.handleCreateComment = this.handleCreateComment.bind(this); + this.handleEditComment = this.handleEditComment.bind(this); + this.handleSaveComment = this.handleSaveComment.bind(this); + this.handleBlockPersonAlt = this.handleBlockPersonAlt.bind(this); + this.handleDeleteComment = this.handleDeleteComment.bind(this); + this.handleRemoveComment = this.handleRemoveComment.bind(this); + this.handleCommentVote = this.handleCommentVote.bind(this); + this.handleAddModToCommunity = this.handleAddModToCommunity.bind(this); + this.handleAddAdmin = this.handleAddAdmin.bind(this); + this.handlePurgePerson = this.handlePurgePerson.bind(this); + this.handlePurgeComment = this.handlePurgeComment.bind(this); + this.handleCommentReport = this.handleCommentReport.bind(this); + this.handleDistinguishComment = this.handleDistinguishComment.bind(this); + this.handleTransferCommunity = this.handleTransferCommunity.bind(this); + this.handleCommentReplyRead = this.handleCommentReplyRead.bind(this); + this.handlePersonMentionRead = this.handlePersonMentionRead.bind(this); + this.handleBanFromCommunity = this.handleBanFromCommunity.bind(this); + this.handleBanPerson = this.handleBanPerson.bind(this); + this.handlePostVote = this.handlePostVote.bind(this); + this.handlePostEdit = this.handlePostEdit.bind(this); + this.handlePostReport = this.handlePostReport.bind(this); + this.handleLockPost = this.handleLockPost.bind(this); + this.handleDeletePost = this.handleDeletePost.bind(this); + this.handleRemovePost = this.handleRemovePost.bind(this); + this.handleSavePost = this.handleSavePost.bind(this); + this.handlePurgePost = this.handlePurgePost.bind(this); + this.handleFeaturePost = this.handleFeaturePost.bind(this); // Only fetch the data if coming from another route - if (this.isoData.path === this.context.router.route.match.url) { + if (FirstLoadService.isFirstLoad) { this.state = { ...this.state, - personRes: this.isoData.routeData[0], + personRes: this.isoData.routeData.personResponse, - loading: false, + isIsomorphic: true, }; - } else { - this.fetchUserData(); } } @@@ -223,14 -267,14 +272,12 @@@ } } -- static fetchInitialData({ ++ static async fetchInitialData({ client, path, query: { page, sort, view: urlView }, auth, - }: InitialFetchRequest< - QueryParams - >): WithPromiseKeys { - }: InitialFetchRequest>): Promise< - RequestState - >[] { ++ }: InitialFetchRequest>): Promise { const pathSplit = path.split("/"); const username = pathSplit[2]; @@@ -245,35 -289,21 +292,23 @@@ auth, }; - return [client.getPersonDetails(form)]; + return { - personResponse: client.getPersonDetails(form), ++ personResponse: await client.getPersonDetails(form), + }; } - componentDidMount() { - this.setPersonBlock(); - setupTippy(); - } - - componentWillUnmount() { - this.subscription?.unsubscribe(); - saveScrollPosition(this.context); - } - get documentTitle(): string { + const siteName = this.state.siteRes.site_view.site.name; const res = this.state.personRes; - return res - ? `@${res.person_view.person.name} - ${this.state.siteRes.site_view.site.name}` - : ""; + return res.state == "success" + ? `@${res.data.person_view.person.name} - ${siteName}` + : siteName; } - render() { - const { personRes, loading, siteRes } = this.state; - const { page, sort, view } = getProfileQueryParams(); - - return ( -
- {loading ? ( + renderPersonRes() { + switch (this.state.personRes.state) { + case "loading": + return (
diff --cc src/shared/components/person/registration-applications.tsx index 72ac501,17b2a02..be1eb2c --- a/src/shared/components/person/registration-applications.tsx +++ b/src/shared/components/person/registration-applications.tsx @@@ -1,28 -1,22 +1,22 @@@ import { Component, linkEvent } from "inferno"; import { + ApproveRegistrationApplication, GetSiteResponse, -- ListRegistrationApplications, ListRegistrationApplicationsResponse, - RegistrationApplicationResponse, - UserOperation, - wsJsonToRes, - wsUserOp, + RegistrationApplicationView, } from "lemmy-js-client"; - import { Subscription } from "rxjs"; import { i18n } from "../../i18next"; import { InitialFetchRequest } from "../../interfaces"; - import { UserService, WebSocketService } from "../../services"; + import { UserService } from "../../services"; + import { FirstLoadService } from "../../services/FirstLoadService"; + import { HttpService, RequestState } from "../../services/HttpService"; import { - WithPromiseKeys, ++ RouteDataResponse, + editRegistrationApplication, fetchLimit, - isBrowser, - myAuth, + myAuthRequired, setIsoData, setupTippy, - toast, - updateRegistrationApplicationRes, - wsClient, - wsSubscribe, } from "../../utils"; import { HtmlTags } from "../common/html-tags"; import { Spinner } from "../common/icon"; @@@ -34,12 -28,8 +28,12 @@@ enum UnreadOrAll All, } - interface RegistrationApplicationsData { ++type RegistrationApplicationsData = RouteDataResponse<{ + listRegistrationApplicationsResponse: ListRegistrationApplicationsResponse; - } ++}>; + interface RegistrationApplicationsState { - listRegistrationApplicationsResponse?: ListRegistrationApplicationsResponse; + appsRes: RequestState; siteRes: GetSiteResponse; unreadOrAll: UnreadOrAll; page: number; @@@ -50,9 -40,9 +44,9 @@@ export class RegistrationApplications e any, RegistrationApplicationsState > { - private isoData = setIsoData(this.context); + private isoData = setIsoData(this.context); - private subscription?: Subscription; state: RegistrationApplicationsState = { + appsRes: { state: "empty" }, siteRes: this.isoData.site_res, unreadOrAll: UnreadOrAll.Unread, page: 1, @@@ -63,20 -53,15 +57,15 @@@ super(props, context); this.handlePageChange = this.handlePageChange.bind(this); - - this.parseMessage = this.parseMessage.bind(this); - this.subscription = wsSubscribe(this.parseMessage); + this.handleApproveApplication = this.handleApproveApplication.bind(this); // Only fetch the data if coming from another route - if (this.isoData.path === this.context.router.route.match.url) { + if (FirstLoadService.isFirstLoad) { this.state = { ...this.state, - listRegistrationApplicationsResponse: - this.isoData.routeData.listRegistrationApplicationsResponse, - loading: false, - appsRes: this.isoData.routeData[0], ++ appsRes: this.isoData.routeData.listRegistrationApplicationsResponse, + isIsomorphic: true, }; - } else { - this.refetch(); } } @@@ -197,28 -184,34 +188,29 @@@ this.refetch(); } -- static fetchInitialData({ ++ static async fetchInitialData({ auth, client, - }: InitialFetchRequest): WithPromiseKeys { - const form: ListRegistrationApplications = { - unread_only: true, - page: 1, - limit: fetchLimit, - auth: auth as string, - }; - }: InitialFetchRequest): Promise[] { - const promises: Promise>[] = []; - - if (auth) { - const form: ListRegistrationApplications = { - unread_only: true, - page: 1, - limit: fetchLimit, - auth, - }; - promises.push(client.listRegistrationApplications(form)); - } else { - promises.push(Promise.resolve({ state: "empty" })); - } -- - return promises; ++ }: InitialFetchRequest): Promise { + return { - listRegistrationApplicationsResponse: - client.listRegistrationApplications(form), ++ listRegistrationApplicationsResponse: auth ++ ? await client.listRegistrationApplications({ ++ unread_only: true, ++ page: 1, ++ limit: fetchLimit, ++ auth: auth as string, ++ }) ++ : { state: "empty" }, + }; } - refetch() { - let unread_only = this.state.unreadOrAll == UnreadOrAll.Unread; - let auth = myAuth(); - if (auth) { - let form: ListRegistrationApplications = { + async refetch() { + const unread_only = this.state.unreadOrAll == UnreadOrAll.Unread; + this.setState({ + appsRes: { state: "loading" }, + }); + this.setState({ + appsRes: await HttpService.client.listRegistrationApplications({ unread_only: unread_only, page: this.state.page, limit: fetchLimit, diff --cc src/shared/components/person/reports.tsx index 51b41c9,29daa3f..fb8e8b8 --- a/src/shared/components/person/reports.tsx +++ b/src/shared/components/person/reports.tsx @@@ -13,28 -13,23 +13,24 @@@ import PostReportView, PrivateMessageReportResponse, PrivateMessageReportView, - UserOperation, - wsJsonToRes, - wsUserOp, + ResolveCommentReport, + ResolvePostReport, + ResolvePrivateMessageReport, } from "lemmy-js-client"; - import { Subscription } from "rxjs"; import { i18n } from "../../i18next"; import { InitialFetchRequest } from "../../interfaces"; - import { UserService, WebSocketService } from "../../services"; + import { HttpService, UserService } from "../../services"; + import { FirstLoadService } from "../../services/FirstLoadService"; + import { RequestState } from "../../services/HttpService"; import { - WithPromiseKeys, ++ RouteDataResponse, amAdmin, + editCommentReport, + editPostReport, + editPrivateMessageReport, fetchLimit, - isBrowser, - myAuth, + myAuthRequired, setIsoData, - setupTippy, - toast, - updateCommentReportRes, - updatePostReportRes, - updatePrivateMessageReportRes, - wsClient, - wsSubscribe, } from "../../utils"; import { CommentReport } from "../comment/comment-report"; import { HtmlTags } from "../common/html-tags"; @@@ -61,12 -56,6 +57,12 @@@ enum MessageEnum PrivateMessageReport, } - interface ReportsData { ++type ReportsData = RouteDataResponse<{ + commentReportsResponse: ListCommentReportsResponse; + postReportsResponse: ListPostReportsResponse; + privateMessageReportsResponse?: ListPrivateMessageReportsResponse; - } ++}>; + type ItemType = { id: number; type_: MessageEnum; @@@ -87,47 -75,45 +82,49 @@@ interface ReportsState } export class Reports extends Component { - private isoData = setIsoData(this.context); + private isoData = setIsoData(this.context); - private subscription?: Subscription; state: ReportsState = { + commentReportsRes: { state: "empty" }, + postReportsRes: { state: "empty" }, + messageReportsRes: { state: "empty" }, unreadOrAll: UnreadOrAll.Unread, messageType: MessageType.All, - combined: [], page: 1, siteRes: this.isoData.site_res, - loading: true, + isIsomorphic: false, }; constructor(props: any, context: any) { super(props, context); this.handlePageChange = this.handlePageChange.bind(this); - - this.parseMessage = this.parseMessage.bind(this); - this.subscription = wsSubscribe(this.parseMessage); + this.handleResolveCommentReport = + this.handleResolveCommentReport.bind(this); + this.handleResolvePostReport = this.handleResolvePostReport.bind(this); + this.handleResolvePrivateMessageReport = + this.handleResolvePrivateMessageReport.bind(this); // Only fetch the data if coming from another route - if (this.isoData.path === this.context.router.route.match.url) { + if (FirstLoadService.isFirstLoad) { - const [commentReportsRes, postReportsRes, messageReportsRes] = - this.isoData.routeData; + const { - commentReportsResponse, - postReportsResponse, - privateMessageReportsResponse, ++ commentReportsResponse: commentReportsRes, ++ postReportsResponse: postReportsRes, ++ privateMessageReportsResponse: messageReportsRes, + } = this.isoData.routeData; + this.state = { ...this.state, - listCommentReportsResponse: commentReportsResponse, - listPostReportsResponse: postReportsResponse, - listPrivateMessageReportsResponse: privateMessageReportsResponse, + commentReportsRes, + postReportsRes, + isIsomorphic: true, }; - this.state = { - ...this.state, - combined: this.buildCombined(), - loading: false, - }; - } else { - this.refetch(); + if (amAdmin()) { + this.state = { + ...this.state, - messageReportsRes, ++ messageReportsRes: messageReportsRes ?? { state: "empty" }, + }; + } } } @@@ -404,192 -438,187 +449,180 @@@ } privateMessageReports() { - let reports = - this.state.listPrivateMessageReportsResponse?.private_message_reports; - return ( - reports && ( -
- {reports.map(pmr => ( - <> -
- - - ))} -
- ) - ); + const res = this.state.messageReportsRes; + switch (res.state) { + case "loading": + return ( +
+ +
+ ); + case "success": { + const reports = res.data.private_message_reports; + return ( +
+ {reports.map(pmr => ( + <> +
+ + + ))} +
+ ); + } + } } - handlePageChange(page: number) { + async handlePageChange(page: number) { this.setState({ page }); - this.refetch(); + await this.refetch(); } - handleUnreadOrAllChange(i: Reports, event: any) { + async handleUnreadOrAllChange(i: Reports, event: any) { i.setState({ unreadOrAll: Number(event.target.value), page: 1 }); - i.refetch(); + await i.refetch(); } - handleMessageTypeChange(i: Reports, event: any) { + async handleMessageTypeChange(i: Reports, event: any) { i.setState({ messageType: Number(event.target.value), page: 1 }); - i.refetch(); + await i.refetch(); } -- static fetchInitialData({ ++ static async fetchInitialData({ auth, client, - }: InitialFetchRequest): WithPromiseKeys { - }: InitialFetchRequest): Promise[] { - const promises: Promise>[] = []; - ++ }: InitialFetchRequest): Promise { const unresolved_only = true; const page = 1; const limit = fetchLimit; - if (auth) { - const commentReportsForm: ListCommentReports = { - unresolved_only, - page, - limit, - auth, - }; - promises.push(client.listCommentReports(commentReportsForm)); + const commentReportsForm: ListCommentReports = { + unresolved_only, + page, + limit, + auth: auth as string, + }; - const postReportsForm: ListPostReports = { + const postReportsForm: ListPostReports = { + unresolved_only, + page, + limit, + auth: auth as string, + }; + - const data: WithPromiseKeys = { - commentReportsResponse: client.listCommentReports(commentReportsForm), - postReportsResponse: client.listPostReports(postReportsForm), ++ const data: ReportsData = { ++ commentReportsResponse: await client.listCommentReports( ++ commentReportsForm ++ ), ++ postReportsResponse: await client.listPostReports(postReportsForm), + }; + + if (amAdmin()) { + const privateMessageReportsForm: ListPrivateMessageReports = { unresolved_only, page, limit, - auth, + auth: auth as string, }; - promises.push(client.listPostReports(postReportsForm)); - data.privateMessageReportsResponse = client.listPrivateMessageReports( - privateMessageReportsForm - if (amAdmin()) { - const privateMessageReportsForm: ListPrivateMessageReports = { - unresolved_only, - page, - limit, - auth, - }; - promises.push( - client.listPrivateMessageReports(privateMessageReportsForm) - ); - } else { - promises.push(Promise.resolve({ state: "empty" })); - } - } else { - promises.push( - Promise.resolve({ state: "empty" }), - Promise.resolve({ state: "empty" }), - Promise.resolve({ state: "empty" }) -- ); ++ data.privateMessageReportsResponse = ++ await client.listPrivateMessageReports(privateMessageReportsForm); } - return promises; + return data; } - refetch() { - const unresolved_only = this.state.unreadOrAll === UnreadOrAll.Unread; + async refetch() { + const unresolved_only = this.state.unreadOrAll == UnreadOrAll.Unread; const page = this.state.page; const limit = fetchLimit; - const auth = myAuth(); + const auth = myAuthRequired(); + + this.setState({ + commentReportsRes: { state: "loading" }, + postReportsRes: { state: "loading" }, + messageReportsRes: { state: "loading" }, + }); + + const form: + | ListCommentReports + | ListPostReports + | ListPrivateMessageReports = { + unresolved_only, + page, + limit, + auth, + }; - if (auth) { - const commentReportsForm: ListCommentReports = { - unresolved_only, - page, - limit, - auth, - }; + this.setState({ + commentReportsRes: await HttpService.client.listCommentReports(form), + postReportsRes: await HttpService.client.listPostReports(form), + }); - WebSocketService.Instance.send( - wsClient.listCommentReports(commentReportsForm) - ); + if (amAdmin()) { + this.setState({ + messageReportsRes: await HttpService.client.listPrivateMessageReports( + form + ), + }); + } + } - const postReportsForm: ListPostReports = { - unresolved_only, - page, - limit, - auth, - }; + async handleResolveCommentReport(form: ResolveCommentReport) { + const res = await HttpService.client.resolveCommentReport(form); + this.findAndUpdateCommentReport(res); + } - WebSocketService.Instance.send(wsClient.listPostReports(postReportsForm)); + async handleResolvePostReport(form: ResolvePostReport) { + const res = await HttpService.client.resolvePostReport(form); + this.findAndUpdatePostReport(res); + } - if (amAdmin()) { - const privateMessageReportsForm: ListPrivateMessageReports = { - unresolved_only, - page, - limit, - auth, - }; - WebSocketService.Instance.send( - wsClient.listPrivateMessageReports(privateMessageReportsForm) + async handleResolvePrivateMessageReport(form: ResolvePrivateMessageReport) { + const res = await HttpService.client.resolvePrivateMessageReport(form); + this.findAndUpdatePrivateMessageReport(res); + } + + findAndUpdateCommentReport(res: RequestState) { + this.setState(s => { + if (s.commentReportsRes.state == "success" && res.state == "success") { + s.commentReportsRes.data.comment_reports = editCommentReport( + res.data.comment_report_view, + s.commentReportsRes.data.comment_reports ); } - } + return s; + }); } - parseMessage(msg: any) { - let op = wsUserOp(msg); - console.log(msg); - if (msg.error) { - toast(i18n.t(msg.error), "danger"); - return; - } else if (msg.reconnect) { - this.refetch(); - } else if (op == UserOperation.ListCommentReports) { - let data = wsJsonToRes(msg); - this.setState({ listCommentReportsResponse: data }); - this.setState({ combined: this.buildCombined(), loading: false }); - // this.sendUnreadCount(); - window.scrollTo(0, 0); - setupTippy(); - } else if (op == UserOperation.ListPostReports) { - let data = wsJsonToRes(msg); - this.setState({ listPostReportsResponse: data }); - this.setState({ combined: this.buildCombined(), loading: false }); - // this.sendUnreadCount(); - window.scrollTo(0, 0); - setupTippy(); - } else if (op == UserOperation.ListPrivateMessageReports) { - let data = wsJsonToRes(msg); - this.setState({ listPrivateMessageReportsResponse: data }); - this.setState({ combined: this.buildCombined(), loading: false }); - // this.sendUnreadCount(); - window.scrollTo(0, 0); - setupTippy(); - } else if (op == UserOperation.ResolvePostReport) { - let data = wsJsonToRes(msg); - updatePostReportRes( - data.post_report_view, - this.state.listPostReportsResponse?.post_reports - ); - let urcs = UserService.Instance.unreadReportCountSub; - if (data.post_report_view.post_report.resolved) { - urcs.next(urcs.getValue() - 1); - } else { - urcs.next(urcs.getValue() + 1); - } - this.setState(this.state); - } else if (op == UserOperation.ResolveCommentReport) { - let data = wsJsonToRes(msg); - updateCommentReportRes( - data.comment_report_view, - this.state.listCommentReportsResponse?.comment_reports - ); - let urcs = UserService.Instance.unreadReportCountSub; - if (data.comment_report_view.comment_report.resolved) { - urcs.next(urcs.getValue() - 1); - } else { - urcs.next(urcs.getValue() + 1); + findAndUpdatePostReport(res: RequestState) { + this.setState(s => { + if (s.postReportsRes.state == "success" && res.state == "success") { + s.postReportsRes.data.post_reports = editPostReport( + res.data.post_report_view, + s.postReportsRes.data.post_reports + ); } - this.setState(this.state); - } else if (op == UserOperation.ResolvePrivateMessageReport) { - let data = wsJsonToRes(msg); - updatePrivateMessageReportRes( - data.private_message_report_view, - this.state.listPrivateMessageReportsResponse?.private_message_reports - ); - let urcs = UserService.Instance.unreadReportCountSub; - if (data.private_message_report_view.private_message_report.resolved) { - urcs.next(urcs.getValue() - 1); - } else { - urcs.next(urcs.getValue() + 1); + return s; + }); + } + + findAndUpdatePrivateMessageReport( + res: RequestState + ) { + this.setState(s => { + if (s.messageReportsRes.state == "success" && res.state == "success") { + s.messageReportsRes.data.private_message_reports = + editPrivateMessageReport( + res.data.private_message_report_view, + s.messageReportsRes.data.private_message_reports + ); } - this.setState(this.state); - } + return s; + }); } } diff --cc src/shared/components/post/create-post.tsx index 684dda2,71fac79..bb39cda --- a/src/shared/components/post/create-post.tsx +++ b/src/shared/components/post/create-post.tsx @@@ -1,22 -1,22 +1,24 @@@ import { Component } from "inferno"; import { RouteComponentProps } from "inferno-router/dist/Route"; import { + CreatePost as CreatePostI, GetCommunity, + GetCommunityResponse, GetSiteResponse, - PostView, - UserOperation, - wsJsonToRes, - wsUserOp, + ListCommunitiesResponse, } from "lemmy-js-client"; - import { Subscription } from "rxjs"; - import { InitialFetchRequest, PostFormParams } from "shared/interfaces"; import { i18n } from "../../i18next"; - import { WebSocketService } from "../../services"; + import { InitialFetchRequest, PostFormParams } from "../../interfaces"; + import { FirstLoadService } from "../../services/FirstLoadService"; + import { + HttpService, + RequestState, + WrappedLemmyHttp, + } from "../../services/HttpService"; import { Choice, QueryParams, - WithPromiseKeys, ++ RouteDataResponse, enableDownvotes, enableNsfw, getIdFromString, @@@ -36,10 -32,6 +34,11 @@@ export interface CreatePostProps communityId?: number; } - interface CreatePostData { ++type CreatePostData = RouteDataResponse<{ + communityResponse?: GetCommunityResponse; - } ++ initialCommunitiesRes: ListCommunitiesResponse; ++}>; + function getCreatePostQueryParams() { return getQueryParams({ communityId: getIdFromString, @@@ -56,8 -54,7 +61,7 @@@ export class CreatePost extends Compone RouteComponentProps>, CreatePostState > { - private isoData = setIsoData(this.context); + private isoData = setIsoData(this.context); - private subscription?: Subscription; state: CreatePostState = { siteRes: this.isoData.site_res, loading: true, @@@ -70,17 -69,14 +76,15 @@@ this.handleSelectedCommunityChange = this.handleSelectedCommunityChange.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) { - const { communityResponse } = this.isoData.routeData; + if (FirstLoadService.isFirstLoad) { - const [communityRes, listCommunitiesRes] = this.isoData.routeData; ++ const { communityResponse: communityRes, initialCommunitiesRes } = ++ this.isoData.routeData; - if (communityResponse) { + if (communityRes?.state === "success") { const communityChoice: Choice = { - label: communityResponse.community_view.community.title, - value: communityResponse.community_view.community.id.toString(), + label: communityRes.data.community_view.community.title, + value: communityRes.data.community_view.community.id.toString(), }; this.state = { @@@ -92,9 -88,9 +96,9 @@@ this.state = { ...this.state, loading: false, - initialCommunitiesRes: listCommunitiesRes, ++ initialCommunitiesRes, + isIsomorphic: true, }; - } else { - this.fetchCommunity(); } } @@@ -203,18 -218,23 +226,25 @@@ }); } - handlePostCreate(post_view: PostView) { - this.props.history.replace(`/post/${post_view.post.id}`); + async handlePostCreate(form: CreatePostI) { + const res = await HttpService.client.createPost(form); + + if (res.state === "success") { + const postId = res.data.post_view.post.id; + this.props.history.replace(`/post/${postId}`); + } } -- static fetchInitialData({ ++ static async fetchInitialData({ client, query: { communityId }, auth, - }: InitialFetchRequest>): Promise< - RequestState - >[] { - const promises: Promise>[] = []; + }: InitialFetchRequest< + QueryParams - >): WithPromiseKeys { - const data: WithPromiseKeys = {}; ++ >): Promise { ++ const data: CreatePostData = { ++ initialCommunitiesRes: await fetchCommunitiesForOptions(client), ++ }; if (communityId) { const form: GetCommunity = { @@@ -222,31 -242,13 +252,9 @@@ id: getIdFromString(communityId), }; - data.communityResponse = client.getCommunity(form); - promises.push(client.getCommunity(form)); - } else { - promises.push(Promise.resolve({ state: "empty" })); ++ data.communityResponse = await client.getCommunity(form); } - promises.push(fetchCommunitiesForOptions(client)); - - return promises; + return data; } - - parseMessage(msg: any) { - const op = wsUserOp(msg); - console.log(msg); - if (msg.error) { - toast(i18n.t(msg.error), "danger"); - return; - } - - if (op === UserOperation.GetCommunity) { - const { - community_view: { - community: { title, id }, - }, - } = wsJsonToRes(msg); - - this.setState({ - selectedCommunityChoice: { label: title, value: id.toString() }, - loading: false, - }); - } - } } diff --cc src/shared/components/post/post.tsx index fc8245d,9c68532..501c06d --- a/src/shared/components/post/post.tsx +++ b/src/shared/components/post/post.tsx @@@ -53,7 -77,6 +77,7 @@@ import isImage, myAuth, restoreScrollPosition, - saveCommentRes, ++ RouteDataResponse, saveScrollPosition, setIsoData, setupTippy, @@@ -73,11 -93,6 +94,11 @@@ import { PostListing } from "./post-lis const commentsShownInterval = 15; - interface PostData { - postResponse: GetPostResponse; - commentsResponse: GetCommentsResponse; - } ++type PostData = RouteDataResponse<{ ++ postRes: GetPostResponse; ++ commentsRes: GetCommentsResponse; ++}>; + interface PostState { postId?: number; commentId?: number; @@@ -96,13 -110,13 +116,13 @@@ } export class Post extends Component { - private subscription?: Subscription; - private isoData = setIsoData(this.context); + private isoData = setIsoData(this.context); private commentScrollDebounced: () => void; state: PostState = { + postRes: { state: "empty" }, + commentsRes: { state: "empty" }, postId: getIdFromProps(this.props), commentId: getCommentIdFromProps(this.props), - commentTree: [], commentSort: "Hot", commentViewType: CommentViewType.Tree, scrolled: false, @@@ -121,8 -168,8 +174,8 @@@ this.state = { ...this.state, commentSectionRef: createRef() }; // Only fetch the data if coming from another route - if (this.isoData.path === this.context.router.route.match.url) { - const { commentsResponse, postResponse } = this.isoData.routeData; + if (FirstLoadService.isFirstLoad) { - const [postRes, commentsRes] = this.isoData.routeData; ++ const { commentsRes, postRes } = this.isoData.routeData; this.state = { ...this.state, @@@ -205,8 -220,13 +226,12 @@@ } } - static fetchInitialData(req: InitialFetchRequest): WithPromiseKeys { - const pathSplit = req.path.split("/"); - static fetchInitialData({ - auth, ++ static async fetchInitialData({ + client, + path, - }: InitialFetchRequest): Promise[] { ++ auth, ++ }: InitialFetchRequest): Promise { + const pathSplit = path.split("/"); - const promises: Promise>[] = []; const pathType = pathSplit.at(1); const id = pathSplit.at(2) ? Number(pathSplit.at(2)) : undefined; @@@ -233,10 -252,10 +257,10 @@@ commentsForm.parent_id = id; } - promises.push(client.getPost(postForm)); - promises.push(client.getComments(commentsForm)); - - return promises; + return { - postResponse: req.client.getPost(postForm), - commentsResponse: req.client.getComments(commentsForm), ++ postRes: await client.getPost(postForm), ++ commentsRes: await client.getComments(commentsForm), + }; } componentWillUnmount() { diff --cc src/shared/components/private_message/create-private-message.tsx index c897c44,817cfd8..5b9eb98 --- a/src/shared/components/private_message/create-private-message.tsx +++ b/src/shared/components/private_message/create-private-message.tsx @@@ -3,18 -4,13 +4,14 @@@ import GetPersonDetails, GetPersonDetailsResponse, GetSiteResponse, - UserOperation, - wsJsonToRes, - wsUserOp, } from "lemmy-js-client"; - import { Subscription } from "rxjs"; import { i18n } from "../../i18next"; import { InitialFetchRequest } from "../../interfaces"; - import { WebSocketService } from "../../services"; + import { FirstLoadService } from "../../services/FirstLoadService"; + import { HttpService, RequestState } from "../../services/HttpService"; import { - WithPromiseKeys, ++ RouteDataResponse, getRecipientIdFromProps, - isBrowser, myAuth, setIsoData, toast, @@@ -25,27 -19,23 +20,27 @@@ import { HtmlTags } from "../common/htm import { Spinner } from "../common/icon"; import { PrivateMessageForm } from "./private-message-form"; - interface CreatePrivateMessageData { ++type CreatePrivateMessageData = RouteDataResponse<{ + recipientDetailsResponse: GetPersonDetailsResponse; - } ++}>; + interface CreatePrivateMessageState { siteRes: GetSiteResponse; - recipientDetailsRes?: GetPersonDetailsResponse; - recipient_id: number; - loading: boolean; + recipientRes: RequestState; + recipientId: number; + isIsomorphic: boolean; } export class CreatePrivateMessage extends Component< any, CreatePrivateMessageState > { - private isoData = setIsoData(this.context); + private isoData = setIsoData(this.context); - private subscription?: Subscription; state: CreatePrivateMessageState = { siteRes: this.isoData.site_res, - recipient_id: getRecipientIdFromProps(this.props), - loading: true, + recipientRes: { state: "empty" }, + recipientId: getRecipientIdFromProps(this.props), + isIsomorphic: false, }; constructor(props: any, context: any) { @@@ -53,56 -43,81 +48,87 @@@ this.handlePrivateMessageCreate = this.handlePrivateMessageCreate.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) { this.state = { ...this.state, - recipientDetailsRes: this.isoData.routeData.recipientDetailsResponse, - loading: false, - recipientRes: this.isoData.routeData[0], ++ recipientRes: this.isoData.routeData.recipientDetailsResponse, + isIsomorphic: true, }; - } else { - this.fetchPersonDetails(); } } - fetchPersonDetails() { - let form: GetPersonDetails = { - person_id: this.state.recipient_id, - sort: "New", - saved_only: false, - auth: myAuth(false), - }; - WebSocketService.Instance.send(wsClient.getPersonDetails(form)); + async componentDidMount() { + if (!this.state.isIsomorphic) { + await this.fetchPersonDetails(); + } } - static fetchInitialData( - req: InitialFetchRequest - ): WithPromiseKeys { - const person_id = Number(req.path.split("/").pop()); ++ static async fetchInitialData({ ++ client, ++ path, ++ auth, ++ }: InitialFetchRequest): Promise { ++ const person_id = Number(path.split("/").pop()); + + const form: GetPersonDetails = { + person_id, + sort: "New", + saved_only: false, - auth: req.auth, ++ auth, + }; + + return { - recipientDetailsResponse: req.client.getPersonDetails(form), ++ recipientDetailsResponse: await client.getPersonDetails(form), + }; + } + + async fetchPersonDetails() { + this.setState({ + recipientRes: { state: "loading" }, + }); + + this.setState({ + recipientRes: await HttpService.client.getPersonDetails({ + person_id: this.state.recipientId, + sort: "New", + saved_only: false, + auth: myAuth(), + }), + }); + } + - static fetchInitialData( - req: InitialFetchRequest - ): Promise>[] { - const person_id = Number(req.path.split("/").pop()); - const form: GetPersonDetails = { - person_id, - sort: "New", - saved_only: false, - auth: req.auth, - }; - return [req.client.getPersonDetails(form)]; - } - get documentTitle(): string { - let name_ = this.state.recipientDetailsRes?.person_view.person.name; - return name_ ? `${i18n.t("create_private_message")} - ${name_}` : ""; + if (this.state.recipientRes.state == "success") { + const name_ = this.state.recipientRes.data.person_view.person.name; + return `${i18n.t("create_private_message")} - ${name_}`; + } else { + return ""; + } } - componentWillUnmount() { - if (isBrowser()) { - this.subscription?.unsubscribe(); + renderRecipientRes() { + switch (this.state.recipientRes.state) { + case "loading": + return ( +
+ +
+ ); + case "success": { + const res = this.state.recipientRes.data; + return ( +
+
+
{i18n.t("create_private_message")}
+ +
+
+ ); + } } } diff --cc src/shared/components/search.tsx index c62c7a9,8097dbd..9f46673 --- a/src/shared/components/search.tsx +++ b/src/shared/components/search.tsx @@@ -32,7 -27,6 +27,7 @@@ import { HttpService, RequestState } fr import { Choice, QueryParams, - WithPromiseKeys, ++ RouteDataResponse, capitalizeFirstLetter, commentsToFlatNodes, communityToChoice, @@@ -81,14 -70,6 +71,14 @@@ interface SearchProps page: number; } - interface SearchData { ++type SearchData = RouteDataResponse<{ + communityResponse?: GetCommunityResponse; + listCommunitiesResponse?: ListCommunitiesResponse; + creatorDetailsResponse?: GetPersonDetailsResponse; + searchResponse?: SearchResponse; + resolveObjectResponse?: ResolveObjectResponse; - } ++}>; + type FilterType = "creator" | "community"; interface SearchState { @@@ -246,16 -228,19 +237,20 @@@ function getListing } export class Search extends Component { - private isoData = setIsoData(this.context); + private isoData = setIsoData(this.context); - private subscription?: Subscription; ++ state: SearchState = { - searchLoading: false, + resolveObjectRes: { state: "empty" }, + creatorDetailsRes: { state: "empty" }, + communitiesRes: { state: "empty" }, + communityRes: { state: "empty" }, siteRes: this.isoData.site_res, - communities: [], - searchCommunitiesLoading: false, - searchCreatorLoading: false, creatorSearchOptions: [], communitySearchOptions: [], + searchRes: { state: "empty" }, + searchCreatorLoading: false, + searchCommunitiesLoading: false, + isIsomorphic: false, }; constructor(props: any, context: any) { @@@ -279,65 -261,43 +271,64 @@@ }; // Only fetch the data if coming from another route - if (this.isoData.path === this.context.router.route.match.url) { + if (FirstLoadService.isFirstLoad) { - const [ - communityRes, - communitiesRes, - creatorDetailsRes, - searchRes, - resolveObjectRes, - ] = this.isoData.routeData; + const { - communityResponse, - creatorDetailsResponse, - listCommunitiesResponse, - resolveObjectResponse, - searchResponse, ++ communityResponse: communityRes, ++ creatorDetailsResponse: creatorDetailsRes, ++ listCommunitiesResponse: communitiesRes, ++ resolveObjectResponse: resolveObjectRes, ++ searchResponse: searchRes, + } = this.isoData.routeData; - // This can be single or multiple communities given - if (listCommunitiesResponse) { + this.state = { + ...this.state, - communitiesRes, - communityRes, - creatorDetailsRes, - creatorSearchOptions: - creatorDetailsRes.state == "success" - ? [personToChoice(creatorDetailsRes.data.person_view)] - : [], + isIsomorphic: true, + }; + - if (communityRes.state === "success") { ++ if (creatorDetailsRes?.state === "success") { + this.state = { + ...this.state, - communities: listCommunitiesResponse.communities, ++ creatorSearchOptions: ++ creatorDetailsRes?.state === "success" ++ ? [personToChoice(creatorDetailsRes.data.person_view)] ++ : [], ++ creatorDetailsRes, + }; + } - if (communityResponse) { ++ ++ if (communitiesRes?.state === "success") { this.state = { ...this.state, - communities: [communityResponse.community_view], -- communitySearchOptions: [ - communityToChoice(communityResponse.community_view), - communityToChoice(communityRes.data.community_view), -- ], ++ communitiesRes, }; } - this.state = { - ...this.state, - creatorDetails: creatorDetailsResponse, - creatorSearchOptions: creatorDetailsResponse - ? [personToChoice(creatorDetailsResponse.person_view)] - : [], - }; - if (q) { ++ if (communityRes?.state === "success") { + this.state = { + ...this.state, - searchRes, - resolveObjectRes, ++ communityRes, + }; + } + + if (q !== "") { + this.state = { + ...this.state, - searchResponse, - resolveObjectResponse, - searchLoading: false, + }; - } else { - this.search(); - } - } else { - const listCommunitiesForm: ListCommunities = { - type_: defaultListingType, - sort: defaultSortType, - limit: fetchLimit, - auth: myAuth(false), - }; + - WebSocketService.Instance.send( - wsClient.listCommunities(listCommunitiesForm) - ); ++ if (searchRes?.state === "success") { ++ this.state = { ++ ...this.state, ++ searchRes, ++ }; ++ } + - if (q) { - this.search(); ++ if (resolveObjectRes?.state === "success") { ++ this.state = { ++ ...this.state, ++ resolveObjectRes, ++ }; ++ } + } } } @@@ -345,26 -328,23 +359,24 @@@ saveScrollPosition(this.context); } -- static fetchInitialData({ ++ static async fetchInitialData({ client, auth, query: { communityId, creatorId, q, type, sort, listingType, page }, - }: InitialFetchRequest< - QueryParams - >): WithPromiseKeys { - }: InitialFetchRequest>): Promise< - RequestState - >[] { - const promises: Promise>[] = []; - ++ }: InitialFetchRequest>): Promise { const community_id = getIdFromString(communityId); - let communityResponse: Promise | undefined = - undefined; - let listCommunitiesResponse: Promise | undefined = ++ let communityResponse: RequestState | undefined = + undefined; ++ let listCommunitiesResponse: ++ | RequestState ++ | undefined = undefined; if (community_id) { const getCommunityForm: GetCommunity = { id: community_id, auth, }; - promises.push(client.getCommunity(getCommunityForm)); - promises.push(Promise.resolve({ state: "empty" })); + - communityResponse = client.getCommunity(getCommunityForm); ++ communityResponse = await client.getCommunity(getCommunityForm); } else { const listCommunitiesForm: ListCommunities = { type_: defaultListingType, @@@ -372,29 -352,23 +384,31 @@@ limit: fetchLimit, auth, }; - promises.push(Promise.resolve({ state: "empty" })); - promises.push(client.listCommunities(listCommunitiesForm)); + - listCommunitiesResponse = client.listCommunities(listCommunitiesForm); ++ listCommunitiesResponse = await client.listCommunities( ++ listCommunitiesForm ++ ); } const creator_id = getIdFromString(creatorId); - let creatorDetailsResponse: Promise | undefined = - undefined; ++ let creatorDetailsResponse: ++ | RequestState ++ | undefined = undefined; if (creator_id) { const getCreatorForm: GetPersonDetails = { person_id: creator_id, auth, }; - promises.push(client.getPersonDetails(getCreatorForm)); - } else { - promises.push(Promise.resolve({ state: "empty" })); + - creatorDetailsResponse = client.getPersonDetails(getCreatorForm); ++ creatorDetailsResponse = await client.getPersonDetails(getCreatorForm); } const query = getSearchQueryFromQuery(q); - let searchResponse: Promise | undefined = undefined; - let resolveObjectResponse: - | Promise - | undefined = undefined; ++ let searchResponse: RequestState | undefined = undefined; ++ let resolveObjectResponse: RequestState | undefined = ++ undefined; + if (query) { const form: SearchForm = { q: query, @@@ -409,16 -383,17 +423,16 @@@ }; if (query !== "") { - searchResponse = client.search(form); - promises.push(client.search(form)); ++ searchResponse = await client.search(form); if (auth) { const resolveObjectForm: ResolveObject = { q: query, auth, }; - resolveObjectResponse = client - promises.push(client.resolveObject(resolveObjectForm)); ++ resolveObjectResponse = await client + .resolveObject(resolveObjectForm) + .catch(() => undefined); } - } else { - promises.push(Promise.resolve({ state: "empty" })); - promises.push(Promise.resolve({ state: "empty" })); } } @@@ -493,7 -463,7 +508,7 @@@ minLength={1} />