diff --git a/src/server/index.tsx b/src/server/index.tsx index a93595e0..4ab4f76f 100644 --- a/src/server/index.tsx +++ b/src/server/index.tsx @@ -129,7 +129,7 @@ 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: any[] = []; + let routeData: Record = {}; let errorPageData: ErrorPageData | undefined; try { let try_site: any = await client.getSite(getSiteForm); @@ -160,18 +160,27 @@ server.get("/*", async (req, res) => { }; if (activeRoute?.fetchInitialData) { - routeData = 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); } + const error = Object.values(routeData).find(val => val?.error)?.error; + // Redirect to the 404 if there's an API error - if (routeData[0] && routeData[0].error) { - const error = routeData[0].error; + if (error) { console.error(error); if (error === "instance_is_private") { return res.redirect(`/signup`); diff --git a/src/shared/components/community/communities.tsx b/src/shared/components/community/communities.tsx index 2e147b83..53e0e967 100644 --- a/src/shared/components/community/communities.tsx +++ b/src/shared/components/community/communities.tsx @@ -16,6 +16,7 @@ import { i18n } from "../../i18next"; import { WebSocketService } from "../../services"; import { QueryParams, + WithPromiseKeys, getPageFromString, getQueryParams, getQueryString, @@ -36,6 +37,10 @@ import { CommunityLink } from "./community-link"; const communityLimit = 50; +interface CommunitiesData { + listCommunitiesResponse: ListCommunitiesResponse; +} + interface CommunitiesState { listCommunitiesResponse?: ListCommunitiesResponse; loading: boolean; @@ -88,7 +93,7 @@ function refetch() { export class Communities extends Component { private subscription?: Subscription; - private isoData = setIsoData(this.context); + private isoData = setIsoData(this.context); state: CommunitiesState = { loading: true, siteRes: this.isoData.site_res, @@ -105,10 +110,11 @@ export class Communities extends Component { // Only fetch the data if coming from another route if (this.isoData.path === this.context.router.route.match.url) { - const listRes = this.isoData.routeData[0] as ListCommunitiesResponse; + const { listCommunitiesResponse } = this.isoData.routeData; + this.state = { ...this.state, - listCommunitiesResponse: listRes, + listCommunitiesResponse, loading: false, }; } else { @@ -312,7 +318,9 @@ export class Communities extends Component { query: { listingType, page }, client, auth, - }: InitialFetchRequest>): Promise[] { + }: InitialFetchRequest< + QueryParams + >): WithPromiseKeys { const listCommunitiesForm: ListCommunities = { type_: getListingTypeFromQuery(listingType), sort: "TopMonth", @@ -321,7 +329,9 @@ export class Communities extends Component { auth: auth, }; - return [client.listCommunities(listCommunitiesForm)]; + return { + listCommunitiesResponse: client.listCommunities(listCommunitiesForm), + }; } parseMessage(msg: any) { diff --git a/src/shared/components/community/community.tsx b/src/shared/components/community/community.tsx index af562c19..f3ab1e21 100644 --- a/src/shared/components/community/community.tsx +++ b/src/shared/components/community/community.tsx @@ -33,6 +33,7 @@ import { import { UserService, WebSocketService } from "../../services"; import { QueryParams, + WithPromiseKeys, commentsToFlatNodes, communityRSSUrl, createCommentLikeRes, @@ -76,6 +77,12 @@ import { SiteSidebar } from "../home/site-sidebar"; import { PostListings } from "../post/post-listings"; import { CommunityLink } from "./community-link"; +interface CommunityData { + communityResponse: GetCommunityResponse; + postsResponse?: GetPostsResponse; + commentsResponse?: GetCommentsResponse; +} + interface State { communityRes?: GetCommunityResponse; communityLoading: boolean; @@ -115,7 +122,7 @@ export class Community extends Component< RouteComponentProps<{ name: string }>, State > { - private isoData = setIsoData(this.context); + private isoData = setIsoData(this.context); private subscription?: Subscription; state: State = { communityLoading: true, @@ -137,23 +144,20 @@ export class Community extends Component< // 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; + this.state = { ...this.state, - communityRes: this.isoData.routeData[0] as GetCommunityResponse, + communityRes: communityResponse, }; - const postsRes = this.isoData.routeData[1] as - | GetPostsResponse - | undefined; - const commentsRes = this.isoData.routeData[2] as - | GetCommentsResponse - | undefined; - if (postsRes) { - this.state = { ...this.state, posts: postsRes.posts }; + if (postsResponse) { + this.state = { ...this.state, posts: postsResponse.posts }; } - if (commentsRes) { - this.state = { ...this.state, comments: commentsRes.comments }; + if (commentsResponse) { + this.state = { ...this.state, comments: commentsResponse.comments }; } this.state = { @@ -189,16 +193,16 @@ export class Community extends Component< path, query: { dataType: urlDataType, page: urlPage, sort: urlSort }, auth, - }: InitialFetchRequest>): Promise[] { + }: InitialFetchRequest< + QueryParams + >): WithPromiseKeys { const pathSplit = path.split("/"); - const promises: Promise[] = []; const communityName = pathSplit[2]; const communityForm: GetCommunity = { name: communityName, auth, }; - promises.push(client.getCommunity(communityForm)); const dataType = getDataTypeFromQuery(urlDataType); @@ -206,6 +210,9 @@ export class Community extends Component< const page = getPageFromString(urlPage); + let postsResponse: Promise | undefined = undefined; + let commentsResponse: Promise | undefined = undefined; + if (dataType === DataType.Post) { const getPostsForm: GetPosts = { community_name: communityName, @@ -216,8 +223,8 @@ export class Community extends Component< saved_only: false, auth, }; - promises.push(client.getPosts(getPostsForm)); - promises.push(Promise.resolve()); + + postsResponse = client.getPosts(getPostsForm); } else { const getCommentsForm: GetComments = { community_name: communityName, @@ -228,11 +235,15 @@ export class Community extends Component< saved_only: false, auth, }; - promises.push(Promise.resolve()); - promises.push(client.getComments(getCommentsForm)); + + commentsResponse = client.getComments(getCommentsForm); } - return promises; + return { + communityResponse: client.getCommunity(communityForm), + commentsResponse, + postsResponse, + }; } get documentTitle(): string { diff --git a/src/shared/components/home/admin-settings.tsx b/src/shared/components/home/admin-settings.tsx index d266db58..4419cf36 100644 --- a/src/shared/components/home/admin-settings.tsx +++ b/src/shared/components/home/admin-settings.tsx @@ -2,7 +2,6 @@ import autosize from "autosize"; import { Component, linkEvent } from "inferno"; import { BannedPersonsResponse, - GetBannedPersons, GetFederatedInstancesResponse, GetSiteResponse, PersonView, @@ -16,6 +15,7 @@ import { i18n } from "../../i18next"; import { InitialFetchRequest } from "../../interfaces"; import { WebSocketService } from "../../services"; import { + WithPromiseKeys, capitalizeFirstLetter, isBrowser, myAuth, @@ -35,6 +35,11 @@ import RateLimitForm from "./rate-limit-form"; import { SiteForm } from "./site-form"; import { TaglineForm } from "./tagline-form"; +interface AdminSettingsData { + bannedPersonsResponse: BannedPersonsResponse; + federatedInstancesResponse: GetFederatedInstancesResponse; +} + interface AdminSettingsState { siteRes: GetSiteResponse; instancesRes?: GetFederatedInstancesResponse; @@ -45,7 +50,7 @@ interface AdminSettingsState { 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, @@ -62,11 +67,13 @@ export class AdminSettings extends Component { // 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; + this.state = { ...this.state, - banned: (this.isoData.routeData[0] as BannedPersonsResponse).banned, - instancesRes: this.isoData - .routeData[1] as GetFederatedInstancesResponse, + banned: bannedPersonsResponse.banned, + instancesRes: federatedInstancesResponse, loading: false, }; } else { @@ -84,17 +91,16 @@ export class AdminSettings extends Component { } } - static fetchInitialData(req: InitialFetchRequest): Promise[] { - let promises: Promise[] = []; - - let auth = req.auth; - if (auth) { - let bannedPersonsForm: GetBannedPersons = { auth }; - promises.push(req.client.getBannedPersons(bannedPersonsForm)); - promises.push(req.client.getFederatedInstances({ auth })); - } - - return promises; + static fetchInitialData({ + auth, + client, + }: InitialFetchRequest): WithPromiseKeys { + return { + bannedPersonsResponse: client.getBannedPersons({ auth: auth as string }), + federatedInstancesResponse: client.getFederatedInstances({ + auth: auth as string, + }) as Promise, + }; } componentDidMount() { diff --git a/src/shared/components/home/home.tsx b/src/shared/components/home/home.tsx index 52a82126..e85c3e66 100644 --- a/src/shared/components/home/home.tsx +++ b/src/shared/components/home/home.tsx @@ -69,6 +69,7 @@ import { toast, trendingFetchLimit, updatePersonBlock, + WithPromiseKeys, wsClient, wsSubscribe, } from "../../utils"; @@ -103,6 +104,12 @@ interface HomeProps { page: number; } +interface HomeData { + postsResponse?: GetPostsResponse; + commentsResponse?: GetCommentsResponse; + trendingResponse: ListCommunitiesResponse; +} + function getDataTypeFromQuery(type?: string): DataType { return type ? DataType[type] : DataType.Post; } @@ -237,7 +244,7 @@ function getRss(listingType: ListingType) { } export class Home extends Component { - private isoData = setIsoData(this.context); + private isoData = setIsoData(this.context); private subscription?: Subscription; state: HomeState = { trendingCommunities: [], @@ -264,22 +271,15 @@ export class Home extends Component { // Only fetch the data if coming from another route if (this.isoData.path === this.context.router.route.match.url) { - const postsRes = this.isoData.routeData[0] as - | GetPostsResponse - | undefined; - const commentsRes = this.isoData.routeData[1] as - | GetCommentsResponse - | undefined; - const trendingRes = this.isoData.routeData[2] as - | ListCommunitiesResponse - | undefined; + const { trendingResponse, commentsResponse, postsResponse } = + this.isoData.routeData; - if (postsRes) { - this.state = { ...this.state, posts: postsRes.posts }; + if (postsResponse) { + this.state = { ...this.state, posts: postsResponse.posts }; } - if (commentsRes) { - this.state = { ...this.state, comments: commentsRes.comments }; + if (commentsResponse) { + this.state = { ...this.state, comments: commentsResponse.comments }; } if (isBrowser()) { @@ -290,7 +290,7 @@ export class Home extends Component { const taglines = this.state?.siteRes?.taglines ?? []; this.state = { ...this.state, - trendingCommunities: trendingRes?.communities ?? [], + trendingCommunities: trendingResponse?.communities ?? [], loading: false, tagline: getRandomFromList(taglines)?.content, }; @@ -317,7 +317,7 @@ export class Home extends Component { client, auth, query: { dataType: urlDataType, listingType, page: urlPage, sort: urlSort }, - }: InitialFetchRequest>): Promise[] { + }: InitialFetchRequest>): WithPromiseKeys { const dataType = getDataTypeFromQuery(urlDataType); // TODO figure out auth default_listingType, default_sort_type @@ -328,6 +328,9 @@ export class Home extends Component { const promises: Promise[] = []; + let postsResponse: Promise | undefined = undefined; + let commentsResponse: Promise | undefined = undefined; + if (dataType === DataType.Post) { const getPostsForm: GetPosts = { type_, @@ -338,8 +341,7 @@ export class Home extends Component { auth, }; - promises.push(client.getPosts(getPostsForm)); - promises.push(Promise.resolve()); + postsResponse = client.getPosts(getPostsForm); } else { const getCommentsForm: GetComments = { page, @@ -349,8 +351,8 @@ export class Home extends Component { saved_only: false, auth, }; - promises.push(Promise.resolve()); - promises.push(client.getComments(getCommentsForm)); + + commentsResponse = client.getComments(getCommentsForm); } const trendingCommunitiesForm: ListCommunities = { @@ -361,7 +363,11 @@ export class Home extends Component { }; promises.push(client.listCommunities(trendingCommunitiesForm)); - return promises; + return { + trendingResponse: client.listCommunities(trendingCommunitiesForm), + commentsResponse, + postsResponse, + }; } get documentTitle(): string { diff --git a/src/shared/components/home/instances.tsx b/src/shared/components/home/instances.tsx index 674374d0..fd1ed617 100644 --- a/src/shared/components/home/instances.tsx +++ b/src/shared/components/home/instances.tsx @@ -12,6 +12,7 @@ import { i18n } from "../../i18next"; import { InitialFetchRequest } from "../../interfaces"; import { WebSocketService } from "../../services"; import { + WithPromiseKeys, isBrowser, relTags, setIsoData, @@ -21,6 +22,10 @@ import { } from "../../utils"; import { HtmlTags } from "../common/html-tags"; +interface InstancesData { + federatedInstancesResponse: GetFederatedInstancesResponse; +} + interface InstancesState { siteRes: GetSiteResponse; instancesRes?: GetFederatedInstancesResponse; @@ -28,7 +33,7 @@ interface InstancesState { } export class Instances extends Component { - private isoData = setIsoData(this.context); + private isoData = setIsoData(this.context); state: InstancesState = { siteRes: this.isoData.site_res, loading: true, @@ -45,8 +50,7 @@ export class Instances extends Component { if (this.isoData.path == this.context.router.route.match.url) { this.state = { ...this.state, - instancesRes: this.isoData - .routeData[0] as GetFederatedInstancesResponse, + instancesRes: this.isoData.routeData.federatedInstancesResponse, loading: false, }; } else { @@ -54,12 +58,14 @@ export class Instances extends Component { } } - static fetchInitialData(req: InitialFetchRequest): Promise[] { - let promises: Promise[] = []; - - promises.push(req.client.getFederatedInstances({})); - - return promises; + static fetchInitialData({ + client, + }: InitialFetchRequest): WithPromiseKeys { + return { + federatedInstancesResponse: client.getFederatedInstances( + {} + ) as Promise, + }; } get documentTitle(): string { diff --git a/src/shared/components/modlog.tsx b/src/shared/components/modlog.tsx index ef28816b..cb4b37f4 100644 --- a/src/shared/components/modlog.tsx +++ b/src/shared/components/modlog.tsx @@ -39,6 +39,7 @@ import { WebSocketService } from "../services"; import { Choice, QueryParams, + WithPromiseKeys, amAdmin, amMod, debounce, @@ -83,6 +84,13 @@ type View = | AdminPurgePostView | AdminPurgeCommentView; +interface ModlogData { + modlogResponse: GetModlogResponse; + communityResponse?: GetCommunityResponse; + modUserResponse?: GetPersonDetailsResponse; + userResponse?: GetPersonDetailsResponse; +} + interface ModlogType { id: number; type_: ModlogActionType; @@ -642,7 +650,7 @@ export class Modlog extends Component< RouteComponentProps<{ communityId?: string }>, ModlogState > { - private isoData = setIsoData(this.context); + private isoData = setIsoData(this.context); private subscription?: Subscription; state: ModlogState = { @@ -667,35 +675,35 @@ export class Modlog extends Component< // Only fetch the data if coming from another route if (this.isoData.path === this.context.router.route.match.url) { + const { + modlogResponse, + communityResponse, + modUserResponse, + userResponse, + } = this.isoData.routeData; + this.state = { ...this.state, - res: this.isoData.routeData[0] as GetModlogResponse, + res: modlogResponse, }; - const communityRes: GetCommunityResponse | undefined = - this.isoData.routeData[1]; - // Getting the moderators this.state = { ...this.state, - communityMods: communityRes?.moderators, + communityMods: communityResponse?.moderators, }; - const filteredModRes: GetPersonDetailsResponse | undefined = - this.isoData.routeData[2]; - if (filteredModRes) { + if (modUserResponse) { this.state = { ...this.state, - modSearchOptions: [personToChoice(filteredModRes.person_view)], + modSearchOptions: [personToChoice(modUserResponse.person_view)], }; } - const filteredUserRes: GetPersonDetailsResponse | undefined = - this.isoData.routeData[3]; - if (filteredUserRes) { + if (userResponse) { this.state = { ...this.state, - userSearchOptions: [personToChoice(filteredUserRes.person_view)], + userSearchOptions: [personToChoice(userResponse.person_view)], }; } @@ -986,9 +994,10 @@ export class Modlog extends Component< query: { modId: urlModId, page, userId: urlUserId, actionType }, auth, site, - }: InitialFetchRequest>): Promise[] { + }: InitialFetchRequest< + QueryParams + >): WithPromiseKeys { 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) @@ -1005,41 +1014,47 @@ export class Modlog extends Component< auth, }; - promises.push(client.getModlog(modlogForm)); + let communityResponse: Promise | undefined = + undefined; if (communityId) { const communityForm: GetCommunity = { id: communityId, auth, }; - promises.push(client.getCommunity(communityForm)); - } else { - promises.push(Promise.resolve()); + + communityResponse = client.getCommunity(communityForm); } + let modUserResponse: Promise | undefined = + undefined; + if (modId) { const getPersonForm: GetPersonDetails = { person_id: modId, auth, }; - promises.push(client.getPersonDetails(getPersonForm)); - } else { - promises.push(Promise.resolve()); + modUserResponse = client.getPersonDetails(getPersonForm); } + let userResponse: Promise | undefined = undefined; + if (userId) { const getPersonForm: GetPersonDetails = { person_id: userId, auth, }; - promises.push(client.getPersonDetails(getPersonForm)); - } else { - promises.push(Promise.resolve()); + userResponse = client.getPersonDetails(getPersonForm); } - return promises; + return { + modlogResponse: client.getModlog(modlogForm), + communityResponse, + modUserResponse, + userResponse, + }; } parseMessage(msg: any) { diff --git a/src/shared/components/person/inbox.tsx b/src/shared/components/person/inbox.tsx index 8145ab62..6393fc62 100644 --- a/src/shared/components/person/inbox.tsx +++ b/src/shared/components/person/inbox.tsx @@ -29,6 +29,7 @@ import { i18n } from "../../i18next"; import { CommentViewType, InitialFetchRequest } from "../../interfaces"; import { UserService, WebSocketService } from "../../services"; import { + WithPromiseKeys, commentsToFlatNodes, createCommentLikeRes, editCommentRes, @@ -69,6 +70,13 @@ enum ReplyEnum { Mention, Message, } + +interface InboxData { + repliesResponse: GetRepliesResponse; + personMentionsResponse: GetPersonMentionsResponse; + privateMessagesResponse: PrivateMessagesResponse; +} + type ReplyType = { id: number; type_: ReplyEnum; @@ -90,7 +98,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, @@ -115,17 +123,18 @@ export class Inbox extends Component { 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 (this.isoData.path === this.context.router.route.match.url) { + const { + personMentionsResponse, + privateMessagesResponse, + repliesResponse, + } = this.isoData.routeData; + this.state = { ...this.state, - replies: - (this.isoData.routeData[0] as GetRepliesResponse).replies || [], - mentions: - (this.isoData.routeData[1] as GetPersonMentionsResponse).mentions || - [], - messages: - (this.isoData.routeData[2] as PrivateMessagesResponse) - .private_messages || [], + replies: repliesResponse.replies ?? [], + mentions: personMentionsResponse.mentions ?? [], + messages: privateMessagesResponse.private_messages ?? [], loading: false, }; this.state = { ...this.state, combined: this.buildCombined() }; @@ -481,53 +490,51 @@ export class Inbox extends Component { i.refetch(); } - static fetchInitialData(req: InitialFetchRequest): Promise[] { - let promises: Promise[] = []; + static fetchInitialData({ + auth, + client, + }: InitialFetchRequest): WithPromiseKeys { + const sort: CommentSortType = "New"; - let sort: CommentSortType = "New"; - let auth = req.auth; + // 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 - let repliesForm: GetReplies = { - sort: "New", - unread_only: true, - page: 1, - limit: fetchLimit, - auth, - }; - promises.push(req.client.getReplies(repliesForm)); + const personMentionsForm: GetPersonMentions = { + sort, + unread_only: true, + page: 1, + limit: fetchLimit, + auth: auth as string, + }; - let personMentionsForm: GetPersonMentions = { - sort, - unread_only: true, - page: 1, - limit: fetchLimit, - auth, - }; - promises.push(req.client.getPersonMentions(personMentionsForm)); + const privateMessagesForm: GetPrivateMessages = { + unread_only: true, + page: 1, + limit: fetchLimit, + auth: auth as string, + }; - let privateMessagesForm: GetPrivateMessages = { - unread_only: true, - page: 1, - limit: fetchLimit, - auth, - }; - promises.push(req.client.getPrivateMessages(privateMessagesForm)); - } - - return promises; + return { + privateMessagesResponse: client.getPrivateMessages(privateMessagesForm), + personMentionsResponse: client.getPersonMentions(personMentionsForm), + repliesResponse: client.getReplies(repliesForm), + }; } refetch() { - let sort = this.state.sort; - let unread_only = this.state.unreadOrAll == UnreadOrAll.Unread; - let page = this.state.page; - let limit = fetchLimit; - let auth = myAuth(); + const { sort, page, unreadOrAll } = this.state; + const unread_only = unreadOrAll === UnreadOrAll.Unread; + const limit = fetchLimit; + const auth = myAuth(); if (auth) { - let repliesForm: GetReplies = { + const repliesForm: GetReplies = { sort, unread_only, page, @@ -536,7 +543,7 @@ export class Inbox extends Component { }; WebSocketService.Instance.send(wsClient.getReplies(repliesForm)); - let personMentionsForm: GetPersonMentions = { + const personMentionsForm: GetPersonMentions = { sort, unread_only, page, @@ -547,7 +554,7 @@ export class Inbox extends Component { wsClient.getPersonMentions(personMentionsForm) ); - let privateMessagesForm: GetPrivateMessages = { + const privateMessagesForm: GetPrivateMessages = { unread_only, page, limit, diff --git a/src/shared/components/person/profile.tsx b/src/shared/components/person/profile.tsx index 42c7c306..00a44197 100644 --- a/src/shared/components/person/profile.tsx +++ b/src/shared/components/person/profile.tsx @@ -29,6 +29,7 @@ import { InitialFetchRequest, PersonDetailsView } from "../../interfaces"; import { UserService, WebSocketService } from "../../services"; import { QueryParams, + WithPromiseKeys, canMod, capitalizeFirstLetter, createCommentLikeRes, @@ -67,6 +68,10 @@ import { CommunityLink } from "../community/community-link"; import { PersonDetails } from "./person-details"; import { PersonListing } from "./person-listing"; +interface ProfileData { + personResponse: GetPersonDetailsResponse; +} + interface ProfileState { personRes?: GetPersonDetailsResponse; loading: boolean; @@ -152,7 +157,7 @@ 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, @@ -175,7 +180,7 @@ export class Profile extends Component< if (this.isoData.path === this.context.router.route.match.url) { this.state = { ...this.state, - personRes: this.isoData.routeData[0] as GetPersonDetailsResponse, + personRes: this.isoData.routeData.personResponse, loading: false, }; } else { @@ -223,7 +228,9 @@ export class Profile extends Component< path, query: { page, sort, view: urlView }, auth, - }: InitialFetchRequest>): Promise[] { + }: InitialFetchRequest< + QueryParams + >): WithPromiseKeys { const pathSplit = path.split("/"); const username = pathSplit[2]; @@ -238,7 +245,9 @@ export class Profile extends Component< auth, }; - return [client.getPersonDetails(form)]; + return { + personResponse: client.getPersonDetails(form), + }; } componentDidMount() { diff --git a/src/shared/components/person/registration-applications.tsx b/src/shared/components/person/registration-applications.tsx index 39b65900..72ac5016 100644 --- a/src/shared/components/person/registration-applications.tsx +++ b/src/shared/components/person/registration-applications.tsx @@ -13,6 +13,7 @@ import { i18n } from "../../i18next"; import { InitialFetchRequest } from "../../interfaces"; import { UserService, WebSocketService } from "../../services"; import { + WithPromiseKeys, fetchLimit, isBrowser, myAuth, @@ -33,6 +34,10 @@ enum UnreadOrAll { All, } +interface RegistrationApplicationsData { + listRegistrationApplicationsResponse: ListRegistrationApplicationsResponse; +} + interface RegistrationApplicationsState { listRegistrationApplicationsResponse?: ListRegistrationApplicationsResponse; siteRes: GetSiteResponse; @@ -45,7 +50,7 @@ export class RegistrationApplications extends Component< any, RegistrationApplicationsState > { - private isoData = setIsoData(this.context); + private isoData = setIsoData(this.context); private subscription?: Subscription; state: RegistrationApplicationsState = { siteRes: this.isoData.site_res, @@ -63,11 +68,11 @@ export class RegistrationApplications extends Component< 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 (this.isoData.path === this.context.router.route.match.url) { this.state = { ...this.state, - listRegistrationApplicationsResponse: this.isoData - .routeData[0] as ListRegistrationApplicationsResponse, + listRegistrationApplicationsResponse: + this.isoData.routeData.listRegistrationApplicationsResponse, loading: false, }; } else { @@ -192,21 +197,21 @@ export class RegistrationApplications extends Component< this.refetch(); } - static fetchInitialData(req: InitialFetchRequest): Promise[] { - let promises: Promise[] = []; + static fetchInitialData({ + auth, + client, + }: InitialFetchRequest): WithPromiseKeys { + const form: ListRegistrationApplications = { + unread_only: true, + page: 1, + limit: fetchLimit, + auth: auth as string, + }; - let auth = req.auth; - if (auth) { - let form: ListRegistrationApplications = { - unread_only: true, - page: 1, - limit: fetchLimit, - auth, - }; - promises.push(req.client.listRegistrationApplications(form)); - } - - return promises; + return { + listRegistrationApplicationsResponse: + client.listRegistrationApplications(form), + }; } refetch() { diff --git a/src/shared/components/person/reports.tsx b/src/shared/components/person/reports.tsx index 3c00f545..51b41c92 100644 --- a/src/shared/components/person/reports.tsx +++ b/src/shared/components/person/reports.tsx @@ -22,6 +22,7 @@ import { i18n } from "../../i18next"; import { InitialFetchRequest } from "../../interfaces"; import { UserService, WebSocketService } from "../../services"; import { + WithPromiseKeys, amAdmin, fetchLimit, isBrowser, @@ -60,6 +61,12 @@ enum MessageEnum { PrivateMessageReport, } +interface ReportsData { + commentReportsResponse: ListCommentReportsResponse; + postReportsResponse: ListPostReportsResponse; + privateMessageReportsResponse?: ListPrivateMessageReportsResponse; +} + type ItemType = { id: number; type_: MessageEnum; @@ -80,7 +87,7 @@ interface ReportsState { } export class Reports extends Component { - private isoData = setIsoData(this.context); + private isoData = setIsoData(this.context); private subscription?: Subscription; state: ReportsState = { unreadOrAll: UnreadOrAll.Unread, @@ -100,21 +107,20 @@ export class Reports extends Component { 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 (this.isoData.path === this.context.router.route.match.url) { + const { + commentReportsResponse, + postReportsResponse, + privateMessageReportsResponse, + } = this.isoData.routeData; + this.state = { ...this.state, - listCommentReportsResponse: this.isoData - .routeData[0] as ListCommentReportsResponse, - listPostReportsResponse: this.isoData - .routeData[1] as ListPostReportsResponse, + listCommentReportsResponse: commentReportsResponse, + listPostReportsResponse: postReportsResponse, + listPrivateMessageReportsResponse: privateMessageReportsResponse, }; - if (amAdmin()) { - this.state = { - ...this.state, - listPrivateMessageReportsResponse: this.isoData - .routeData[2] as ListPrivateMessageReportsResponse, - }; - } + this.state = { ...this.state, combined: this.buildCombined(), @@ -432,73 +438,78 @@ export class Reports extends Component { i.refetch(); } - static fetchInitialData(req: InitialFetchRequest): Promise[] { - let promises: Promise[] = []; + static fetchInitialData({ + auth, + client, + }: InitialFetchRequest): WithPromiseKeys { + const unresolved_only = true; + const page = 1; + const limit = fetchLimit; - let unresolved_only = true; - let page = 1; - let limit = fetchLimit; - let auth = req.auth; + const commentReportsForm: ListCommentReports = { + unresolved_only, + page, + limit, + auth: auth as string, + }; - if (auth) { - let commentReportsForm: ListCommentReports = { + const postReportsForm: ListPostReports = { + unresolved_only, + page, + limit, + auth: auth as string, + }; + + const data: WithPromiseKeys = { + commentReportsResponse: client.listCommentReports(commentReportsForm), + postReportsResponse: client.listPostReports(postReportsForm), + }; + + if (amAdmin()) { + const privateMessageReportsForm: ListPrivateMessageReports = { unresolved_only, page, limit, - auth, + auth: auth as string, }; - promises.push(req.client.listCommentReports(commentReportsForm)); - let postReportsForm: ListPostReports = { - unresolved_only, - page, - limit, - auth, - }; - promises.push(req.client.listPostReports(postReportsForm)); - - if (amAdmin()) { - let privateMessageReportsForm: ListPrivateMessageReports = { - unresolved_only, - page, - limit, - auth, - }; - promises.push( - req.client.listPrivateMessageReports(privateMessageReportsForm) - ); - } + data.privateMessageReportsResponse = client.listPrivateMessageReports( + privateMessageReportsForm + ); } - return promises; + return data; } refetch() { - let unresolved_only = this.state.unreadOrAll == UnreadOrAll.Unread; - let page = this.state.page; - let limit = fetchLimit; - let auth = myAuth(); + const unresolved_only = this.state.unreadOrAll === UnreadOrAll.Unread; + const page = this.state.page; + const limit = fetchLimit; + const auth = myAuth(); + if (auth) { - let commentReportsForm: ListCommentReports = { + const commentReportsForm: ListCommentReports = { unresolved_only, page, limit, auth, }; + WebSocketService.Instance.send( wsClient.listCommentReports(commentReportsForm) ); - let postReportsForm: ListPostReports = { + const postReportsForm: ListPostReports = { unresolved_only, page, limit, auth, }; + WebSocketService.Instance.send(wsClient.listPostReports(postReportsForm)); if (amAdmin()) { - let privateMessageReportsForm: ListPrivateMessageReports = { + const privateMessageReportsForm: ListPrivateMessageReports = { unresolved_only, page, limit, diff --git a/src/shared/components/post/create-post.tsx b/src/shared/components/post/create-post.tsx index d8a2ccef..f43cbb07 100644 --- a/src/shared/components/post/create-post.tsx +++ b/src/shared/components/post/create-post.tsx @@ -16,6 +16,7 @@ import { WebSocketService } from "../../services"; import { Choice, QueryParams, + WithPromiseKeys, enableDownvotes, enableNsfw, getIdFromString, @@ -36,6 +37,10 @@ export interface CreatePostProps { communityId?: number; } +interface CreatePostData { + communityResponse?: GetCommunityResponse; +} + function getCreatePostQueryParams() { return getQueryParams({ communityId: getIdFromString, @@ -52,7 +57,7 @@ export class CreatePost extends Component< RouteComponentProps>, CreatePostState > { - private isoData = setIsoData(this.context); + private isoData = setIsoData(this.context); private subscription?: Subscription; state: CreatePostState = { siteRes: this.isoData.site_res, @@ -71,14 +76,12 @@ export class CreatePost extends Component< // Only fetch the data if coming from another route if (this.isoData.path === this.context.router.route.match.url) { - const communityRes = this.isoData.routeData[0] as - | GetCommunityResponse - | undefined; + const { communityResponse } = this.isoData.routeData; - if (communityRes) { + if (communityResponse) { const communityChoice: Choice = { - label: communityRes.community_view.community.name, - value: communityRes.community_view.community.id.toString(), + label: communityResponse.community_view.community.name, + value: communityResponse.community_view.community.id.toString(), }; this.state = { @@ -206,8 +209,10 @@ export class CreatePost extends Component< client, query: { communityId }, auth, - }: InitialFetchRequest>): Promise[] { - const promises: Promise[] = []; + }: InitialFetchRequest< + QueryParams + >): WithPromiseKeys { + const data: WithPromiseKeys = {}; if (communityId) { const form: GetCommunity = { @@ -215,12 +220,10 @@ export class CreatePost extends Component< id: getIdFromString(communityId), }; - promises.push(client.getCommunity(form)); - } else { - promises.push(Promise.resolve()); + data.communityResponse = client.getCommunity(form); } - return promises; + return data; } parseMessage(msg: any) { diff --git a/src/shared/components/post/post.tsx b/src/shared/components/post/post.tsx index 96d7911e..fc8245de 100644 --- a/src/shared/components/post/post.tsx +++ b/src/shared/components/post/post.tsx @@ -60,6 +60,7 @@ import { toast, trendingFetchLimit, updatePersonBlock, + WithPromiseKeys, wsClient, wsSubscribe, } from "../../utils"; @@ -72,6 +73,11 @@ import { PostListing } from "./post-listing"; const commentsShownInterval = 15; +interface PostData { + postResponse: GetPostResponse; + commentsResponse: GetCommentsResponse; +} + interface PostState { postId?: number; commentId?: number; @@ -91,7 +97,7 @@ interface PostState { export class Post extends Component { private subscription?: Subscription; - private isoData = setIsoData(this.context); + private isoData = setIsoData(this.context); private commentScrollDebounced: () => void; state: PostState = { postId: getIdFromProps(this.props), @@ -115,11 +121,13 @@ export class Post extends Component { 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) { + if (this.isoData.path === this.context.router.route.match.url) { + const { commentsResponse, postResponse } = this.isoData.routeData; + this.state = { ...this.state, - postRes: this.isoData.routeData[0] as GetPostResponse, - commentsRes: this.isoData.routeData[1] as GetCommentsResponse, + postRes: postResponse, + commentsRes: commentsResponse, }; if (this.state.commentsRes) { @@ -197,19 +205,18 @@ export class Post extends Component { } } - static fetchInitialData(req: InitialFetchRequest): Promise[] { - let pathSplit = req.path.split("/"); - let promises: Promise[] = []; + static fetchInitialData(req: InitialFetchRequest): WithPromiseKeys { + const pathSplit = req.path.split("/"); - let pathType = pathSplit.at(1); - let id = pathSplit.at(2) ? Number(pathSplit.at(2)) : undefined; - let auth = req.auth; + const pathType = pathSplit.at(1); + const id = pathSplit.at(2) ? Number(pathSplit.at(2)) : undefined; + const auth = req.auth; - let postForm: GetPost = { + const postForm: GetPost = { auth, }; - let commentsForm: GetComments = { + const commentsForm: GetComments = { max_depth: commentTreeMaxDepth, sort: "Hot", type_: "All", @@ -218,7 +225,7 @@ export class Post extends Component { }; // Set the correct id based on the path type - if (pathType == "post") { + if (pathType === "post") { postForm.id = id; commentsForm.post_id = id; } else { @@ -226,10 +233,10 @@ export class Post extends Component { commentsForm.parent_id = id; } - promises.push(req.client.getPost(postForm)); - promises.push(req.client.getComments(commentsForm)); - - return promises; + return { + postResponse: req.client.getPost(postForm), + commentsResponse: req.client.getComments(commentsForm), + }; } componentWillUnmount() { diff --git a/src/shared/components/private_message/create-private-message.tsx b/src/shared/components/private_message/create-private-message.tsx index 327386cb..c897c44e 100644 --- a/src/shared/components/private_message/create-private-message.tsx +++ b/src/shared/components/private_message/create-private-message.tsx @@ -12,6 +12,7 @@ import { i18n } from "../../i18next"; import { InitialFetchRequest } from "../../interfaces"; import { WebSocketService } from "../../services"; import { + WithPromiseKeys, getRecipientIdFromProps, isBrowser, myAuth, @@ -24,6 +25,10 @@ import { HtmlTags } from "../common/html-tags"; import { Spinner } from "../common/icon"; import { PrivateMessageForm } from "./private-message-form"; +interface CreatePrivateMessageData { + recipientDetailsResponse: GetPersonDetailsResponse; +} + interface CreatePrivateMessageState { siteRes: GetSiteResponse; recipientDetailsRes?: GetPersonDetailsResponse; @@ -35,7 +40,7 @@ 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, @@ -52,11 +57,10 @@ export class CreatePrivateMessage extends Component< 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 (this.isoData.path === this.context.router.route.match.url) { this.state = { ...this.state, - recipientDetailsRes: this.isoData - .routeData[0] as GetPersonDetailsResponse, + recipientDetailsRes: this.isoData.routeData.recipientDetailsResponse, loading: false, }; } else { @@ -74,15 +78,21 @@ export class CreatePrivateMessage extends Component< WebSocketService.Instance.send(wsClient.getPersonDetails(form)); } - static fetchInitialData(req: InitialFetchRequest): Promise[] { - let person_id = Number(req.path.split("/").pop()); - let form: GetPersonDetails = { + static fetchInitialData( + req: InitialFetchRequest + ): WithPromiseKeys { + 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)]; + + return { + recipientDetailsResponse: req.client.getPersonDetails(form), + }; } get documentTitle(): string { diff --git a/src/shared/components/search.tsx b/src/shared/components/search.tsx index 9f762785..c62c7a98 100644 --- a/src/shared/components/search.tsx +++ b/src/shared/components/search.tsx @@ -32,6 +32,7 @@ import { WebSocketService } from "../services"; import { Choice, QueryParams, + WithPromiseKeys, capitalizeFirstLetter, commentsToFlatNodes, communityToChoice, @@ -80,6 +81,14 @@ interface SearchProps { page: number; } +interface SearchData { + communityResponse?: GetCommunityResponse; + listCommunitiesResponse?: ListCommunitiesResponse; + creatorDetailsResponse?: GetPersonDetailsResponse; + searchResponse?: SearchResponse; + resolveObjectResponse?: ResolveObjectResponse; +} + type FilterType = "creator" | "community"; interface SearchState { @@ -237,7 +246,7 @@ function getListing( } export class Search extends Component { - private isoData = setIsoData(this.context); + private isoData = setIsoData(this.context); private subscription?: Subscription; state: SearchState = { searchLoading: false, @@ -271,45 +280,44 @@ export class Search extends Component { // Only fetch the data if coming from another route if (this.isoData.path === this.context.router.route.match.url) { - const communityRes = this.isoData.routeData[0] as - | GetCommunityResponse - | undefined; - const communitiesRes = this.isoData.routeData[1] as - | ListCommunitiesResponse - | undefined; + const { + communityResponse, + creatorDetailsResponse, + listCommunitiesResponse, + resolveObjectResponse, + searchResponse, + } = this.isoData.routeData; + // This can be single or multiple communities given - if (communitiesRes) { + if (listCommunitiesResponse) { this.state = { ...this.state, - communities: communitiesRes.communities, + communities: listCommunitiesResponse.communities, }; } - if (communityRes) { + if (communityResponse) { this.state = { ...this.state, - communities: [communityRes.community_view], + communities: [communityResponse.community_view], communitySearchOptions: [ - communityToChoice(communityRes.community_view), + communityToChoice(communityResponse.community_view), ], }; } - const creatorRes = this.isoData.routeData[2] as GetPersonDetailsResponse; - this.state = { ...this.state, - creatorDetails: creatorRes, - creatorSearchOptions: creatorRes - ? [personToChoice(creatorRes.person_view)] + creatorDetails: creatorDetailsResponse, + creatorSearchOptions: creatorDetailsResponse + ? [personToChoice(creatorDetailsResponse.person_view)] : [], }; if (q !== "") { this.state = { ...this.state, - searchResponse: this.isoData.routeData[3] as SearchResponse, - resolveObjectResponse: this.isoData - .routeData[4] as ResolveObjectResponse, + searchResponse, + resolveObjectResponse, searchLoading: false, }; } else { @@ -342,17 +350,21 @@ export class Search extends Component { client, auth, query: { communityId, creatorId, q, type, sort, listingType, page }, - }: InitialFetchRequest>): Promise[] { - const promises: Promise[] = []; - + }: InitialFetchRequest< + QueryParams + >): WithPromiseKeys { const community_id = getIdFromString(communityId); + let communityResponse: Promise | undefined = + undefined; + let listCommunitiesResponse: Promise | undefined = + undefined; if (community_id) { const getCommunityForm: GetCommunity = { id: community_id, auth, }; - promises.push(client.getCommunity(getCommunityForm)); - promises.push(Promise.resolve()); + + communityResponse = client.getCommunity(getCommunityForm); } else { const listCommunitiesForm: ListCommunities = { type_: defaultListingType, @@ -360,23 +372,29 @@ export class Search extends Component { limit: fetchLimit, auth, }; - promises.push(Promise.resolve()); - promises.push(client.listCommunities(listCommunitiesForm)); + + listCommunitiesResponse = client.listCommunities(listCommunitiesForm); } const creator_id = getIdFromString(creatorId); + let creatorDetailsResponse: Promise | undefined = + undefined; if (creator_id) { const getCreatorForm: GetPersonDetails = { person_id: creator_id, auth, }; - promises.push(client.getPersonDetails(getCreatorForm)); - } else { - promises.push(Promise.resolve()); + + creatorDetailsResponse = client.getPersonDetails(getCreatorForm); } const query = getSearchQueryFromQuery(q); + let searchResponse: Promise | undefined = undefined; + let resolveObjectResponse: + | Promise + | undefined = undefined; + if (query) { const form: SearchForm = { q: query, @@ -391,21 +409,26 @@ export class Search extends Component { }; if (query !== "") { - promises.push(client.search(form)); + searchResponse = client.search(form); if (auth) { const resolveObjectForm: ResolveObject = { q: query, auth, }; - promises.push(client.resolveObject(resolveObjectForm)); + resolveObjectResponse = client + .resolveObject(resolveObjectForm) + .catch(() => undefined); } - } else { - promises.push(Promise.resolve()); - promises.push(Promise.resolve()); } } - return promises; + return { + communityResponse, + creatorDetailsResponse, + listCommunitiesResponse, + resolveObjectResponse, + searchResponse, + }; } get documentTitle(): string { diff --git a/src/shared/interfaces.ts b/src/shared/interfaces.ts index a6b2ae47..dc4490cb 100644 --- a/src/shared/interfaces.ts +++ b/src/shared/interfaces.ts @@ -5,15 +5,15 @@ import { ErrorPageData } from "./utils"; /** * This contains serialized data, it needs to be deserialized before use. */ -export interface IsoData { +export interface IsoData { path: string; - routeData: any[]; + routeData: T; site_res: GetSiteResponse; errorPageData?: ErrorPageData; } -export type IsoDataOptionalSite = Partial & - Pick>; +export type IsoDataOptionalSite = Partial> & + Pick, Exclude, "site_res">>; export interface ILemmyConfig { wsHost?: string; diff --git a/src/shared/routes.ts b/src/shared/routes.ts index b5c28189..81771d03 100644 --- a/src/shared/routes.ts +++ b/src/shared/routes.ts @@ -22,13 +22,14 @@ import { Post } from "./components/post/post"; import { CreatePrivateMessage } from "./components/private_message/create-private-message"; import { Search } from "./components/search"; import { InitialFetchRequest } from "./interfaces"; +import { WithPromiseKeys } from "./utils"; -interface IRoutePropsWithFetch extends IRouteProps { +interface IRoutePropsWithFetch extends IRouteProps { // TODO Make sure this one is good. - fetchInitialData?(req: InitialFetchRequest): Promise[]; + fetchInitialData?(req: InitialFetchRequest): WithPromiseKeys; } -export const routes: IRoutePropsWithFetch[] = [ +export const routes: IRoutePropsWithFetch>[] = [ { path: `/`, component: Home, diff --git a/src/shared/utils.ts b/src/shared/utils.ts index 5648df00..fe83977d 100644 --- a/src/shared/utils.ts +++ b/src/shared/utils.ts @@ -1262,7 +1262,7 @@ export function isBrowser() { return typeof window !== "undefined"; } -export function setIsoData(context: any): IsoData { +export function setIsoData(context: any): IsoData { // If its the browser, you need to deserialize the data from the window if (isBrowser()) { return window.isoData; @@ -1557,6 +1557,10 @@ export type QueryParams> = { [key in keyof T]?: string; }; +export type WithPromiseKeys = { + [K in keyof T]: Promise; +}; + export function getQueryParams>(processors: { [K in keyof T]: (param: string) => T[K]; }): T {