diff options
author | crupest <crupest@outlook.com> | 2023-07-30 23:47:53 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2023-07-30 23:47:53 +0800 |
commit | 538d6830a0022b49b99695095d85e567b0c86e71 (patch) | |
tree | a0c4d164b05d03f636d603b28f77ca881c16ef10 /FrontEnd/src/pages/timeline/TimelinePostContentView.tsx | |
parent | a148f11c193d35ba489f887ed393aedf58a1c714 (diff) | |
download | timeline-538d6830a0022b49b99695095d85e567b0c86e71.tar.gz timeline-538d6830a0022b49b99695095d85e567b0c86e71.tar.bz2 timeline-538d6830a0022b49b99695095d85e567b0c86e71.zip |
...
Diffstat (limited to 'FrontEnd/src/pages/timeline/TimelinePostContentView.tsx')
-rw-r--r-- | FrontEnd/src/pages/timeline/TimelinePostContentView.tsx | 187 |
1 files changed, 187 insertions, 0 deletions
diff --git a/FrontEnd/src/pages/timeline/TimelinePostContentView.tsx b/FrontEnd/src/pages/timeline/TimelinePostContentView.tsx new file mode 100644 index 00000000..41080e10 --- /dev/null +++ b/FrontEnd/src/pages/timeline/TimelinePostContentView.tsx @@ -0,0 +1,187 @@ +import * as React from "react"; +import classnames from "classnames"; +import { marked } from "marked"; + +import { UiLogicError } from "@/common"; + +import { HttpNetworkError } from "@/http/common"; +import { getHttpTimelineClient, HttpTimelinePostInfo } from "@/http/timeline"; + +import { useUser } from "@/services/user"; + +import Skeleton from "@/views/common/Skeleton"; +import LoadFailReload from "@/views/common/LoadFailReload"; + +const TextView: React.FC<TimelinePostContentViewProps> = (props) => { + const { post, className, style } = props; + + const [text, setText] = React.useState<string | null>(null); + const [error, setError] = React.useState<"offline" | "error" | null>(null); + + const [reloadKey, setReloadKey] = React.useState<number>(0); + + React.useEffect(() => { + let subscribe = true; + + setText(null); + setError(null); + + void getHttpTimelineClient() + .getPostDataAsString(post.timelineOwnerV2, post.timelineNameV2, post.id) + .then( + (data) => { + if (subscribe) setText(data); + }, + (error) => { + if (subscribe) { + if (error instanceof HttpNetworkError) { + setError("offline"); + } else { + setError("error"); + } + } + } + ); + + return () => { + subscribe = false; + }; + }, [post.timelineOwnerV2, post.timelineNameV2, post.id, reloadKey]); + + if (error != null) { + return ( + <LoadFailReload + className={className} + style={style} + onReload={() => setReloadKey(reloadKey + 1)} + /> + ); + } else if (text == null) { + return <Skeleton />; + } else { + return ( + <div className={className} style={style}> + {text} + </div> + ); + } +}; + +const ImageView: React.FC<TimelinePostContentViewProps> = (props) => { + const { post, className, style } = props; + + useUser(); + + return ( + <img + src={getHttpTimelineClient().generatePostDataUrl( + post.timelineOwnerV2, + post.timelineNameV2, + post.id + )} + className={classnames(className, "timeline-content-image")} + style={style} + /> + ); +}; + +const MarkdownView: React.FC<TimelinePostContentViewProps> = (props) => { + const { post, className, style } = props; + + const [markdown, setMarkdown] = React.useState<string | null>(null); + const [error, setError] = React.useState<"offline" | "error" | null>(null); + + const [reloadKey, setReloadKey] = React.useState<number>(0); + + React.useEffect(() => { + let subscribe = true; + + setMarkdown(null); + setError(null); + + void getHttpTimelineClient() + .getPostDataAsString(post.timelineOwnerV2, post.timelineNameV2, post.id) + .then( + (data) => { + if (subscribe) setMarkdown(data); + }, + (error) => { + if (subscribe) { + if (error instanceof HttpNetworkError) { + setError("offline"); + } else { + setError("error"); + } + } + } + ); + + return () => { + subscribe = false; + }; + }, [post.timelineOwnerV2, post.timelineNameV2, post.id, reloadKey]); + + const markdownHtml = React.useMemo<string | null>(() => { + if (markdown == null) return null; + return marked.parse(markdown); + }, [markdown]); + + if (error != null) { + return ( + <LoadFailReload + className={className} + style={style} + onReload={() => setReloadKey(reloadKey + 1)} + /> + ); + } else if (markdown == null) { + return <Skeleton />; + } else { + if (markdownHtml == null) { + throw new UiLogicError("Markdown is not null but markdown html is."); + } + return ( + <div + className={classnames(className, "markdown-container")} + style={style} + dangerouslySetInnerHTML={{ + __html: markdownHtml, + }} + /> + ); + } +}; + +export interface TimelinePostContentViewProps { + post: HttpTimelinePostInfo; + className?: string; + style?: React.CSSProperties; +} + +const viewMap: Record<string, React.FC<TimelinePostContentViewProps>> = { + "text/plain": TextView, + "text/markdown": MarkdownView, + "image/png": ImageView, + "image/jpeg": ImageView, + "image/gif": ImageView, + "image/webp": ImageView, +}; + +const TimelinePostContentView: React.FC<TimelinePostContentViewProps> = ( + props +) => { + const { post, className, style } = props; + + const type = post.dataList[0].kind; + + if (type in viewMap) { + const View = viewMap[type]; + return <View post={post} className={className} style={style} />; + } else { + // TODO: i18n + console.error("Unknown post type", post); + return <div>Error, unknown post type!</div>; + } +}; + +export default TimelinePostContentView; |