diff options
author | crupest <crupest@outlook.com> | 2021-06-15 21:19:02 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2021-06-15 21:19:02 +0800 |
commit | 1552c0086c396aa89e0ad965a6cbd6c3ea70cac4 (patch) | |
tree | 2347d15102711f8dc917dfd6e1888fc1527c5c64 /FrontEnd/src | |
parent | c442bec1342a16ddfdb8e103f9b238e4581639e2 (diff) | |
download | timeline-1552c0086c396aa89e0ad965a6cbd6c3ea70cac4.tar.gz timeline-1552c0086c396aa89e0ad965a6cbd6c3ea70cac4.tar.bz2 timeline-1552c0086c396aa89e0ad965a6cbd6c3ea70cac4.zip |
...
Diffstat (limited to 'FrontEnd/src')
-rw-r--r-- | FrontEnd/src/index.css | 8 | ||||
-rw-r--r-- | FrontEnd/src/service-worker.tsx | 2 | ||||
-rw-r--r-- | FrontEnd/src/views/timeline-common/Timeline.tsx | 39 | ||||
-rw-r--r-- | FrontEnd/src/views/timeline-common/TimelinePageTemplate.tsx | 38 | ||||
-rw-r--r-- | FrontEnd/src/views/timeline-common/TimelinePagedPostListView.tsx | 2 | ||||
-rw-r--r-- | FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx | 170 | ||||
-rw-r--r-- | FrontEnd/src/views/timeline-common/TimelinePostListView.tsx | 1 | ||||
-rw-r--r-- | FrontEnd/src/views/timeline-common/TimelinePostView.tsx | 12 | ||||
-rw-r--r-- | FrontEnd/src/views/timeline-common/index.css | 3 |
9 files changed, 129 insertions, 146 deletions
diff --git a/FrontEnd/src/index.css b/FrontEnd/src/index.css index e102fbb4..bcced69a 100644 --- a/FrontEnd/src/index.css +++ b/FrontEnd/src/index.css @@ -1,3 +1,11 @@ +:root {
+ --tl-background-color: #f8f9fa;
+}
+
+body {
+ background: var(--tl-background-color);
+}
+
.tl-color-primary {
color: var(--tl-primary-color);
}
diff --git a/FrontEnd/src/service-worker.tsx b/FrontEnd/src/service-worker.tsx index ea8dfc32..e40124fe 100644 --- a/FrontEnd/src/service-worker.tsx +++ b/FrontEnd/src/service-worker.tsx @@ -4,7 +4,7 @@ import { Button } from "react-bootstrap"; import { pushAlert } from "./services/alert"; -if ("serviceWorker" in navigator) { +if (import.meta.env.PROD && "serviceWorker" in navigator) { let isThisTriggerUpgrade = false; const upgradeSuccessLocalStorageKey = "TIMELINE_UPGRADE_SUCCESS"; diff --git a/FrontEnd/src/views/timeline-common/Timeline.tsx b/FrontEnd/src/views/timeline-common/Timeline.tsx index 21daa5e2..90eba18f 100644 --- a/FrontEnd/src/views/timeline-common/Timeline.tsx +++ b/FrontEnd/src/views/timeline-common/Timeline.tsx @@ -6,7 +6,11 @@ import { HttpNetworkError, HttpNotFoundError, } from "@/http/common"; -import { getHttpTimelineClient, HttpTimelinePostInfo } from "@/http/timeline"; +import { + getHttpTimelineClient, + HttpTimelineInfo, + HttpTimelinePostInfo, +} from "@/http/timeline"; import { getTimelinePostUpdate$ } from "@/services/timeline"; @@ -15,6 +19,7 @@ import TimelineTop from "./TimelineTop"; import TimelineLoading from "./TimelineLoading"; import "./index.css"; +import TimelinePostEdit from "./TimelinePostEdit"; export interface TimelineProps { className?: string; @@ -31,10 +36,12 @@ const Timeline: React.FC<TimelineProps> = (props) => { const [state, setState] = React.useState< "loading" | "loaded" | "offline" | "notexist" | "forbid" | "error" >("loading"); + const [timeline, setTimeline] = React.useState<HttpTimelineInfo | null>(null); const [posts, setPosts] = React.useState<HttpTimelinePostInfo[]>([]); React.useEffect(() => { setState("loading"); + setTimeline(null); setPosts([]); }, [timelineName]); @@ -73,16 +80,20 @@ const Timeline: React.FC<TimelineProps> = (props) => { if (timelineName != null) { let subscribe = true; - void getHttpTimelineClient() - .listPost(timelineName) - .then( - (data) => { - if (subscribe) { - setState("loaded"); - setPosts(data); - } - }, - (error) => { + const client = getHttpTimelineClient(); + Promise.all([ + client.getTimeline(timelineName), + client.listPost(timelineName), + ]).then( + ([t, p]) => { + if (subscribe) { + setTimeline(t); + setPosts(p); + setState("loaded"); + } + }, + (error) => { + if (subscribe) { if (error instanceof HttpNetworkError) { setState("offline"); } else if (error instanceof HttpForbiddenError) { @@ -94,7 +105,8 @@ const Timeline: React.FC<TimelineProps> = (props) => { setState("error"); } } - ); + } + ); return () => { subscribe = false; @@ -137,6 +149,9 @@ const Timeline: React.FC<TimelineProps> = (props) => { posts={posts} onReload={onReload.current} /> + {timeline?.postable && ( + <TimelinePostEdit timeline={timeline} onPosted={onReload.current} /> + )} </> ); } diff --git a/FrontEnd/src/views/timeline-common/TimelinePageTemplate.tsx b/FrontEnd/src/views/timeline-common/TimelinePageTemplate.tsx index 658ce502..9b9ebbc2 100644 --- a/FrontEnd/src/views/timeline-common/TimelinePageTemplate.tsx +++ b/FrontEnd/src/views/timeline-common/TimelinePageTemplate.tsx @@ -87,29 +87,12 @@ const TimelinePageTemplate: React.FC<TimelinePageTemplateProps> = (props) => { } }, [timeline]); - const [bottomSpaceHeight, setBottomSpaceHeight] = React.useState<number>(0); - const [timelineReloadKey, setTimelineReloadKey] = React.useState<number>(0); const reloadTimeline = (): void => { setTimelineReloadKey((old) => old + 1); }; - const onPostEditHeightChange = React.useCallback((height: number): void => { - setBottomSpaceHeight(height); - if (height === 0) { - const alertHost = getAlertHost(); - if (alertHost != null) { - alertHost.style.removeProperty("margin-bottom"); - } - } else { - const alertHost = getAlertHost(); - if (alertHost != null) { - alertHost.style.marginBottom = `${height}px`; - } - } - }, []); - const cardCollapseLocalStorageKey = `timeline.${timelineName}.cardCollapse`; const [cardCollapse, setCardCollapse] = React.useState<boolean>(true); @@ -142,12 +125,7 @@ const TimelinePageTemplate: React.FC<TimelinePageTemplateProps> = (props) => { connectionStatus={connectionStatus} /> ) : null} - <Container - className="px-0" - style={{ - minHeight: `calc(100vh - ${56 + bottomSpaceHeight}px)`, - }} - > + <Container className="px-0"> {(() => { if (state === "offline") { // TODO: i18n @@ -169,20 +147,6 @@ const TimelinePageTemplate: React.FC<TimelinePageTemplateProps> = (props) => { } })()} </Container> - {timeline != null && timeline.postable ? ( - <> - <div - style={{ height: bottomSpaceHeight }} - className="flex-fix-length" - /> - <TimelinePostEdit - className="fixed-bottom" - timeline={timeline} - onHeightChange={onPostEditHeightChange} - onPosted={reloadTimeline} - /> - </> - ) : null} </> ); }; diff --git a/FrontEnd/src/views/timeline-common/TimelinePagedPostListView.tsx b/FrontEnd/src/views/timeline-common/TimelinePagedPostListView.tsx index 37f02a82..2cb32481 100644 --- a/FrontEnd/src/views/timeline-common/TimelinePagedPostListView.tsx +++ b/FrontEnd/src/views/timeline-common/TimelinePagedPostListView.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { HttpTimelinePostInfo } from "@/http/timeline"; +import { HttpTimelineInfo, HttpTimelinePostInfo } from "@/http/timeline"; import useScrollToTop from "@/utilities/useScrollToTop"; diff --git a/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx b/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx index 5f3f0345..abb04c1b 100644 --- a/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx +++ b/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx @@ -18,7 +18,9 @@ import { base64 } from "@/http/common"; import BlobImage from "../common/BlobImage"; import LoadingButton from "../common/LoadingButton"; import { PopupMenu } from "../common/Menu"; +import Card from "../common/Card"; import MarkdownPostEdit from "./MarkdownPostEdit"; +import TimelineLine from "./TimelineLine"; interface TimelinePostEditTextProps { text: string; @@ -110,13 +112,13 @@ const postKindIconClassNameMap: Record<PostKind, string> = { export interface TimelinePostEditProps { className?: string; + style?: React.CSSProperties; timeline: HttpTimelineInfo; onPosted: (newPost: HttpTimelinePostInfo) => void; - onHeightChange?: (height: number) => void; } const TimelinePostEdit: React.FC<TimelinePostEditProps> = (props) => { - const { timeline, onHeightChange, className, onPosted } = props; + const { timeline, style, className, onPosted } = props; const { t } = useTranslation(); @@ -138,24 +140,6 @@ const TimelinePostEdit: React.FC<TimelinePostEditProps> = (props) => { (kind === "text" && text.length !== 0) || (kind === "image" && image != null); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const containerRef = React.useRef<HTMLDivElement>(null!); - - const notifyHeightChange = (): void => { - if (onHeightChange) { - onHeightChange(containerRef.current.clientHeight); - } - }; - - React.useEffect(() => { - notifyHeightChange(); - return () => { - if (onHeightChange) { - onHeightChange(0); - } - }; - }); - const onPostError = (): void => { pushAlert({ type: "danger", @@ -212,78 +196,86 @@ const TimelinePostEdit: React.FC<TimelinePostEditProps> = (props) => { return ( <div - ref={containerRef} - className={classnames("container-fluid bg-light", className)} + className={classnames("timeline-item current", className)} + style={style} > - {showMarkdown ? ( - <MarkdownPostEdit - className="w-100" - onClose={() => setShowMarkdown(false)} - timeline={timeline.name} - onPosted={onPosted} - onPostError={onPostError} - /> - ) : ( - <Row> - <Col className="px-1 py-1"> - {(() => { - if (kind === "text") { - return ( - <TimelinePostEditText - className="w-100 h-100 timeline-post-edit" - text={text} - disabled={process} - onChange={(t) => { - setText(t); - window.localStorage.setItem(draftTextLocalStorageKey, t); - }} - /> - ); - } else if (kind === "image") { - return ( - <TimelinePostEditImage - onSelect={setImage} - disabled={process} + <TimelineLine center="node" current /> + <Card className="timeline-item-card"> + {showMarkdown ? ( + <MarkdownPostEdit + className="w-100" + onClose={() => setShowMarkdown(false)} + timeline={timeline.name} + onPosted={onPosted} + onPostError={onPostError} + /> + ) : ( + <Row> + <Col className="px-1 py-1"> + {(() => { + if (kind === "text") { + return ( + <TimelinePostEditText + className="w-100 h-100 timeline-post-edit" + text={text} + disabled={process} + onChange={(t) => { + setText(t); + window.localStorage.setItem( + draftTextLocalStorageKey, + t + ); + }} + /> + ); + } else if (kind === "image") { + return ( + <TimelinePostEditImage + onSelect={setImage} + disabled={process} + /> + ); + } + })()} + </Col> + <Col xs="auto" className="align-self-end m-1"> + <div className="d-block text-center mt-1 mb-2"> + <PopupMenu + items={(["text", "image", "markdown"] as const).map( + (kind) => ({ + type: "button", + text: `timeline.post.type.${kind}`, + iconClassName: postKindIconClassNameMap[kind], + onClick: () => { + if (kind === "markdown") { + setShowMarkdown(true); + } else { + setKind(kind); + } + }, + }) + )} + > + <i + className={classnames( + postKindIconClassNameMap[kind], + "icon-button large" + )} /> - ); - } - })()} - </Col> - <Col xs="auto" className="align-self-end m-1"> - <div className="d-block text-center mt-1 mb-2"> - <PopupMenu - items={(["text", "image", "markdown"] as const).map((kind) => ({ - type: "button", - text: `timeline.post.type.${kind}`, - iconClassName: postKindIconClassNameMap[kind], - onClick: () => { - if (kind === "markdown") { - setShowMarkdown(true); - } else { - setKind(kind); - } - }, - }))} + </PopupMenu> + </div> + <LoadingButton + variant="primary" + onClick={onSend} + disabled={!canSend} + loading={process} > - <i - className={classnames( - postKindIconClassNameMap[kind], - "icon-button large" - )} - /> - </PopupMenu> - </div> - <LoadingButton - variant="primary" - onClick={onSend} - disabled={!canSend} - loading={process} - > - {t("timeline.send")} - </LoadingButton> - </Col> - </Row> - )} + {t("timeline.send")} + </LoadingButton> + </Col> + </Row> + )} + </Card> </div> ); }; diff --git a/FrontEnd/src/views/timeline-common/TimelinePostListView.tsx b/FrontEnd/src/views/timeline-common/TimelinePostListView.tsx index ba204b72..3213f76d 100644 --- a/FrontEnd/src/views/timeline-common/TimelinePostListView.tsx +++ b/FrontEnd/src/views/timeline-common/TimelinePostListView.tsx @@ -63,7 +63,6 @@ const TimelinePostListView: React.FC<TimelinePostListViewProps> = (props) => { <TimelinePostView key={post.id} post={post} - current={posts.length - 1 === post.index} onChanged={onReload} onDeleted={onReload} /> diff --git a/FrontEnd/src/views/timeline-common/TimelinePostView.tsx b/FrontEnd/src/views/timeline-common/TimelinePostView.tsx index ea40f80a..5572c5c3 100644 --- a/FrontEnd/src/views/timeline-common/TimelinePostView.tsx +++ b/FrontEnd/src/views/timeline-common/TimelinePostView.tsx @@ -16,7 +16,6 @@ import PostPropertyChangeDialog from "./PostPropertyChangeDialog"; export interface TimelinePostViewProps { post: HttpTimelinePostInfo; - current?: boolean; className?: string; style?: React.CSSProperties; cardStyle?: React.CSSProperties; @@ -26,7 +25,6 @@ export interface TimelinePostViewProps { const TimelinePostView: React.FC<TimelinePostViewProps> = (props) => { const { post, className, style, cardStyle, onChanged, onDeleted } = props; - const current = props.current === true; const [operationMaskVisible, setOperationMaskVisible] = React.useState<boolean>(false); @@ -55,11 +53,15 @@ const TimelinePostView: React.FC<TimelinePostViewProps> = (props) => { return ( <div id={`timeline-post-${post.id}`} - className={classnames("timeline-item", current && "current", className)} + className={classnames("timeline-item", className)} style={style} > - <TimelineLine center="node" current={current} /> - <Card ref={cardRef} className="timeline-item-card" style={cardStyle}> + <TimelineLine center="node" /> + <Card + ref={cardRef} + className="timeline-item-card enter-animation" + style={cardStyle} + > {post.editable ? ( <i className="bi-chevron-down text-info icon-button float-end" diff --git a/FrontEnd/src/views/timeline-common/index.css b/FrontEnd/src/views/timeline-common/index.css index 7d7eb213..e59983aa 100644 --- a/FrontEnd/src/views/timeline-common/index.css +++ b/FrontEnd/src/views/timeline-common/index.css @@ -150,6 +150,9 @@ .timeline-item-card { position: relative; padding: 0.3em 0.5em 1em 4em; +} + +.timeline-item-card.enter-animation { animation: 0.6s forwards; opacity: 0; } |