aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2021-06-15 21:19:02 +0800
committercrupest <crupest@outlook.com>2021-06-15 21:19:02 +0800
commit1552c0086c396aa89e0ad965a6cbd6c3ea70cac4 (patch)
tree2347d15102711f8dc917dfd6e1888fc1527c5c64 /FrontEnd
parentc442bec1342a16ddfdb8e103f9b238e4581639e2 (diff)
downloadtimeline-1552c0086c396aa89e0ad965a6cbd6c3ea70cac4.tar.gz
timeline-1552c0086c396aa89e0ad965a6cbd6c3ea70cac4.tar.bz2
timeline-1552c0086c396aa89e0ad965a6cbd6c3ea70cac4.zip
...
Diffstat (limited to 'FrontEnd')
-rw-r--r--FrontEnd/.vscode/extensions.json1
-rw-r--r--FrontEnd/src/index.css8
-rw-r--r--FrontEnd/src/service-worker.tsx2
-rw-r--r--FrontEnd/src/views/timeline-common/Timeline.tsx39
-rw-r--r--FrontEnd/src/views/timeline-common/TimelinePageTemplate.tsx38
-rw-r--r--FrontEnd/src/views/timeline-common/TimelinePagedPostListView.tsx2
-rw-r--r--FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx170
-rw-r--r--FrontEnd/src/views/timeline-common/TimelinePostListView.tsx1
-rw-r--r--FrontEnd/src/views/timeline-common/TimelinePostView.tsx12
-rw-r--r--FrontEnd/src/views/timeline-common/index.css3
10 files changed, 129 insertions, 147 deletions
diff --git a/FrontEnd/.vscode/extensions.json b/FrontEnd/.vscode/extensions.json
index 78df04fa..83c0aab4 100644
--- a/FrontEnd/.vscode/extensions.json
+++ b/FrontEnd/.vscode/extensions.json
@@ -2,7 +2,6 @@
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
- "syler.sass-indented",
"editorconfig.editorconfig"
]
}
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;
}