diff options
-rw-r--r-- | BackEnd/Timeline/Services/Timeline/TimelinePostService.cs | 2 | ||||
-rw-r--r-- | FrontEnd/src/utilities/hooks.ts | 10 | ||||
-rw-r--r-- | FrontEnd/src/utilities/hooks/useReverseScrollPositionRemember.ts | 2 | ||||
-rw-r--r-- | FrontEnd/src/utilities/hooks/useScrollToBottom.ts (renamed from FrontEnd/src/utilities/hooks/useScrollToTop.ts) | 12 | ||||
-rw-r--r-- | FrontEnd/src/views/timeline/Timeline.css (renamed from FrontEnd/src/views/timeline/index.css) | 0 | ||||
-rw-r--r-- | FrontEnd/src/views/timeline/Timeline.tsx | 128 | ||||
-rw-r--r-- | FrontEnd/src/views/timeline/TimelinePagedPostListView.tsx | 34 | ||||
-rw-r--r-- | FrontEnd/src/views/timeline/TimelinePostEdit.css | 8 | ||||
-rw-r--r-- | FrontEnd/src/views/timeline/TimelinePostEditCard.tsx | 2 | ||||
-rw-r--r-- | FrontEnd/src/views/timeline/index.tsx | 4 |
10 files changed, 83 insertions, 119 deletions
diff --git a/BackEnd/Timeline/Services/Timeline/TimelinePostService.cs b/BackEnd/Timeline/Services/Timeline/TimelinePostService.cs index 8fee0467..8a1501c1 100644 --- a/BackEnd/Timeline/Services/Timeline/TimelinePostService.cs +++ b/BackEnd/Timeline/Services/Timeline/TimelinePostService.cs @@ -400,7 +400,7 @@ namespace Timeline.Services.Timeline query = query.Where(p => p.LastUpdated >= modifiedSince || (p.Author != null && p.Author.UsernameChangeTime >= modifiedSince));
}
- query = query.OrderBy(p => p.Time).Skip(pageSize * (page - 1)).Take(pageSize); + query = query.OrderByDescending(p => p.Time).Skip(pageSize * (page - 1)).Take(pageSize); var items = await query.ToListAsync(); diff --git a/FrontEnd/src/utilities/hooks.ts b/FrontEnd/src/utilities/hooks.ts index c499b36b..a59f7167 100644 --- a/FrontEnd/src/utilities/hooks.ts +++ b/FrontEnd/src/utilities/hooks.ts @@ -1,11 +1,5 @@ import useClickOutside from "./hooks/useClickOutside"; -import useReverseScrollPositionRemember from "./hooks/useReverseScrollPositionRemember"; -import useScrollToTop from "./hooks/useScrollToTop"; +import useScrollToBottom from "./hooks/useScrollToBottom"; import { useIsSmallScreen } from "./hooks/mediaQuery"; -export { - useClickOutside, - useReverseScrollPositionRemember, - useScrollToTop, - useIsSmallScreen, -}; +export { useClickOutside, useScrollToBottom, useIsSmallScreen }; diff --git a/FrontEnd/src/utilities/hooks/useReverseScrollPositionRemember.ts b/FrontEnd/src/utilities/hooks/useReverseScrollPositionRemember.ts index 6fdd4b43..c0b6ce2c 100644 --- a/FrontEnd/src/utilities/hooks/useReverseScrollPositionRemember.ts +++ b/FrontEnd/src/utilities/hooks/useReverseScrollPositionRemember.ts @@ -1,3 +1,5 @@ +// Not used now!!! But preserved for future use. + import React from "react"; let on = false; diff --git a/FrontEnd/src/utilities/hooks/useScrollToTop.ts b/FrontEnd/src/utilities/hooks/useScrollToBottom.ts index 95c8b7b9..f6780d9f 100644 --- a/FrontEnd/src/utilities/hooks/useScrollToTop.ts +++ b/FrontEnd/src/utilities/hooks/useScrollToBottom.ts @@ -2,7 +2,7 @@ import React from "react"; import { fromEvent } from "rxjs"; import { filter, throttleTime } from "rxjs/operators"; -function useScrollToTop( +function useScrollToBottom( handler: () => void, enable = true, option = { @@ -23,9 +23,11 @@ function useScrollToTop( React.useEffect(() => { const subscription = fromEvent(window, "scroll") .pipe( - filter(() => { - return window.scrollY <= option.maxOffset; - }), + filter( + () => + window.scrollY >= + document.body.scrollHeight - window.innerHeight - option.maxOffset + ), throttleTime(option.throttle) ) .subscribe(() => { @@ -40,4 +42,4 @@ function useScrollToTop( }, [enable, option.maxOffset, option.throttle]); } -export default useScrollToTop; +export default useScrollToBottom; diff --git a/FrontEnd/src/views/timeline/index.css b/FrontEnd/src/views/timeline/Timeline.css index fa3542dd..fa3542dd 100644 --- a/FrontEnd/src/views/timeline/index.css +++ b/FrontEnd/src/views/timeline/Timeline.css diff --git a/FrontEnd/src/views/timeline/Timeline.tsx b/FrontEnd/src/views/timeline/Timeline.tsx index 6399b6bc..84624313 100644 --- a/FrontEnd/src/views/timeline/Timeline.tsx +++ b/FrontEnd/src/views/timeline/Timeline.tsx @@ -1,5 +1,6 @@ import React from "react"; import classnames from "classnames"; +import { useScrollToBottom } from "@/utilities/hooks"; import { HubConnectionState } from "@microsoft/signalr"; import { @@ -16,14 +17,14 @@ import { import { useUser } from "@/services/user"; import { getTimelinePostUpdate$ } from "@/services/timeline"; -import TimelinePagedPostListView from "./TimelinePagedPostListView"; +import TimelinePostListView from "./TimelinePostListView"; import TimelineEmptyItem from "./TimelineEmptyItem"; import TimelineLoading from "./TimelineLoading"; import TimelinePostEdit from "./TimelinePostEdit"; import TimelinePostEditNoLogin from "./TimelinePostEditNoLogin"; import TimelineCard from "./TimelineCard"; -import "./index.css"; +import "./Timeline.css"; export interface TimelineProps { className?: string; @@ -46,6 +47,9 @@ const Timeline: React.FC<TimelineProps> = (props) => { "offline" | "forbid" | "notfound" | "error" | null >(null); + const [currentPage, setCurrentPage] = React.useState(1); + const [totalPage, setTotalPage] = React.useState(0); + const [timelineReloadKey, setTimelineReloadKey] = React.useState(0); const [postsReloadKey, setPostsReloadKey] = React.useState(0); @@ -60,71 +64,50 @@ const Timeline: React.FC<TimelineProps> = (props) => { }, [timelineOwner, timelineName]); React.useEffect(() => { - if (timelineName != null) { - let subscribe = true; - - getHttpTimelineClient() - .getTimeline(timelineOwner, timelineName) - .then( - (t) => { - if (subscribe) { - setTimeline(t); - } - }, - (error) => { - if (subscribe) { - if (error instanceof HttpNetworkError) { - setError("offline"); - } else if (error instanceof HttpForbiddenError) { - setError("forbid"); - } else if (error instanceof HttpNotFoundError) { - setError("notfound"); - } else { - console.error(error); - setError("error"); - } - } + getHttpTimelineClient() + .getTimeline(timelineOwner, timelineName) + .then( + (t) => { + setTimeline(t); + }, + (error) => { + if (error instanceof HttpNetworkError) { + setError("offline"); + } else if (error instanceof HttpForbiddenError) { + setError("forbid"); + } else if (error instanceof HttpNotFoundError) { + setError("notfound"); + } else { + console.error(error); + setError("error"); } - ); - - return () => { - subscribe = false; - }; - } + } + ); }, [timelineOwner, timelineName, timelineReloadKey]); React.useEffect(() => { - let subscribe = true; - void getHttpTimelineClient() - .listPost(timelineOwner, timelineName) + getHttpTimelineClient() + .listPost(timelineOwner, timelineName, 1) .then( - (ps) => { - if (subscribe) { - setPosts( - ps.items.filter( - (p): p is HttpTimelinePostInfo => p.deleted === false - ) - ); - } + (page) => { + setPosts( + page.items.filter((p): p is HttpTimelinePostInfo => !p.deleted) + ); + setTotalPage(page.totalPageCount); }, (error) => { - if (subscribe) { - if (error instanceof HttpNetworkError) { - setError("offline"); - } else if (error instanceof HttpForbiddenError) { - setError("forbid"); - } else if (error instanceof HttpNotFoundError) { - setError("notfound"); - } else { - console.error(error); - setError("error"); - } + if (error instanceof HttpNetworkError) { + setError("offline"); + } else if (error instanceof HttpForbiddenError) { + setError("forbid"); + } else if (error instanceof HttpNotFoundError) { + setError("notfound"); + } else { + console.error(error); + setError("error"); } } ); - return () => { - subscribe = false; - }; }, [timelineOwner, timelineName, postsReloadKey]); React.useEffect(() => { @@ -143,6 +126,33 @@ const Timeline: React.FC<TimelineProps> = (props) => { }; }, [timelineOwner, timelineName]); + useScrollToBottom(() => { + console.log(`Load page ${currentPage + 1}.`); + setCurrentPage(currentPage + 1); + void getHttpTimelineClient() + .listPost(timelineOwner, timelineName, currentPage + 1) + .then( + (page) => { + const ps = page.items.filter( + (p): p is HttpTimelinePostInfo => !p.deleted + ); + setPosts((old) => [...(old ?? []), ...ps]); + }, + (error) => { + if (error instanceof HttpNetworkError) { + setError("offline"); + } else if (error instanceof HttpForbiddenError) { + setError("forbid"); + } else if (error instanceof HttpNotFoundError) { + setError("notfound"); + } else { + console.error(error); + setError("error"); + } + } + ); + }, currentPage < totalPage); + if (error === "offline") { return ( <div className={className} style={style}> @@ -181,8 +191,7 @@ const Timeline: React.FC<TimelineProps> = (props) => { )} {posts && ( <div style={style} className={classnames("timeline", className)}> - <TimelineEmptyItem height={40} /> - <TimelinePagedPostListView posts={posts} onReload={updatePosts} /> + <TimelineEmptyItem height={50} /> {timeline?.postable ? ( <TimelinePostEdit timeline={timeline} onPosted={updatePosts} /> ) : user == null ? ( @@ -190,6 +199,7 @@ const Timeline: React.FC<TimelineProps> = (props) => { ) : ( <TimelineEmptyItem startSegmentLength={20} center="none" current /> )} + <TimelinePostListView posts={posts} onReload={updatePosts} /> </div> )} </> diff --git a/FrontEnd/src/views/timeline/TimelinePagedPostListView.tsx b/FrontEnd/src/views/timeline/TimelinePagedPostListView.tsx deleted file mode 100644 index 6a0ad0f5..00000000 --- a/FrontEnd/src/views/timeline/TimelinePagedPostListView.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from "react"; - -import { HttpTimelinePostInfo } from "@/http/timeline"; - -import { useScrollToTop } from "@/utilities/hooks"; - -import TimelinePostListView from "./TimelinePostListView"; - -export interface TimelinePagedPostListViewProps { - posts: HttpTimelinePostInfo[]; - onReload: () => void; -} - -const TimelinePagedPostListView: React.FC<TimelinePagedPostListViewProps> = ( - props -) => { - const { posts, onReload } = props; - - const [lastViewCount, setLastViewCount] = React.useState<number>(10); - - const viewingPosts = React.useMemo(() => { - return lastViewCount >= posts.length - ? posts.slice() - : posts.slice(-lastViewCount); - }, [posts, lastViewCount]); - - useScrollToTop(() => { - setLastViewCount(lastViewCount + 10); - }, lastViewCount < posts.length); - - return <TimelinePostListView posts={viewingPosts} onReload={onReload} />; -}; - -export default TimelinePagedPostListView; diff --git a/FrontEnd/src/views/timeline/TimelinePostEdit.css b/FrontEnd/src/views/timeline/TimelinePostEdit.css index 4ce98383..fb34e673 100644 --- a/FrontEnd/src/views/timeline/TimelinePostEdit.css +++ b/FrontEnd/src/views/timeline/TimelinePostEdit.css @@ -2,15 +2,9 @@ padding-bottom: 0;
}
-.timeline-post-edit .timeline-item-card {
- border-bottom-left-radius: 0;
- border-bottom-right-radius: 0;
- border-bottom: none;
-}
-
.timeline-post-edit {
position: sticky !important;
- bottom: 0;
+ top: 0;
z-index: 1;
}
diff --git a/FrontEnd/src/views/timeline/TimelinePostEditCard.tsx b/FrontEnd/src/views/timeline/TimelinePostEditCard.tsx index a69d413a..de0e7e43 100644 --- a/FrontEnd/src/views/timeline/TimelinePostEditCard.tsx +++ b/FrontEnd/src/views/timeline/TimelinePostEditCard.tsx @@ -22,7 +22,7 @@ const TimelinePostEdit: React.FC<TimelinePostEditCardProps> = ({ className={classnames("timeline-item timeline-post-edit", className)} style={style} > - <TimelineLine center="node" current /> + <TimelineLine center="node" /> <Card className="timeline-item-card">{children}</Card> </div> ); diff --git a/FrontEnd/src/views/timeline/index.tsx b/FrontEnd/src/views/timeline/index.tsx index 131c38c7..cb9fb46f 100644 --- a/FrontEnd/src/views/timeline/index.tsx +++ b/FrontEnd/src/views/timeline/index.tsx @@ -3,8 +3,6 @@ import { useParams } from "react-router-dom"; import { UiLogicError } from "@/common"; -import { useReverseScrollPositionRemember } from "@/utilities/hooks"; - import Timeline from "./Timeline"; const TimelinePage: React.FC = () => { @@ -15,8 +13,6 @@ const TimelinePage: React.FC = () => { const timeline = timelineNameParam || "self"; - useReverseScrollPositionRemember(); - return ( <div className="container"> <Timeline timelineOwner={owner} timelineName={timeline} /> |