mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-02-03 00:33:49 +03:00
feat: infinite scroll friend list
This commit is contained in:
parent
b1e263814c
commit
515a08a3a6
@ -4,7 +4,6 @@ import {
|
|||||||
XCircleIcon,
|
XCircleIcon,
|
||||||
} from "@primer/octicons-react";
|
} from "@primer/octicons-react";
|
||||||
import * as styles from "./user-friend-modal.css";
|
import * as styles from "./user-friend-modal.css";
|
||||||
import cn from "classnames";
|
|
||||||
import { SPACING_UNIT } from "@renderer/theme.css";
|
import { SPACING_UNIT } from "@renderer/theme.css";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
@ -108,7 +107,7 @@ export const UserFriendItem = (props: UserFriendItemProps) => {
|
|||||||
|
|
||||||
if (type === "BLOCKED") {
|
if (type === "BLOCKED") {
|
||||||
return (
|
return (
|
||||||
<div className={cn(styles.friendListContainer, styles.profileContentBox)}>
|
<div className={styles.friendListContainer}>
|
||||||
<div className={styles.friendListButton} style={{ cursor: "inherit" }}>
|
<div className={styles.friendListButton} style={{ cursor: "inherit" }}>
|
||||||
<div className={styles.friendAvatarContainer}>
|
<div className={styles.friendAvatarContainer}>
|
||||||
{profileImageUrl ? (
|
{profileImageUrl ? (
|
||||||
@ -149,7 +148,7 @@ export const UserFriendItem = (props: UserFriendItemProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn(styles.friendListContainer, styles.profileContentBox)}>
|
<div className={styles.friendListContainer}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={styles.friendListButton}
|
className={styles.friendListButton}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { SPACING_UNIT } from "@renderer/theme.css";
|
import { SPACING_UNIT, vars } from "@renderer/theme.css";
|
||||||
import { UserFriend } from "@types";
|
import { UserFriend } from "@types";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { UserFriendItem } from "./user-friend-item";
|
import { UserFriendItem } from "./user-friend-item";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useToast, useUserDetails } from "@renderer/hooks";
|
import { useToast, useUserDetails } from "@renderer/hooks";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import Skeleton, { SkeletonTheme } from "react-loading-skeleton";
|
||||||
|
|
||||||
export interface UserFriendModalListProps {
|
export interface UserFriendModalListProps {
|
||||||
userId: string;
|
userId: string;
|
||||||
@ -22,14 +23,18 @@ export const UserFriendModalList = ({
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const [page, setPage] = useState(0);
|
const [page, setPage] = useState(0);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [maxPage, setMaxPage] = useState(0);
|
const [maxPage, setMaxPage] = useState(0);
|
||||||
const [friends, setFriends] = useState<UserFriend[]>([]);
|
const [friends, setFriends] = useState<UserFriend[]>([]);
|
||||||
|
const listContainer = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const { userDetails, undoFriendship } = useUserDetails();
|
const { userDetails, undoFriendship } = useUserDetails();
|
||||||
const isMe = userDetails?.id == userId;
|
const isMe = userDetails?.id == userId;
|
||||||
|
|
||||||
const loadNextPage = () => {
|
const loadNextPage = () => {
|
||||||
if (page > maxPage) return;
|
if (page > maxPage) return;
|
||||||
|
setIsLoading(true);
|
||||||
|
console.log("loading next page");
|
||||||
window.electron
|
window.electron
|
||||||
.getUserFriends(userId, pageSize, page * pageSize)
|
.getUserFriends(userId, pageSize, page * pageSize)
|
||||||
.then((newPage) => {
|
.then((newPage) => {
|
||||||
@ -40,9 +45,29 @@ export const UserFriendModalList = ({
|
|||||||
setFriends([...friends, ...newPage.friends]);
|
setFriends([...friends, ...newPage.friends]);
|
||||||
setPage(page + 1);
|
setPage(page + 1);
|
||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch(() => {})
|
||||||
|
.finally(() => setIsLoading(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleScroll = () => {
|
||||||
|
const scrollTop = listContainer.current?.scrollTop || 0;
|
||||||
|
const scrollHeight = listContainer.current?.scrollHeight || 0;
|
||||||
|
const clientHeight = listContainer.current?.clientHeight || 0;
|
||||||
|
const maxScrollTop = scrollHeight - clientHeight;
|
||||||
|
|
||||||
|
if (scrollTop < maxScrollTop * 0.9 || isLoading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadNextPage();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
listContainer.current?.addEventListener("scroll", handleScroll);
|
||||||
|
return () =>
|
||||||
|
listContainer.current?.removeEventListener("scroll", handleScroll);
|
||||||
|
}, [isLoading]);
|
||||||
|
|
||||||
const reloadList = () => {
|
const reloadList = () => {
|
||||||
setPage(0);
|
setPage(0);
|
||||||
setMaxPage(0);
|
setMaxPage(0);
|
||||||
@ -70,27 +95,42 @@ export const UserFriendModalList = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<SkeletonTheme baseColor={vars.color.background} highlightColor="#444">
|
||||||
style={{
|
<div
|
||||||
display: "flex",
|
ref={listContainer}
|
||||||
flexDirection: "column",
|
style={{
|
||||||
gap: `${SPACING_UNIT * 2}px`,
|
display: "flex",
|
||||||
}}
|
flexDirection: "column",
|
||||||
>
|
gap: `${SPACING_UNIT * 2}px`,
|
||||||
{friends.length === 0 && <p>{t("no_friends_added")}</p>}
|
maxHeight: "400px",
|
||||||
{friends.map((friend) => {
|
overflowY: "scroll",
|
||||||
return (
|
}}
|
||||||
<UserFriendItem
|
>
|
||||||
userId={friend.id}
|
{friends.length === 0 && <p>{t("no_friends_added")}</p>}
|
||||||
displayName={friend.displayName}
|
{friends.map((friend) => {
|
||||||
profileImageUrl={friend.profileImageUrl}
|
return (
|
||||||
onClickItem={handleClickFriend}
|
<UserFriendItem
|
||||||
onClickUndoFriendship={handleUndoFriendship}
|
userId={friend.id}
|
||||||
type={isMe ? "ACCEPTED" : null}
|
displayName={friend.displayName}
|
||||||
key={friend.id}
|
profileImageUrl={friend.profileImageUrl}
|
||||||
|
onClickItem={handleClickFriend}
|
||||||
|
onClickUndoFriendship={handleUndoFriendship}
|
||||||
|
type={isMe ? "ACCEPTED" : null}
|
||||||
|
key={"modal" + friend.id}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{isLoading && (
|
||||||
|
<Skeleton
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: "54px",
|
||||||
|
overflow: "hidden",
|
||||||
|
borderRadius: "4px",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
)}
|
||||||
})}
|
</div>
|
||||||
</div>
|
</SkeletonTheme>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,17 +1,6 @@
|
|||||||
import { SPACING_UNIT, vars } from "../../../theme.css";
|
import { SPACING_UNIT, vars } from "../../../theme.css";
|
||||||
import { style } from "@vanilla-extract/css";
|
import { style } from "@vanilla-extract/css";
|
||||||
|
|
||||||
export const profileContentBox = style({
|
|
||||||
display: "flex",
|
|
||||||
gap: `${SPACING_UNIT * 3}px`,
|
|
||||||
alignItems: "center",
|
|
||||||
borderRadius: "4px",
|
|
||||||
border: `solid 1px ${vars.color.border}`,
|
|
||||||
width: "100%",
|
|
||||||
boxShadow: "0px 0px 15px 0px rgba(0, 0, 0, 0.7)",
|
|
||||||
transition: "all ease 0.3s",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const friendAvatarContainer = style({
|
export const friendAvatarContainer = style({
|
||||||
width: "35px",
|
width: "35px",
|
||||||
minWidth: "35px",
|
minWidth: "35px",
|
||||||
@ -42,8 +31,14 @@ export const profileAvatar = style({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const friendListContainer = style({
|
export const friendListContainer = style({
|
||||||
|
display: "flex",
|
||||||
|
gap: `${SPACING_UNIT * 3}px`,
|
||||||
|
alignItems: "center",
|
||||||
|
borderRadius: "4px",
|
||||||
|
border: `solid 1px ${vars.color.border}`,
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "54px",
|
height: "54px",
|
||||||
|
minHeight: "54px",
|
||||||
transition: "all ease 0.2s",
|
transition: "all ease 0.2s",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
":hover": {
|
":hover": {
|
||||||
|
Loading…
Reference in New Issue
Block a user