diff options
-rw-r--r-- | FrontEnd/src/pages/timeline/TimelinePostView.css | 30 | ||||
-rw-r--r-- | FrontEnd/src/pages/timeline/TimelinePostView.tsx | 102 | ||||
-rw-r--r-- | FrontEnd/src/views/common/dialog/index.ts | 14 |
3 files changed, 78 insertions, 68 deletions
diff --git a/FrontEnd/src/pages/timeline/TimelinePostView.css b/FrontEnd/src/pages/timeline/TimelinePostView.css new file mode 100644 index 00000000..2cd8cd6b --- /dev/null +++ b/FrontEnd/src/pages/timeline/TimelinePostView.css @@ -0,0 +1,30 @@ +.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; +} + +.timeline-post-author-avatar { + border-radius: 50%; + width: 2em; + height: 2em; +} + +.timeline-post-delete-button { + position: absolute; + right: 0; + bottom: 0; +} + +.timeline-post-content { + white-space: pre-line; +}
\ No newline at end of file diff --git a/FrontEnd/src/pages/timeline/TimelinePostView.tsx b/FrontEnd/src/pages/timeline/TimelinePostView.tsx index f7aec169..bdd2e3ef 100644 --- a/FrontEnd/src/pages/timeline/TimelinePostView.tsx +++ b/FrontEnd/src/pages/timeline/TimelinePostView.tsx @@ -1,5 +1,5 @@ -import * as React from "react"; -import classnames from "classnames"; +import { useState } from "react"; +import classNames from "classnames"; import { getHttpTimelineClient, HttpTimelinePostInfo } from "@/http/timeline"; @@ -8,6 +8,7 @@ import { pushAlert } from "@/services/alert"; 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"; @@ -15,92 +16,68 @@ import TimelineLine from "./TimelineLine"; import TimelinePostContentView from "./TimelinePostContentView"; import IconButton from "@/views/common/button/IconButton"; +import "./TimelinePostView.css"; + export interface TimelinePostViewProps { post: HttpTimelinePostInfo; className?: string; - style?: React.CSSProperties; - cardStyle?: React.CSSProperties; onChanged: (post: HttpTimelinePostInfo) => void; onDeleted: () => void; } -const TimelinePostView: React.FC<TimelinePostViewProps> = (props) => { - const { post, className, style, cardStyle, onChanged, onDeleted } = props; +export function TimelinePostView(props: TimelinePostViewProps) { + const { post, className, onDeleted } = props; const [operationMaskVisible, setOperationMaskVisible] = - React.useState<boolean>(false); - const [dialog, setDialog] = React.useState< - "delete" | "changeproperty" | null - >(null); + useState<boolean>(false); - const [maskElement, setMaskElement] = React.useState<HTMLElement | null>( - null, - ); + const { switchDialog, dialogPropsMap } = useDialog(["delete"], { + onClose: { + delete: () => { + setOperationMaskVisible(false); + }, + }, + }); + const [maskElement, setMaskElement] = useState<HTMLElement | null>(null); useClickOutside(maskElement, () => setOperationMaskVisible(false)); - const cardRef = React.useRef<HTMLDivElement>(null); - React.useEffect(() => { - const cardIntersectionObserver = new IntersectionObserver(([e]) => { - if (e.intersectionRatio > 0) { - if (cardRef.current != null) { - cardRef.current.style.animationName = "timeline-post-enter"; - } - } - }); - if (cardRef.current) { - cardIntersectionObserver.observe(cardRef.current); - } - - return () => { - cardIntersectionObserver.disconnect(); - }; - }, []); - return ( <div id={`timeline-post-${post.id}`} - className={classnames("timeline-item", className)} - style={style} + className={classNames("timeline-post cru-primary", className)} > <TimelineLine center="node" /> - <Card - containerRef={cardRef} - className="timeline-item-card enter-animation" - style={cardStyle} - > - {post.editable ? ( + <Card className="timeline-post-card"> + {post.editable && ( <IconButton icon="chevron-down" - color="primary" - className="cru-float-right" + className="timeline-post-edit-button" onClick={(e) => { setOperationMaskVisible(true); e.stopPropagation(); }} /> - ) : null} - <div className="timeline-item-header"> - <span className="me-2"> - <span> - <UserAvatar - username={post.author.username} - className="timeline-avatar me-1" - /> - <small className="text-dark me-2">{post.author.nickname}</small> - <small className="text-secondary white-space-no-wrap"> - {new Date(post.time).toLocaleTimeString()} - </small> - </span> - </span> + )} + <div className="timeline-post-header"> + <UserAvatar + username={post.author.username} + className="timeline-post-author-avatar" + /> + <small className="timeline-post-author-nickname"> + {post.author.nickname} + </small> + <small className="timeline-post-time"> + {new Date(post.time).toLocaleTimeString()} + </small> </div> - <div className="timeline-content"> + <div className="timeline-post-content"> <TimelinePostContentView post={post} /> </div> {operationMaskVisible ? ( <div ref={setMaskElement} - className="timeline-post-item-options-mask" + className="timeline-post-options-mask" onClick={() => { setOperationMaskVisible(false); }} @@ -108,7 +85,6 @@ const TimelinePostView: React.FC<TimelinePostViewProps> = (props) => { <FlatButton text="changeProperty" onClick={(e) => { - setDialog("changeproperty"); e.stopPropagation(); }} /> @@ -116,7 +92,7 @@ const TimelinePostView: React.FC<TimelinePostViewProps> = (props) => { text="delete" color="danger" onClick={(e) => { - setDialog("delete"); + switchDialog("delete"); e.stopPropagation(); }} /> @@ -126,11 +102,6 @@ const TimelinePostView: React.FC<TimelinePostViewProps> = (props) => { <ConfirmDialog title="timeline.post.deleteDialog.title" body="timeline.post.deleteDialog.prompt" - open={dialog === "delete"} - onClose={() => { - setDialog(null); - setOperationMaskVisible(false); - }} onConfirm={() => { void getHttpTimelineClient() .deletePost(post.timelineOwnerV2, post.timelineNameV2, post.id) @@ -141,9 +112,10 @@ const TimelinePostView: React.FC<TimelinePostViewProps> = (props) => { }); }); }} + {...dialogPropsMap.delete} /> </div> ); -}; +} export default TimelinePostView; diff --git a/FrontEnd/src/views/common/dialog/index.ts b/FrontEnd/src/views/common/dialog/index.ts index e37b9ed2..59f15791 100644 --- a/FrontEnd/src/views/common/dialog/index.ts +++ b/FrontEnd/src/views/common/dialog/index.ts @@ -18,14 +18,19 @@ type DialogPropsMap<D extends string> = DialogMap< export function useDialog<D extends string>( dialogs: D[], - initDialog?: D | null, + options?: { + initDialog?: D | null; + onClose?: { + [K in D]?: () => void; + }; + }, ): { dialog: D | null; switchDialog: (newDialog: D | null) => void; dialogPropsMap: DialogPropsMap<D>; createDialogSwitch: (newDialog: D | null) => () => void; } { - const [dialog, setDialog] = useState<D | null>(initDialog ?? null); + const [dialog, setDialog] = useState<D | null>(options?.initDialog ?? null); const [dialogKeys, setDialogKeys] = useState<DialogKeyMap<D>>( () => Object.fromEntries(dialogs.map((d) => [d, 0])) as DialogKeyMap<D>, @@ -47,7 +52,10 @@ export function useDialog<D extends string>( { key: `${d}-${dialogKeys[d]}`, open: dialog === d, - onClose: () => switchDialog(null), + onClose: () => { + switchDialog(null); + options?.onClose?.[d]?.(); + }, }, ]), ) as DialogPropsMap<D>, |