diff options
author | crupest <crupest@outlook.com> | 2023-08-01 00:29:35 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2023-08-01 00:29:35 +0800 |
commit | e6ccc0174a86a0ade240e6551228598cd81f984b (patch) | |
tree | d495dff9658c23de22191c4532e14016a6fb96a8 /FrontEnd | |
parent | 710ff9d3d2e55113798c39b0595f8f71b12091ef (diff) | |
download | timeline-e6ccc0174a86a0ade240e6551228598cd81f984b.tar.gz timeline-e6ccc0174a86a0ade240e6551228598cd81f984b.tar.bz2 timeline-e6ccc0174a86a0ade240e6551228598cd81f984b.zip |
...
Diffstat (limited to 'FrontEnd')
21 files changed, 199 insertions, 315 deletions
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<TimelineProps> = (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<HttpTimelineInfo | null>(null); - const [posts, setPosts] = React.useState<HttpTimelinePostInfo[] | null>(null); - const [signalrState, setSignalrState] = React.useState<HubConnectionState>( + const [timeline, setTimeline] = useState<HttpTimelineInfo | null>(null); + const [posts, setPosts] = useState<HttpTimelinePostInfo[] | null>(null); + const [signalrState, setSignalrState] = useState<HubConnectionState>( 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<TimelineProps> = (props) => { ); }, [timelineOwner, timelineName, timelineReloadKey]); - React.useEffect(() => { + useEffect(() => { getHttpTimelineClient() .listPost(timelineOwner, timelineName, 1) .then( @@ -110,7 +103,7 @@ const Timeline: React.FC<TimelineProps> = (props) => { ); }, [timelineOwner, timelineName, postsReloadKey]); - React.useEffect(() => { + useEffect(() => { const timelinePostUpdate$ = getTimelinePostUpdate$( timelineOwner, timelineName, @@ -154,33 +147,16 @@ const Timeline: React.FC<TimelineProps> = (props) => { }, currentPage < totalPage); if (error === "offline") { - return ( - <div className={className} style={style}> - Offline. - </div> - ); + return <div className={className}>Offline.</div>; } else if (error === "notfound") { - return ( - <div className={className} style={style}> - Not exist. - </div> - ); + return <div className={className}>Not exist.</div>; } else if (error === "forbid") { - return ( - <div className={className} style={style}> - Forbid. - </div> - ); + return <div className={className}>Forbid.</div>; } else if (error === "error") { - return ( - <div className={className} style={style}> - Error. - </div> - ); + return <div className={className}>Error.</div>; } return ( <> - {timeline == null && posts == null && <TimelineLoading />} {timeline && ( <TimelineCard timeline={timeline} @@ -189,18 +165,15 @@ const Timeline: React.FC<TimelineProps> = (props) => { /> )} {posts && ( - <div style={style} className={classnames("timeline", className)}> - <TimelineEmptyItem className="timeline-top" height={50} /> - {timeline?.postable ? ( + <div className={classnames("timeline", className)}> + {timeline?.postable && ( <TimelinePostEdit timeline={timeline} onPosted={updatePosts} /> - ) : user == null ? ( - <TimelinePostEditNoLogin /> - ) : null} - <TimelinePostListView posts={posts} onReload={updatePosts} /> + )} + <TimelinePostList posts={posts} onReload={updatePosts} /> </div> )} </> ); -}; +} 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 ( <Card - color="secondary" + color="tertiary" className={`timeline-card timeline-card-${ collapse ? "collapse" : "expand" }`} diff --git a/FrontEnd/src/pages/timeline/TimelineDateLabel.css b/FrontEnd/src/pages/timeline/TimelineDateLabel.css new file mode 100644 index 00000000..1c66045b --- /dev/null +++ b/FrontEnd/src/pages/timeline/TimelineDateLabel.css @@ -0,0 +1,9 @@ +.timeline-post-date-badge { + display: inline-block; + padding: 0.1em 0.4em; + border-radius: 0.4em; + background: #7c7c7c; + color: white; + font-size: 0.8em; + margin-left: 5em; +}
\ No newline at end of file diff --git a/FrontEnd/src/pages/timeline/TimelineDateLabel.tsx b/FrontEnd/src/pages/timeline/TimelineDateLabel.tsx index 5f4ac706..eaadcc1a 100644 --- a/FrontEnd/src/pages/timeline/TimelineDateLabel.tsx +++ b/FrontEnd/src/pages/timeline/TimelineDateLabel.tsx @@ -1,19 +1,13 @@ -import * as React from "react"; -import TimelineLine from "./TimelineLine"; +import TimelinePostContainer from "./TimelinePostContainer"; -export interface TimelineDateItemProps { - date: Date; -} +import "./TimelineDateLabel.css"; -const TimelineDateLabel: React.FC<TimelineDateItemProps> = ({ date }) => { +export default function TimelineDateLabel({ date }: { date: Date }) { return ( - <div className="timeline-date-item"> - <TimelineLine center="none" /> - <div className="timeline-date-item-badge"> + <TimelinePostContainer> + <div className="timeline-post-date-badge"> {date.toLocaleDateString()} </div> - </div> + </TimelinePostContainer> ); -}; - -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<TimelineLineProps> { - height?: number | string; - className?: string; - style?: React.CSSProperties; -} - -const TimelineEmptyItem: React.FC<TimelineEmptyItemProps> = (props) => { - const { height, style, className, center, ...lineProps } = props; - - return ( - <div - style={{ ...style, height: height }} - className={classnames("timeline-item", className)} - > - <TimelineLine center={center ?? "none"} {...lineProps} /> - </div> - ); -}; - -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<TimelineLineProps> = ({ - startSegmentLength, - center, - current, - className, - style, -}) => { - return ( - <div - className={classnames( - "timeline-line", - current && "current", - center === "loading" && "loading", - className - )} - style={style} - > - <div className="segment start" style={{ height: startSegmentLength }} /> - {center !== "none" ? ( - <div className="node-container"> - <div className="node"></div> - {center === "loading" ? ( - <svg className="node-loading-edge" viewBox="0 0 100 100"> - <path - d="M 50,10 A 40 40 45 0 1 78.28,21.72" - stroke="currentcolor" - strokeLinecap="square" - strokeWidth="8" - /> - </svg> - ) : null} - </div> - ) : null} - {center !== "loading" ? <div className="segment end"></div> : null} - {current && <div className="segment current-end" />} - </div> - ); -}; - -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 ( - <TimelineEmptyItem - className="timeline-top-loading-enter" - height={100} - center="loading" - startSegmentLength={56} - /> - ); -}; - -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 ( + <Card className={classNames("timeline-post-card", className)}> + {children} + </Card> + ); +} 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 ( + <div className={classNames("timeline-post-container", className)}> + {children} + </div> + ); +} 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<TimelinePostEditProps> = (props) => { - const { timeline, style, className, onPosted } = props; + const { timeline, className, onPosted } = props; const { t } = useTranslation(); @@ -195,7 +195,7 @@ const TimelinePostEdit: React.FC<TimelinePostEditProps> = (props) => { }; return ( - <TimelinePostEditCard className={className} style={style}> + <TimelinePostEditCard className={className}> {showMarkdown ? ( <MarkdownPostEdit className="cru-fill-parent" diff --git a/FrontEnd/src/pages/timeline/TimelinePostEditCard.tsx b/FrontEnd/src/pages/timeline/TimelinePostEditCard.tsx deleted file mode 100644 index fdd53983..00000000 --- a/FrontEnd/src/pages/timeline/TimelinePostEditCard.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import * as React from "react"; -import classnames from "classnames"; - -import Card from "@/views/common/Card"; -import TimelineLine from "./TimelineLine"; - -import "./TimelinePostEdit.css"; - -export interface TimelinePostEditCardProps { - className?: string; - style?: React.CSSProperties; - children?: React.ReactNode; -} - -const TimelinePostEdit: React.FC<TimelinePostEditCardProps> = ({ - className, - style, - children, -}) => { - return ( - <div - className={classnames("timeline-item timeline-post-edit", className)} - style={style} - > - <TimelineLine center="node" /> - <Card className="timeline-item-card">{children}</Card> - </div> - ); -}; - -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 ( - <TimelinePostEditCard> - <div className="mt-3 mb-4"> - <Trans - i18nKey="timeline.postNoLogin" - components={{ l: <Link to="/login" /> }} - /> - </div> - </TimelinePostEditCard> - ); -} 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/TimelinePostListView.tsx b/FrontEnd/src/pages/timeline/TimelinePostList.tsx index f878b004..a3501b33 100644 --- a/FrontEnd/src/pages/timeline/TimelinePostListView.tsx +++ b/FrontEnd/src/pages/timeline/TimelinePostList.tsx @@ -1,11 +1,12 @@ -import { Fragment } from "react"; -import * as React from "react"; +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() && @@ -14,15 +15,15 @@ function dateEqual(left: Date, right: Date): boolean { ); } -export interface TimelinePostListViewProps { +interface TimelinePostListViewProps { posts: HttpTimelinePostInfo[]; onReload: () => void; } -const TimelinePostListView: React.FC<TimelinePostListViewProps> = (props) => { +export default function TimelinePostList(props: TimelinePostListViewProps) { const { posts, onReload } = props; - const groupedPosts = React.useMemo< + const groupedPosts = useMemo< { date: Date; posts: (HttpTimelinePostInfo & { index: number })[]; @@ -51,7 +52,8 @@ const TimelinePostListView: React.FC<TimelinePostListViewProps> = (props) => { }, [posts]); return ( - <> + <div> + <div className="timeline-post-timeline" /> {groupedPosts.map((group) => { return ( <Fragment key={group.date.toDateString()}> @@ -69,8 +71,6 @@ const TimelinePostListView: React.FC<TimelinePostListViewProps> = (props) => { </Fragment> ); })} - </> + </div> ); -}; - -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<boolean>(false); @@ -43,12 +43,8 @@ export function TimelinePostView(props: TimelinePostViewProps) { useClickOutside(maskElement, () => setOperationMaskVisible(false)); return ( - <div - id={`timeline-post-${post.id}`} - className={classNames("timeline-post cru-primary", className)} - > - <TimelineLine center="node" /> - <Card className="timeline-post-card"> + <TimelinePostContainer> + <TimelinePostCard className="cru-primary"> {post.editable && ( <IconButton icon="chevron-down" @@ -98,7 +94,7 @@ export function TimelinePostView(props: TimelinePostViewProps) { /> </div> ) : null} - </Card> + </TimelinePostCard> <ConfirmDialog title="timeline.post.deleteDialog.title" body="timeline.post.deleteDialog.prompt" @@ -114,7 +110,7 @@ export function TimelinePostView(props: TimelinePostViewProps) { }} {...dialogPropsMap.delete} /> - </div> + </TimelinePostContainer> ); } |