diff --git a/lemmy-translations b/lemmy-translations index b4c63029e..62c841802 160000 --- a/lemmy-translations +++ b/lemmy-translations @@ -1 +1 @@ -Subproject commit b4c63029e598c022a04fc21acb45855645bd6794 +Subproject commit 62c8418021bc39543c87b4ae3dcf2419d13f61e0 diff --git a/src/shared/components/common/loading-skeleton.tsx b/src/shared/components/common/loading-skeleton.tsx new file mode 100644 index 000000000..ab146b8a9 --- /dev/null +++ b/src/shared/components/common/loading-skeleton.tsx @@ -0,0 +1,118 @@ +import { Component } from "inferno"; + +interface LoadingSkeletonProps { + itemCount?: number; +} + +interface LoadingSkeletonLineProps { + size: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; +} + +class LoadingSkeletonLine extends Component<LoadingSkeletonLineProps, any> { + render() { + const className = "placeholder placeholder-lg col-" + this.props.size; + return ( + <p className="placeholder-glow m-0"> + <span className={`${className} h-100`} /> + </p> + ); + } +} + +export class PostsLoadingSkeleton extends Component<LoadingSkeletonProps, any> { + render() { + return Array.from({ length: this.props.itemCount ?? 10 }, (_, index) => ( + <PostsLoadingSkeletonItem key={index} /> + )); + } +} + +class PostThumbnailLoadingSkeleton extends Component<any, any> { + render() { + return ( + <div className="thumbnail rounded d-flex justify-content-center placeholder-glow"> + <span className="placeholder placeholder-lg h-100 w-100 rounded" /> + </div> + ); + } +} + +class PostsLoadingSkeletonItem extends Component<any, any> { + render() { + return ( + <div className="my-3"> + <div className="col flex-grow-1"> + <div className="row"> + <div className="col flex-grow-0 order-last order-sm-first"> + <PostThumbnailLoadingSkeleton /> + </div> + <div className="col flex-grow-1"> + <LoadingSkeletonLine size={12} /> + <LoadingSkeletonLine size={8} /> + <LoadingSkeletonLine size={4} /> + </div> + </div> + </div> + </div> + ); + } +} + +export class TrendingCommunitiesLoadingSkeleton extends Component< + LoadingSkeletonProps, + any +> { + render() { + return ( + <div className="mb-2"> + {Array.from({ length: this.props.itemCount ?? 10 }, (_, index) => ( + <TrendingCommunitiesLoadingSkeletonItem key={index} /> + ))} + </div> + ); + } +} + +class TrendingCommunitiesLoadingSkeletonItem extends Component<any, any> { + render() { + return ( + <div className="col flex-grow-1 mt-2 ps-2 pe-4"> + <div className="row"> + <div className="col flex-grow-0 pe-0"> + <div className="d-flex placeholder-glow img-icon"> + <span className="placeholder placeholder-lg w-100 h-100 rounded-circle" /> + </div> + </div> + <div className="col flex-grow-1 pe-0"> + <LoadingSkeletonLine size={12} /> + </div> + </div> + </div> + ); + } +} + +export class CommentsLoadingSkeleton extends Component<any, any> { + render() { + return Array.from({ length: this.props.itemCount ?? 10 }, (_, index) => ( + <CommentsLoadingSkeletonItem key={index} /> + )); + } +} + +class CommentsLoadingSkeletonItem extends Component<any, any> { + render() { + return ( + <div className="col flex-grow-1 my-2 p-2"> + <div className="row"> + <div className="col flex-grow-1"> + <LoadingSkeletonLine size={6} /> + <LoadingSkeletonLine size={12} /> + <LoadingSkeletonLine size={7} /> + <LoadingSkeletonLine size={4} /> + </div> + </div> + </div> + ); + } +} diff --git a/src/shared/components/community/community.tsx b/src/shared/components/community/community.tsx index 195de1fec..8f9c87648 100644 --- a/src/shared/components/community/community.tsx +++ b/src/shared/components/community/community.tsx @@ -100,6 +100,10 @@ import { PostListings } from "../post/post-listings"; import { CommunityLink } from "./community-link"; import { PaginatorCursor } from "../common/paginator-cursor"; import { getHttpBaseInternal } from "../../utils/env"; +import { + CommentsLoadingSkeleton, + PostsLoadingSkeleton, +} from "../common/loading-skeleton"; import { Sidebar } from "./sidebar"; import { IRoutePropsWithFetch } from "../../routes"; @@ -435,11 +439,7 @@ export class Community extends Component<CommunityRouteProps, State> { if (dataType === DataType.Post) { switch (this.state.postsRes.state) { case "loading": - return ( - <h5> - <Spinner large /> - </h5> - ); + return <PostsLoadingSkeleton />; case "success": return ( <PostListings @@ -472,11 +472,7 @@ export class Community extends Component<CommunityRouteProps, State> { } else { switch (this.state.commentsRes.state) { case "loading": - return ( - <h5> - <Spinner large /> - </h5> - ); + return <CommentsLoadingSkeleton />; case "success": return ( <CommentNodes diff --git a/src/shared/components/home/home.tsx b/src/shared/components/home/home.tsx index 3843a2806..56309da29 100644 --- a/src/shared/components/home/home.tsx +++ b/src/shared/components/home/home.tsx @@ -92,7 +92,7 @@ import { toast } from "../../toast"; import { CommentNodes } from "../comment/comment-nodes"; import { DataTypeSelect } from "../common/data-type-select"; import { HtmlTags } from "../common/html-tags"; -import { Icon, Spinner } from "../common/icon"; +import { Icon } from "../common/icon"; import { ListingTypeSelect } from "../common/listing-type-select"; import { SortSelect } from "../common/sort-select"; import { CommunityLink } from "../community/community-link"; @@ -100,6 +100,11 @@ import { PostListings } from "../post/post-listings"; import { SiteSidebar } from "./site-sidebar"; import { PaginatorCursor } from "../common/paginator-cursor"; import { getHttpBaseInternal } from "../../utils/env"; +import { + CommentsLoadingSkeleton, + PostsLoadingSkeleton, + TrendingCommunitiesLoadingSkeleton, +} from "../common/loading-skeleton"; import { RouteComponentProps } from "inferno-router/dist/Route"; import { IRoutePropsWithFetch } from "../../routes"; @@ -531,11 +536,7 @@ export class Home extends Component<HomeRouteProps, HomeState> { trendingCommunities() { switch (this.state.trendingCommunitiesRes?.state) { case "loading": - return ( - <h5> - <Spinner large /> - </h5> - ); + return <TrendingCommunitiesLoadingSkeleton itemCount={5} />; case "success": { const trending = this.state.trendingCommunitiesRes.data.communities; return ( @@ -704,11 +705,7 @@ export class Home extends Component<HomeRouteProps, HomeState> { case "empty": return <div style="min-height: 20000px;"></div>; case "loading": - return ( - <h5> - <Spinner large /> - </h5> - ); + return <PostsLoadingSkeleton />; case "success": { const posts = this.state.postsRes.data.posts; return ( @@ -744,11 +741,7 @@ export class Home extends Component<HomeRouteProps, HomeState> { } else { switch (this.state.commentsRes.state) { case "loading": - return ( - <h5> - <Spinner large /> - </h5> - ); + return <CommentsLoadingSkeleton />; case "success": { const comments = this.state.commentsRes.data.comments; return ( diff --git a/src/shared/components/person/inbox.tsx b/src/shared/components/person/inbox.tsx index 26b8a7e09..239e33407 100644 --- a/src/shared/components/person/inbox.tsx +++ b/src/shared/components/person/inbox.tsx @@ -80,6 +80,7 @@ import { Icon, Spinner } from "../common/icon"; import { Paginator } from "../common/paginator"; import { PrivateMessage } from "../private_message/private-message"; import { getHttpBaseInternal } from "../../utils/env"; +import { CommentsLoadingSkeleton } from "../common/loading-skeleton"; import { RouteComponentProps } from "inferno-router/dist/Route"; import { IRoutePropsWithFetch } from "../../routes"; @@ -583,11 +584,7 @@ export class Inbox extends Component<InboxRouteProps, InboxState> { this.state.mentionsRes.state === "loading" || this.state.messagesRes.state === "loading" ) { - return ( - <h1 className="h4"> - <Spinner large /> - </h1> - ); + return <CommentsLoadingSkeleton />; } else { return ( <div>{this.buildCombined().map(r => this.renderReplyType(r))}</div> @@ -598,11 +595,7 @@ export class Inbox extends Component<InboxRouteProps, InboxState> { replies() { switch (this.state.repliesRes.state) { case "loading": - return ( - <h1 className="h4"> - <Spinner large /> - </h1> - ); + return <CommentsLoadingSkeleton />; case "success": { const replies = this.state.repliesRes.data.replies; return ( @@ -645,11 +638,7 @@ export class Inbox extends Component<InboxRouteProps, InboxState> { mentions() { switch (this.state.mentionsRes.state) { case "loading": - return ( - <h1 className="h4"> - <Spinner large /> - </h1> - ); + return <CommentsLoadingSkeleton />; case "success": { const mentions = this.state.mentionsRes.data.mentions; return ( @@ -695,11 +684,7 @@ export class Inbox extends Component<InboxRouteProps, InboxState> { messages() { switch (this.state.messagesRes.state) { case "loading": - return ( - <h1 className="h4"> - <Spinner large /> - </h1> - ); + return <CommentsLoadingSkeleton />; case "success": { const messages = this.state.messagesRes.data.private_messages; return (