From 538d6830a0022b49b99695095d85e567b0c86e71 Mon Sep 17 00:00:00 2001 From: crupest Date: Sun, 30 Jul 2023 23:47:53 +0800 Subject: ... --- FrontEnd/src/App.tsx | 14 +- FrontEnd/src/pages/home/index.css | 7 + FrontEnd/src/pages/home/index.tsx | 7 + FrontEnd/src/pages/loading/index.css | 7 + FrontEnd/src/pages/loading/index.tsx | 11 + FrontEnd/src/pages/timeline/CollapseButton.tsx | 21 ++ .../src/pages/timeline/ConnectionStatusBadge.css | 36 +++ .../src/pages/timeline/ConnectionStatusBadge.tsx | 41 ++++ FrontEnd/src/pages/timeline/MarkdownPostEdit.css | 21 ++ FrontEnd/src/pages/timeline/MarkdownPostEdit.tsx | 215 +++++++++++++++++ FrontEnd/src/pages/timeline/Timeline.css | 244 +++++++++++++++++++ FrontEnd/src/pages/timeline/Timeline.tsx | 207 ++++++++++++++++ FrontEnd/src/pages/timeline/TimelineCard.tsx | 167 +++++++++++++ FrontEnd/src/pages/timeline/TimelineDateLabel.tsx | 19 ++ .../src/pages/timeline/TimelineDeleteDialog.tsx | 61 +++++ FrontEnd/src/pages/timeline/TimelineEmptyItem.tsx | 25 ++ FrontEnd/src/pages/timeline/TimelineLine.tsx | 51 ++++ FrontEnd/src/pages/timeline/TimelineLoading.tsx | 16 ++ FrontEnd/src/pages/timeline/TimelineMember.css | 8 + FrontEnd/src/pages/timeline/TimelineMember.tsx | 202 ++++++++++++++++ .../src/pages/timeline/TimelinePostContentView.tsx | 187 +++++++++++++++ FrontEnd/src/pages/timeline/TimelinePostEdit.css | 10 + FrontEnd/src/pages/timeline/TimelinePostEdit.tsx | 267 +++++++++++++++++++++ .../src/pages/timeline/TimelinePostEditCard.tsx | 31 +++ .../src/pages/timeline/TimelinePostEditNoLogin.tsx | 18 ++ .../src/pages/timeline/TimelinePostListView.tsx | 76 ++++++ FrontEnd/src/pages/timeline/TimelinePostView.tsx | 149 ++++++++++++ .../timeline/TimelinePropertyChangeDialog.tsx | 87 +++++++ FrontEnd/src/pages/timeline/index.tsx | 23 ++ .../src/views/common/dialog/OperationDialog.tsx | 6 +- FrontEnd/src/views/common/input/InputGroup.tsx | 47 +++- 31 files changed, 2257 insertions(+), 24 deletions(-) create mode 100644 FrontEnd/src/pages/home/index.css create mode 100644 FrontEnd/src/pages/home/index.tsx create mode 100644 FrontEnd/src/pages/loading/index.css create mode 100644 FrontEnd/src/pages/loading/index.tsx create mode 100644 FrontEnd/src/pages/timeline/CollapseButton.tsx create mode 100644 FrontEnd/src/pages/timeline/ConnectionStatusBadge.css create mode 100644 FrontEnd/src/pages/timeline/ConnectionStatusBadge.tsx create mode 100644 FrontEnd/src/pages/timeline/MarkdownPostEdit.css create mode 100644 FrontEnd/src/pages/timeline/MarkdownPostEdit.tsx create mode 100644 FrontEnd/src/pages/timeline/Timeline.css create mode 100644 FrontEnd/src/pages/timeline/Timeline.tsx create mode 100644 FrontEnd/src/pages/timeline/TimelineCard.tsx create mode 100644 FrontEnd/src/pages/timeline/TimelineDateLabel.tsx create mode 100644 FrontEnd/src/pages/timeline/TimelineDeleteDialog.tsx create mode 100644 FrontEnd/src/pages/timeline/TimelineEmptyItem.tsx create mode 100644 FrontEnd/src/pages/timeline/TimelineLine.tsx create mode 100644 FrontEnd/src/pages/timeline/TimelineLoading.tsx create mode 100644 FrontEnd/src/pages/timeline/TimelineMember.css create mode 100644 FrontEnd/src/pages/timeline/TimelineMember.tsx create mode 100644 FrontEnd/src/pages/timeline/TimelinePostContentView.tsx create mode 100644 FrontEnd/src/pages/timeline/TimelinePostEdit.css create mode 100644 FrontEnd/src/pages/timeline/TimelinePostEdit.tsx create mode 100644 FrontEnd/src/pages/timeline/TimelinePostEditCard.tsx create mode 100644 FrontEnd/src/pages/timeline/TimelinePostEditNoLogin.tsx create mode 100644 FrontEnd/src/pages/timeline/TimelinePostListView.tsx create mode 100644 FrontEnd/src/pages/timeline/TimelinePostView.tsx create mode 100644 FrontEnd/src/pages/timeline/TimelinePropertyChangeDialog.tsx create mode 100644 FrontEnd/src/pages/timeline/index.tsx (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/App.tsx b/FrontEnd/src/App.tsx index f638f5e8..ca3e4d38 100644 --- a/FrontEnd/src/App.tsx +++ b/FrontEnd/src/App.tsx @@ -1,27 +1,26 @@ -import * as React from "react"; +import { Suspense } from "react"; import { BrowserRouter, Route, Routes } from "react-router-dom"; import AppBar from "./views/common/AppBar"; import NotFoundPage from "./pages/404"; -import LoadingPage from "./views/common/LoadingPage"; +import HomePage from "./pages/home"; import AboutPage from "./pages/about"; import SettingPage from "./pages/setting"; -import Center from "./views/center"; import LoginPage from "./pages/login"; import RegisterPage from "./pages/register"; -import TimelinePage from "./views/timeline"; +import TimelinePage from "./pages/timeline"; +import LoadingPage from "./pages/loading"; import Search from "./views/search"; import Admin from "./views/admin"; import AlertHost from "./views/common/alert/AlertHost"; export default function App() { return ( - }> + }>
- } /> } /> } /> } /> @@ -30,10 +29,11 @@ export default function App() { } /> } /> } /> + } /> } /> - + ); } diff --git a/FrontEnd/src/pages/home/index.css b/FrontEnd/src/pages/home/index.css new file mode 100644 index 00000000..bc72a182 --- /dev/null +++ b/FrontEnd/src/pages/home/index.css @@ -0,0 +1,7 @@ +.home-page { + width: 100%; + text-align: center; + padding-top: 1em; + font-size: 2em; + color: var(--cru-primary-color); +} \ No newline at end of file diff --git a/FrontEnd/src/pages/home/index.tsx b/FrontEnd/src/pages/home/index.tsx new file mode 100644 index 00000000..76a3d18c --- /dev/null +++ b/FrontEnd/src/pages/home/index.tsx @@ -0,0 +1,7 @@ +import "./index.css"; + +export default function HomePage() { + return ( +
Be patient! I'm working on this...
+ ); +} diff --git a/FrontEnd/src/pages/loading/index.css b/FrontEnd/src/pages/loading/index.css new file mode 100644 index 00000000..08e43c22 --- /dev/null +++ b/FrontEnd/src/pages/loading/index.css @@ -0,0 +1,7 @@ +.loading-page { + width: 100%; + text-align: center; + padding-top: 1em; + font-size: 2em; + color: var(--cru-primary-color); +} \ No newline at end of file diff --git a/FrontEnd/src/pages/loading/index.tsx b/FrontEnd/src/pages/loading/index.tsx new file mode 100644 index 00000000..e4c8edab --- /dev/null +++ b/FrontEnd/src/pages/loading/index.tsx @@ -0,0 +1,11 @@ +import Spinner from "@/views/common/Spinner"; + +import "./index.css"; + +export default function LoadingPage() { + return ( +
+ +
+ ); +} diff --git a/FrontEnd/src/pages/timeline/CollapseButton.tsx b/FrontEnd/src/pages/timeline/CollapseButton.tsx new file mode 100644 index 00000000..8270e160 --- /dev/null +++ b/FrontEnd/src/pages/timeline/CollapseButton.tsx @@ -0,0 +1,21 @@ +import * as React from "react"; + +import IconButton from "@/views/common/button/IconButton"; + +const CollapseButton: React.FC<{ + collapse: boolean; + onClick: () => void; + className?: string; + style?: React.CSSProperties; +}> = ({ collapse, onClick, className, style }) => { + return ( + + ); +}; + +export default CollapseButton; diff --git a/FrontEnd/src/pages/timeline/ConnectionStatusBadge.css b/FrontEnd/src/pages/timeline/ConnectionStatusBadge.css new file mode 100644 index 00000000..7fe83b9b --- /dev/null +++ b/FrontEnd/src/pages/timeline/ConnectionStatusBadge.css @@ -0,0 +1,36 @@ +.connection-status-badge { + font-size: 0.8em; + border-radius: 5px; + padding: 0.1em 1em; + background-color: #eaf2ff; +} +.connection-status-badge::before { + width: 10px; + height: 10px; + border-radius: 50%; + display: inline-block; + content: ""; + margin-right: 0.6em; +} +.connection-status-badge.success { + color: #006100; +} +.connection-status-badge.success::before { + background-color: #006100; +} + +.connection-status-badge.warning { + color: #e4a700; +} + +.connection-status-badge.warning::before { + background-color: #e4a700; +} + +.connection-status-badge.danger { + color: #fd1616; +} + +.connection-status-badge.danger::before { + background-color: #fd1616; +} diff --git a/FrontEnd/src/pages/timeline/ConnectionStatusBadge.tsx b/FrontEnd/src/pages/timeline/ConnectionStatusBadge.tsx new file mode 100644 index 00000000..2b820454 --- /dev/null +++ b/FrontEnd/src/pages/timeline/ConnectionStatusBadge.tsx @@ -0,0 +1,41 @@ +import * as React from "react"; +import classnames from "classnames"; +import { HubConnectionState } from "@microsoft/signalr"; +import { useTranslation } from "react-i18next"; + +import "./ConnectionStatusBadge.css"; + +export interface ConnectionStatusBadgeProps { + status: HubConnectionState; + className?: string; + style?: React.CSSProperties; +} + +const classNameMap: Record = { + Connected: "success", + Connecting: "warning", + Disconnected: "danger", + Disconnecting: "warning", + Reconnecting: "warning", +}; + +const ConnectionStatusBadge: React.FC = (props) => { + const { status, className, style } = props; + + const { t } = useTranslation(); + + return ( +
+ {t(`connectionState.${status}`)} +
+ ); +}; + +export default ConnectionStatusBadge; diff --git a/FrontEnd/src/pages/timeline/MarkdownPostEdit.css b/FrontEnd/src/pages/timeline/MarkdownPostEdit.css new file mode 100644 index 00000000..e36be992 --- /dev/null +++ b/FrontEnd/src/pages/timeline/MarkdownPostEdit.css @@ -0,0 +1,21 @@ +.timeline-markdown-post-edit-page { + overflow: auto; + max-height: 300px; +} + +.timeline-markdown-post-edit-image-container { + position: relative; + text-align: center; + margin-bottom: 1em; +} + +.timeline-markdown-post-edit-image { + max-width: 100%; + max-height: 200px; +} + +.timeline-markdown-post-edit-image-delete-button { + position: absolute; + right: 10px; + top: 2px; +} diff --git a/FrontEnd/src/pages/timeline/MarkdownPostEdit.tsx b/FrontEnd/src/pages/timeline/MarkdownPostEdit.tsx new file mode 100644 index 00000000..9c497108 --- /dev/null +++ b/FrontEnd/src/pages/timeline/MarkdownPostEdit.tsx @@ -0,0 +1,215 @@ +import * as React from "react"; +import classnames from "classnames"; +import { useTranslation } from "react-i18next"; + +import { getHttpTimelineClient, HttpTimelinePostInfo } from "@/http/timeline"; + +import TimelinePostBuilder from "@/services/TimelinePostBuilder"; + +import FlatButton from "@/views/common/button/FlatButton"; +import TabPages from "@/views/common/tab/TabPages"; +import ConfirmDialog from "@/views/common/dialog/ConfirmDialog"; +import Spinner from "@/views/common/Spinner"; +import IconButton from "@/views/common/button/IconButton"; + +import "./MarkdownPostEdit.css"; + +export interface MarkdownPostEditProps { + owner: string; + timeline: string; + onPosted: (post: HttpTimelinePostInfo) => void; + onPostError: () => void; + onClose: () => void; + className?: string; + style?: React.CSSProperties; +} + +const MarkdownPostEdit: React.FC = ({ + owner: ownerUsername, + timeline: timelineName, + onPosted, + onClose, + onPostError, + className, + style, +}) => { + const { t } = useTranslation(); + + const [canLeave, setCanLeave] = React.useState(true); + + const [process, setProcess] = React.useState(false); + + const [showLeaveConfirmDialog, setShowLeaveConfirmDialog] = + React.useState(false); + + const [text, _setText] = React.useState(""); + const [images, _setImages] = React.useState<{ file: File; url: string }[]>( + [] + ); + const [previewHtml, _setPreviewHtml] = React.useState(""); + + const _builder = React.useRef(null); + + const getBuilder = (): TimelinePostBuilder => { + if (_builder.current == null) { + const builder = new TimelinePostBuilder(() => { + setCanLeave(builder.isEmpty); + _setText(builder.text); + _setImages(builder.images); + _setPreviewHtml(builder.renderHtml()); + }); + _builder.current = builder; + } + return _builder.current; + }; + + const canSend = text.length > 0; + + React.useEffect(() => { + return () => { + getBuilder().dispose(); + }; + }, []); + + React.useEffect(() => { + window.onbeforeunload = (): unknown => { + if (!canLeave) { + return t("timeline.confirmLeave"); + } + }; + + return () => { + window.onbeforeunload = null; + }; + }, [canLeave, t]); + + const send = async (): Promise => { + setProcess(true); + try { + const dataList = await getBuilder().build(); + const post = await getHttpTimelineClient().postPost( + ownerUsername, + timelineName, + { + dataList, + } + ); + onPosted(post); + onClose(); + } catch (e) { + setProcess(false); + onPostError(); + } + }; + + return ( + <> + + ) : ( +
+ { + if (canLeave) { + onClose(); + } else { + setShowLeaveConfirmDialog(true); + } + }} + /> + {canSend && ( + void send()} /> + )} +
+ ) + } + pages={[ + { + name: "text", + text: "edit", + page: ( +