From e6ccc0174a86a0ade240e6551228598cd81f984b Mon Sep 17 00:00:00 2001 From: crupest Date: Tue, 1 Aug 2023 00:29:35 +0800 Subject: ... --- FrontEnd/package.json | 2 +- FrontEnd/pnpm-lock.yaml | 35 +++++++- FrontEnd/src/pages/timeline/Timeline.css | 95 +++++++--------------- FrontEnd/src/pages/timeline/Timeline.tsx | 77 ++++++------------ FrontEnd/src/pages/timeline/TimelineCard.tsx | 2 +- FrontEnd/src/pages/timeline/TimelineDateLabel.css | 9 ++ FrontEnd/src/pages/timeline/TimelineDateLabel.tsx | 20 ++--- FrontEnd/src/pages/timeline/TimelineEmptyItem.tsx | 25 ------ FrontEnd/src/pages/timeline/TimelineLine.tsx | 51 ------------ FrontEnd/src/pages/timeline/TimelineLoading.tsx | 16 ---- FrontEnd/src/pages/timeline/TimelinePostCard.css | 9 ++ FrontEnd/src/pages/timeline/TimelinePostCard.tsx | 22 +++++ .../src/pages/timeline/TimelinePostContainer.css | 3 + .../src/pages/timeline/TimelinePostContainer.tsx | 20 +++++ FrontEnd/src/pages/timeline/TimelinePostEdit.tsx | 6 +- .../src/pages/timeline/TimelinePostEditCard.tsx | 31 ------- .../src/pages/timeline/TimelinePostEditNoLogin.tsx | 18 ---- FrontEnd/src/pages/timeline/TimelinePostList.css | 10 +++ FrontEnd/src/pages/timeline/TimelinePostList.tsx | 76 +++++++++++++++++ .../src/pages/timeline/TimelinePostListView.tsx | 76 ----------------- FrontEnd/src/pages/timeline/TimelinePostView.css | 23 +++--- FrontEnd/src/pages/timeline/TimelinePostView.tsx | 20 ++--- 22 files changed, 265 insertions(+), 381 deletions(-) create mode 100644 FrontEnd/src/pages/timeline/TimelineDateLabel.css delete mode 100644 FrontEnd/src/pages/timeline/TimelineEmptyItem.tsx delete mode 100644 FrontEnd/src/pages/timeline/TimelineLine.tsx delete mode 100644 FrontEnd/src/pages/timeline/TimelineLoading.tsx create mode 100644 FrontEnd/src/pages/timeline/TimelinePostCard.css create mode 100644 FrontEnd/src/pages/timeline/TimelinePostCard.tsx create mode 100644 FrontEnd/src/pages/timeline/TimelinePostContainer.css create mode 100644 FrontEnd/src/pages/timeline/TimelinePostContainer.tsx delete mode 100644 FrontEnd/src/pages/timeline/TimelinePostEditCard.tsx delete mode 100644 FrontEnd/src/pages/timeline/TimelinePostEditNoLogin.tsx create mode 100644 FrontEnd/src/pages/timeline/TimelinePostList.css create mode 100644 FrontEnd/src/pages/timeline/TimelinePostList.tsx delete mode 100644 FrontEnd/src/pages/timeline/TimelinePostListView.tsx diff --git a/FrontEnd/package.json b/FrontEnd/package.json index 56af818c..950670f4 100644 --- a/FrontEnd/package.json +++ b/FrontEnd/package.json @@ -12,8 +12,8 @@ "check:fix": "pnpm run type-check && pnpm run lint:fix" }, "dependencies": { + "@floating-ui/react-dom": "^2.0.1", "@microsoft/signalr": "^7.0.7", - "@popperjs/core": "^2.11.8", "axios": "^1.4.0", "bootstrap": "^5.3.0", "bootstrap-icons": "^1.10.5", diff --git a/FrontEnd/pnpm-lock.yaml b/FrontEnd/pnpm-lock.yaml index cad9a287..24e81c7c 100644 --- a/FrontEnd/pnpm-lock.yaml +++ b/FrontEnd/pnpm-lock.yaml @@ -5,12 +5,12 @@ settings: excludeLinksFromLockfile: false dependencies: + '@floating-ui/react-dom': + specifier: ^2.0.1 + version: 2.0.1(react-dom@18.2.0)(react@18.2.0) '@microsoft/signalr': specifier: ^7.0.7 version: 7.0.7 - '@popperjs/core': - specifier: ^2.11.8 - version: 2.11.8 axios: specifier: ^1.4.0 version: 1.4.0 @@ -239,6 +239,34 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true + /@floating-ui/core@1.4.1: + resolution: {integrity: sha512-jk3WqquEJRlcyu7997NtR5PibI+y5bi+LS3hPmguVClypenMsCY3CBa3LAQnozRCtCrYWSEtAdiskpamuJRFOQ==} + dependencies: + '@floating-ui/utils': 0.1.1 + dev: false + + /@floating-ui/dom@1.5.1: + resolution: {integrity: sha512-KwvVcPSXg6mQygvA1TjbN/gh///36kKtllIF8SUm0qpFj8+rvYrpvlYdL1JoA71SHpDqgSSdGOSoQ0Mp3uY5aw==} + dependencies: + '@floating-ui/core': 1.4.1 + '@floating-ui/utils': 0.1.1 + dev: false + + /@floating-ui/react-dom@2.0.1(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-rZtAmSht4Lry6gdhAJDrCp/6rKN7++JnL1/Anbr/DdeyYXQPxvg/ivrbYvJulbRf4vL8b212suwMM2lxbv+RQA==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + dependencies: + '@floating-ui/dom': 1.5.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@floating-ui/utils@0.1.1: + resolution: {integrity: sha512-m0G6wlnhm/AX0H12IOWtK8gASEMffnX08RtKkCgTdHb9JpHKGloI7icFfLg9ZmQeavcvR0PKmzxClyuFPSjKWw==} + dev: false + /@humanwhocodes/config-array@0.11.10: resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==} engines: {node: '>=10.10.0'} @@ -3147,6 +3175,7 @@ packages: /node-gyp-build-optional-packages@5.0.7: resolution: {integrity: sha512-YlCCc6Wffkx0kHkmam79GKvDQ6x+QZkMjFGrIMxgFNILFvGSbCp2fCBC55pGTT9gVaz8Na5CLmxt/urtzRv36w==} hasBin: true + requiresBuild: true dev: true optional: true diff --git a/FrontEnd/src/pages/timeline/Timeline.css b/FrontEnd/src/pages/timeline/Timeline.css index f071f163..30ad75c9 100644 --- a/FrontEnd/src/pages/timeline/Timeline.css +++ b/FrontEnd/src/pages/timeline/Timeline.css @@ -1,7 +1,19 @@ +.timeline { + --timeline-background-color: #f3f3f3; + --timeline-shadow-color: #00000080; + --timeline-post-line-color: #eadd2c; + --timeline-post-line-shadow: 2px 1px 10px -1px var(--timeline-shadow-color); + --timeline-post-card-background-color: rgb(255, 255, 255); + --timeline-post-card-shadow: 4px 2px 10px -2px var(--timeline-shadow-color); + --timeline-post-card-border-radius: 10px; + --timeline-post-text-color: #000000; +} + .timeline { z-index: 0; position: relative; width: 100%; + background-color: var(--timeline-background-color); } @keyframes timeline-line-node { @@ -26,6 +38,7 @@ from { transform: rotate(0turn); } + to { transform: rotate(1turn); } @@ -42,6 +55,7 @@ transform: translate(0, 100%); opacity: 0; } + to { opacity: 1; } @@ -71,40 +85,46 @@ } .timeline-line .segment { - width: 7px; + width: 12px; background: var(--cru-primary-color); } + .timeline-line .segment.start { height: 1.8em; flex: 0 0 auto; } + .timeline-line .segment.end { flex: 1 1 auto; } + .timeline-line .segment.current-end { height: 2em; flex: 0 0 auto; background: linear-gradient(var(--cru-primary-enhance-color), white); } + .timeline-line .node-container { flex: 0 0 auto; position: relative; width: 18px; height: 18px; } + .timeline-line .node { - width: 20px; - height: 20px; + width: 24px; + height: 24px; position: absolute; background: var(--cru-primary-color); - left: -1px; - top: -1px; + left: -3px; + top: -3px; border-radius: 50%; box-sizing: border-box; z-index: 1; animation: 1s infinite alternate; animation-name: timeline-line-node; } + .timeline-line .node-loading-edge { color: var(--cru-primary-color); width: 38px; @@ -116,11 +136,10 @@ z-index: 2; animation: 1.5s linear infinite timeline-line-node-loading-edge; } + .timeline-line.current .segment.start { - background: linear-gradient( - var(--cru-primary-color), - var(--cru-primary-enhance-color) - ); + background: linear-gradient(var(--cru-primary-color), + var(--cru-primary-enhance-color)); } .timeline-line.current .segment.end { @@ -137,67 +156,11 @@ animation-name: timeline-line-node-loading; } -.timeline-item { - position: relative; - padding: 0.5em; -} - -.timeline-item-card { - position: relative; - padding: 0.5em 0.5em 0.5em 4em; -} - -.timeline-item-card.enter-animation { - animation: 0.6s forwards; - opacity: 0; -} - -@media (max-width: 575.98px) { - .timeline-item-card { - padding-left: 3em; - } -} - -.timeline-item-header { - display: flex; - align-items: center; -} - -.timeline-avatar { - border-radius: 50%; - width: 2em; - height: 2em; -} - -.timeline-item-delete-button { - position: absolute; - right: 0; - bottom: 0; -} - -.timeline-content { - white-space: pre-line; -} - .timeline-content-image { max-width: 80%; max-height: 200px; } -.timeline-date-item { - position: relative; - padding: 0.3em 0 0.3em 4em; -} - -.timeline-date-item-badge { - display: inline-block; - padding: 0.1em 0.4em; - border-radius: 0.4em; - background: #7c7c7c; - color: white; - font-size: 0.8em; -} - .timeline-post-item-options-mask { background: rgba(255, 255, 255, 0.85); z-index: 100; @@ -234,4 +197,4 @@ .timeline-top { position: sticky; top: 56px; -} +} \ No newline at end of file diff --git a/FrontEnd/src/pages/timeline/Timeline.tsx b/FrontEnd/src/pages/timeline/Timeline.tsx index f93e1623..317d602e 100644 --- a/FrontEnd/src/pages/timeline/Timeline.tsx +++ b/FrontEnd/src/pages/timeline/Timeline.tsx @@ -1,4 +1,4 @@ -import * as React from "react"; +import { useState, useEffect } from "react"; import classnames from "classnames"; import { useScrollToBottom } from "@/utilities/hooks"; import { HubConnectionState } from "@microsoft/signalr"; @@ -14,56 +14,49 @@ import { HttpTimelinePostInfo, } from "@/http/timeline"; -import { useUser } from "@/services/user"; import { getTimelinePostUpdate$ } from "@/services/timeline"; -import TimelinePostListView from "./TimelinePostListView"; -import TimelineEmptyItem from "./TimelineEmptyItem"; -import TimelineLoading from "./TimelineLoading"; +import TimelinePostList from "./TimelinePostList"; import TimelinePostEdit from "./TimelinePostEdit"; -import TimelinePostEditNoLogin from "./TimelinePostEditNoLogin"; import TimelineCard from "./TimelineCard"; import "./Timeline.css"; export interface TimelineProps { className?: string; - style?: React.CSSProperties; timelineOwner: string; timelineName: string; } -const Timeline: React.FC = (props) => { - const { timelineOwner, timelineName, className, style } = props; +export function Timeline(props: TimelineProps) { + const { timelineOwner, timelineName, className } = props; - const user = useUser(); - - const [timeline, setTimeline] = React.useState(null); - const [posts, setPosts] = React.useState(null); - const [signalrState, setSignalrState] = React.useState( + const [timeline, setTimeline] = useState(null); + const [posts, setPosts] = useState(null); + const [signalrState, setSignalrState] = useState( HubConnectionState.Connecting, ); - const [error, setError] = React.useState< + const [error, setError] = useState< "offline" | "forbid" | "notfound" | "error" | null >(null); - const [currentPage, setCurrentPage] = React.useState(1); - const [totalPage, setTotalPage] = React.useState(0); + const [currentPage, setCurrentPage] = useState(1); + const [totalPage, setTotalPage] = useState(0); - const [timelineReloadKey, setTimelineReloadKey] = React.useState(0); - const [postsReloadKey, setPostsReloadKey] = React.useState(0); + const [timelineReloadKey, setTimelineReloadKey] = useState(0); + const [postsReloadKey, setPostsReloadKey] = useState(0); const updateTimeline = (): void => setTimelineReloadKey((o) => o + 1); const updatePosts = (): void => setPostsReloadKey((o) => o + 1); - React.useEffect(() => { + useEffect(() => { setTimeline(null); setPosts(null); setError(null); setSignalrState(HubConnectionState.Connecting); }, [timelineOwner, timelineName]); - React.useEffect(() => { + useEffect(() => { getHttpTimelineClient() .getTimeline(timelineOwner, timelineName) .then( @@ -85,7 +78,7 @@ const Timeline: React.FC = (props) => { ); }, [timelineOwner, timelineName, timelineReloadKey]); - React.useEffect(() => { + useEffect(() => { getHttpTimelineClient() .listPost(timelineOwner, timelineName, 1) .then( @@ -110,7 +103,7 @@ const Timeline: React.FC = (props) => { ); }, [timelineOwner, timelineName, postsReloadKey]); - React.useEffect(() => { + useEffect(() => { const timelinePostUpdate$ = getTimelinePostUpdate$( timelineOwner, timelineName, @@ -154,33 +147,16 @@ const Timeline: React.FC = (props) => { }, currentPage < totalPage); if (error === "offline") { - return ( -
- Offline. -
- ); + return
Offline.
; } else if (error === "notfound") { - return ( -
- Not exist. -
- ); + return
Not exist.
; } else if (error === "forbid") { - return ( -
- Forbid. -
- ); + return
Forbid.
; } else if (error === "error") { - return ( -
- Error. -
- ); + return
Error.
; } return ( <> - {timeline == null && posts == null && } {timeline && ( = (props) => { /> )} {posts && ( -
- - {timeline?.postable ? ( +
+ {timeline?.postable && ( - ) : user == null ? ( - - ) : null} - + )} +
)} ); -}; +} export default Timeline; diff --git a/FrontEnd/src/pages/timeline/TimelineCard.tsx b/FrontEnd/src/pages/timeline/TimelineCard.tsx index b287c620..04b34ec1 100644 --- a/FrontEnd/src/pages/timeline/TimelineCard.tsx +++ b/FrontEnd/src/pages/timeline/TimelineCard.tsx @@ -123,7 +123,7 @@ export default function TimelineCard(props: TimelinePageCardProps) { return ( = ({ date }) => { +export default function TimelineDateLabel({ date }: { date: Date }) { return ( -
- -
+ +
{date.toLocaleDateString()}
-
+ ); -}; - -export default TimelineDateLabel; +} diff --git a/FrontEnd/src/pages/timeline/TimelineEmptyItem.tsx b/FrontEnd/src/pages/timeline/TimelineEmptyItem.tsx deleted file mode 100644 index 5e0728d4..00000000 --- a/FrontEnd/src/pages/timeline/TimelineEmptyItem.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import * as React from "react"; -import classnames from "classnames"; - -import TimelineLine, { TimelineLineProps } from "./TimelineLine"; - -export interface TimelineEmptyItemProps extends Partial { - height?: number | string; - className?: string; - style?: React.CSSProperties; -} - -const TimelineEmptyItem: React.FC = (props) => { - const { height, style, className, center, ...lineProps } = props; - - return ( -
- -
- ); -}; - -export default TimelineEmptyItem; diff --git a/FrontEnd/src/pages/timeline/TimelineLine.tsx b/FrontEnd/src/pages/timeline/TimelineLine.tsx deleted file mode 100644 index 4a87e6e0..00000000 --- a/FrontEnd/src/pages/timeline/TimelineLine.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import * as React from "react"; -import classnames from "classnames"; - -export interface TimelineLineProps { - current?: boolean; - startSegmentLength?: string | number; - center: "node" | "loading" | "none"; - className?: string; - style?: React.CSSProperties; -} - -const TimelineLine: React.FC = ({ - startSegmentLength, - center, - current, - className, - style, -}) => { - return ( -
-
- {center !== "none" ? ( -
-
- {center === "loading" ? ( - - - - ) : null} -
- ) : null} - {center !== "loading" ?
: null} - {current &&
} -
- ); -}; - -export default TimelineLine; diff --git a/FrontEnd/src/pages/timeline/TimelineLoading.tsx b/FrontEnd/src/pages/timeline/TimelineLoading.tsx deleted file mode 100644 index f876cba9..00000000 --- a/FrontEnd/src/pages/timeline/TimelineLoading.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import * as React from "react"; - -import TimelineEmptyItem from "./TimelineEmptyItem"; - -const TimelineLoading: React.FC = () => { - return ( - - ); -}; - -export default TimelineLoading; diff --git a/FrontEnd/src/pages/timeline/TimelinePostCard.css b/FrontEnd/src/pages/timeline/TimelinePostCard.css new file mode 100644 index 00000000..3a446f44 --- /dev/null +++ b/FrontEnd/src/pages/timeline/TimelinePostCard.css @@ -0,0 +1,9 @@ +.timeline-post-card { + padding: 1em 1em 1em 3em; + color: var(--timeline-post-text-color); + background-color: var(--timeline-post-card-background-color); + box-shadow: var(--timeline-post-card-shadow); + border-radius: var(--timeline-post-card-border-radius); + position: relative; + z-index: 1; +} \ No newline at end of file diff --git a/FrontEnd/src/pages/timeline/TimelinePostCard.tsx b/FrontEnd/src/pages/timeline/TimelinePostCard.tsx new file mode 100644 index 00000000..83479349 --- /dev/null +++ b/FrontEnd/src/pages/timeline/TimelinePostCard.tsx @@ -0,0 +1,22 @@ +import { ReactNode } from "react"; +import classNames from "classnames"; + +import Card from "@/views/common/Card"; + +import "./TimelinePostCard.css"; + +export interface TimelinePostEditCardProps { + className?: string; + children?: ReactNode; +} + +export default function TimelinePostCard({ + className, + children, +}: TimelinePostEditCardProps) { + return ( + + {children} + + ); +} diff --git a/FrontEnd/src/pages/timeline/TimelinePostContainer.css b/FrontEnd/src/pages/timeline/TimelinePostContainer.css new file mode 100644 index 00000000..a12f70b1 --- /dev/null +++ b/FrontEnd/src/pages/timeline/TimelinePostContainer.css @@ -0,0 +1,3 @@ +.timeline-post-container { + padding: 0.5em 1em; +} \ No newline at end of file diff --git a/FrontEnd/src/pages/timeline/TimelinePostContainer.tsx b/FrontEnd/src/pages/timeline/TimelinePostContainer.tsx new file mode 100644 index 00000000..4697268b --- /dev/null +++ b/FrontEnd/src/pages/timeline/TimelinePostContainer.tsx @@ -0,0 +1,20 @@ +import { ReactNode } from "react"; +import classNames from "classnames"; + +import "./TimelinePostContainer.css"; + +export interface TimelinePostEditCardProps { + className?: string; + children?: ReactNode; +} + +export default function TimelinePostContainer({ + className, + children, +}: TimelinePostEditCardProps) { + return ( +
+ {children} +
+ ); +} diff --git a/FrontEnd/src/pages/timeline/TimelinePostEdit.tsx b/FrontEnd/src/pages/timeline/TimelinePostEdit.tsx index c1fa0dd9..b0cc763a 100644 --- a/FrontEnd/src/pages/timeline/TimelinePostEdit.tsx +++ b/FrontEnd/src/pages/timeline/TimelinePostEdit.tsx @@ -18,7 +18,7 @@ import BlobImage from "@/views/common/BlobImage"; import LoadingButton from "@/views/common/button/LoadingButton"; import PopupMenu from "@/views/common/menu/PopupMenu"; import MarkdownPostEdit from "./MarkdownPostEdit"; -import TimelinePostEditCard from "./TimelinePostEditCard"; +import TimelinePostEditCard from "./TimelinePostContainer"; import IconButton from "@/views/common/button/IconButton"; import "./TimelinePostEdit.css"; @@ -118,7 +118,7 @@ export interface TimelinePostEditProps { } const TimelinePostEdit: React.FC = (props) => { - const { timeline, style, className, onPosted } = props; + const { timeline, className, onPosted } = props; const { t } = useTranslation(); @@ -195,7 +195,7 @@ const TimelinePostEdit: React.FC = (props) => { }; return ( - + {showMarkdown ? ( = ({ - className, - style, - children, -}) => { - return ( -
- - {children} -
- ); -}; - -export default TimelinePostEdit; diff --git a/FrontEnd/src/pages/timeline/TimelinePostEditNoLogin.tsx b/FrontEnd/src/pages/timeline/TimelinePostEditNoLogin.tsx deleted file mode 100644 index 1ef0a287..00000000 --- a/FrontEnd/src/pages/timeline/TimelinePostEditNoLogin.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import * as React from "react"; -import { Trans } from "react-i18next"; -import { Link } from "react-router-dom"; - -import TimelinePostEditCard from "./TimelinePostEditCard"; - -export default function TimelinePostEditNoLogin(): React.ReactElement | null { - return ( - -
- }} - /> -
-
- ); -} diff --git a/FrontEnd/src/pages/timeline/TimelinePostList.css b/FrontEnd/src/pages/timeline/TimelinePostList.css new file mode 100644 index 00000000..bd575554 --- /dev/null +++ b/FrontEnd/src/pages/timeline/TimelinePostList.css @@ -0,0 +1,10 @@ +.timeline-post-timeline { + position: absolute; + left: 2.5em; + width: 1em; + top: 0; + bottom: 0; + background-color: var(--timeline-post-line-color); + box-shadow: var(--timeline-post-line-shadow); + z-index: -1; +} \ No newline at end of file diff --git a/FrontEnd/src/pages/timeline/TimelinePostList.tsx b/FrontEnd/src/pages/timeline/TimelinePostList.tsx new file mode 100644 index 00000000..a3501b33 --- /dev/null +++ b/FrontEnd/src/pages/timeline/TimelinePostList.tsx @@ -0,0 +1,76 @@ +import { useMemo, Fragment } from "react"; + +import { HttpTimelinePostInfo } from "@/http/timeline"; + +import TimelinePostView from "./TimelinePostView"; +import TimelineDateLabel from "./TimelineDateLabel"; + +import "./TimelinePostList.css"; + +function dateEqual(left: Date, right: Date): boolean { + return ( + left.getDate() == right.getDate() && + left.getMonth() == right.getMonth() && + left.getFullYear() == right.getFullYear() + ); +} + +interface TimelinePostListViewProps { + posts: HttpTimelinePostInfo[]; + onReload: () => void; +} + +export default function TimelinePostList(props: TimelinePostListViewProps) { + const { posts, onReload } = props; + + const groupedPosts = useMemo< + { + date: Date; + posts: (HttpTimelinePostInfo & { index: number })[]; + }[] + >(() => { + const result: { + date: Date; + posts: (HttpTimelinePostInfo & { index: number })[]; + }[] = []; + let index = 0; + for (const post of posts) { + const time = new Date(post.time); + if (result.length === 0) { + result.push({ date: time, posts: [{ ...post, index }] }); + } else { + const lastGroup = result[result.length - 1]; + if (dateEqual(lastGroup.date, time)) { + lastGroup.posts.push({ ...post, index }); + } else { + result.push({ date: time, posts: [{ ...post, index }] }); + } + } + index++; + } + return result; + }, [posts]); + + return ( +
+
+ {groupedPosts.map((group) => { + return ( + + + {group.posts.map((post) => { + return ( + + ); + })} + + ); + })} +
+ ); +} diff --git a/FrontEnd/src/pages/timeline/TimelinePostListView.tsx b/FrontEnd/src/pages/timeline/TimelinePostListView.tsx deleted file mode 100644 index f878b004..00000000 --- a/FrontEnd/src/pages/timeline/TimelinePostListView.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { Fragment } from "react"; -import * as React from "react"; - -import { HttpTimelinePostInfo } from "@/http/timeline"; - -import TimelinePostView from "./TimelinePostView"; -import TimelineDateLabel from "./TimelineDateLabel"; - -function dateEqual(left: Date, right: Date): boolean { - return ( - left.getDate() == right.getDate() && - left.getMonth() == right.getMonth() && - left.getFullYear() == right.getFullYear() - ); -} - -export interface TimelinePostListViewProps { - posts: HttpTimelinePostInfo[]; - onReload: () => void; -} - -const TimelinePostListView: React.FC = (props) => { - const { posts, onReload } = props; - - const groupedPosts = React.useMemo< - { - date: Date; - posts: (HttpTimelinePostInfo & { index: number })[]; - }[] - >(() => { - const result: { - date: Date; - posts: (HttpTimelinePostInfo & { index: number })[]; - }[] = []; - let index = 0; - for (const post of posts) { - const time = new Date(post.time); - if (result.length === 0) { - result.push({ date: time, posts: [{ ...post, index }] }); - } else { - const lastGroup = result[result.length - 1]; - if (dateEqual(lastGroup.date, time)) { - lastGroup.posts.push({ ...post, index }); - } else { - result.push({ date: time, posts: [{ ...post, index }] }); - } - } - index++; - } - return result; - }, [posts]); - - return ( - <> - {groupedPosts.map((group) => { - return ( - - - {group.posts.map((post) => { - return ( - - ); - })} - - ); - })} - - ); -}; - -export default TimelinePostListView; diff --git a/FrontEnd/src/pages/timeline/TimelinePostView.css b/FrontEnd/src/pages/timeline/TimelinePostView.css index 2cd8cd6b..229b4a7a 100644 --- a/FrontEnd/src/pages/timeline/TimelinePostView.css +++ b/FrontEnd/src/pages/timeline/TimelinePostView.css @@ -1,13 +1,3 @@ -.timeline-post { - position: relative; - padding: 0.5em; -} - -.timeline-post-card { - position: relative; - padding: 0.5em 0.5em 0.5em 4em; -} - .timeline-post-header { display: flex; align-items: center; @@ -19,10 +9,17 @@ height: 2em; } -.timeline-post-delete-button { +.timeline-post-edit-button { + float: right; +} + +.timeline-post-options-mask { position: absolute; - right: 0; - bottom: 0; + inset: 0; + background-color: hsla(0, 0%, 100%, 0.9); + display: flex; + align-items: center; + justify-content: space-around; } .timeline-post-content { diff --git a/FrontEnd/src/pages/timeline/TimelinePostView.tsx b/FrontEnd/src/pages/timeline/TimelinePostView.tsx index bdd2e3ef..2648fa21 100644 --- a/FrontEnd/src/pages/timeline/TimelinePostView.tsx +++ b/FrontEnd/src/pages/timeline/TimelinePostView.tsx @@ -1,5 +1,4 @@ import { useState } from "react"; -import classNames from "classnames"; import { getHttpTimelineClient, HttpTimelinePostInfo } from "@/http/timeline"; @@ -9,13 +8,14 @@ import { useClickOutside } from "@/utilities/hooks"; import UserAvatar from "@/views/common/user/UserAvatar"; import { useDialog } from "@/views/common/dialog"; -import Card from "@/views/common/Card"; import FlatButton from "@/views/common/button/FlatButton"; import ConfirmDialog from "@/views/common/dialog/ConfirmDialog"; -import TimelineLine from "./TimelineLine"; import TimelinePostContentView from "./TimelinePostContentView"; import IconButton from "@/views/common/button/IconButton"; +import TimelinePostContainer from "./TimelinePostContainer"; +import TimelinePostCard from "./TimelinePostCard"; + import "./TimelinePostView.css"; export interface TimelinePostViewProps { @@ -26,7 +26,7 @@ export interface TimelinePostViewProps { } export function TimelinePostView(props: TimelinePostViewProps) { - const { post, className, onDeleted } = props; + const { post, onDeleted } = props; const [operationMaskVisible, setOperationMaskVisible] = useState(false); @@ -43,12 +43,8 @@ export function TimelinePostView(props: TimelinePostViewProps) { useClickOutside(maskElement, () => setOperationMaskVisible(false)); return ( -
- - + + {post.editable && (
) : null} - + -
+ ); } -- cgit v1.2.3