From 538d6830a0022b49b99695095d85e567b0c86e71 Mon Sep 17 00:00:00 2001 From: crupest Date: Sun, 30 Jul 2023 23:47:53 +0800 Subject: ... --- FrontEnd/src/pages/timeline/Timeline.css | 244 +++++++++++++++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 FrontEnd/src/pages/timeline/Timeline.css (limited to 'FrontEnd/src/pages/timeline/Timeline.css') diff --git a/FrontEnd/src/pages/timeline/Timeline.css b/FrontEnd/src/pages/timeline/Timeline.css new file mode 100644 index 00000000..4dd4fdcc --- /dev/null +++ b/FrontEnd/src/pages/timeline/Timeline.css @@ -0,0 +1,244 @@ +.timeline { + z-index: 0; + position: relative; + width: 100%; +} + +@keyframes timeline-line-node { + to { + box-shadow: 0 0 20px 3px var(--cru-primary-l1-color); + } +} + +@keyframes timeline-line-node-current { + to { + box-shadow: 0 0 20px 3px var(--cru-primary-enhance-l1-color); + } +} + +@keyframes timeline-line-node-loading { + to { + box-shadow: 0 0 20px 3px var(--cru-primary-l1-color); + } +} + +@keyframes timeline-line-node-loading-edge { + from { + transform: rotate(0turn); + } + to { + transform: rotate(1turn); + } +} + +@keyframes timeline-top-loading-enter { + from { + transform: translate(0, -100%); + } +} + +@keyframes timeline-post-enter { + from { + transform: translate(0, 100%); + opacity: 0; + } + to { + opacity: 1; + } +} + +.timeline-top-loading-enter { + animation: 1s timeline-top-loading-enter; +} + +.timeline-line { + display: flex; + flex-direction: column; + align-items: center; + width: 30px; + position: absolute; + z-index: 1; + left: 2em; + top: 0; + bottom: 0; + transition: left 0.5s; +} + +@media (max-width: 575.98px) { + .timeline-line { + left: 1em; + } +} + +.timeline-line .segment { + width: 7px; + background: var(--cru-primary-color); +} +.timeline-line .segment.start { + height: 1.8em; + flex: 0 0 auto; +} +.timeline-line .segment.end { + flex: 1 1 auto; +} +.timeline-line .segment.current-end { + height: 2em; + flex: 0 0 auto; + background: linear-gradient(var(--cru-primary-enhance-color), white); +} +.timeline-line .node-container { + flex: 0 0 auto; + position: relative; + width: 18px; + height: 18px; +} +.timeline-line .node { + width: 20px; + height: 20px; + position: absolute; + background: var(--cru-primary-color); + left: -1px; + top: -1px; + border-radius: 50%; + box-sizing: border-box; + z-index: 1; + animation: 1s infinite alternate; + animation-name: timeline-line-node; +} +.timeline-line .node-loading-edge { + color: var(--cru-primary-color); + width: 38px; + height: 38px; + position: absolute; + left: -10px; + top: -10px; + box-sizing: border-box; + z-index: 2; + animation: 1.5s linear infinite timeline-line-node-loading-edge; +} +.timeline-line.current .segment.start { + background: linear-gradient( + var(--cru-primary-color), + var(--cru-primary-enhance-color) + ); +} + +.timeline-line.current .segment.end { + background: var(--cru-primary-enhance-color); +} + +.timeline-line.current .node { + background: var(--cru-primary-enhance-color); + animation-name: timeline-line-node-current; +} + +.timeline-line.loading .node { + background: var(--cru-primary-color); + animation-name: timeline-line-node-loading; +} + +.timeline-item { + position: relative; + padding: 0.5em; +} + +.timeline-item-card { + position: relative; + padding: 0.5em 0.5em 0.5em 4em; +} + +.timeline-item-card.enter-animation { + animation: 0.6s forwards; + opacity: 0; +} + +@media (max-width: 575.98px) { + .timeline-item-card { + padding-left: 3em; + } +} + +.timeline-item-header { + display: flex; + align-items: center; +} + +.timeline-avatar { + border-radius: 50%; + width: 2em; + height: 2em; +} + +.timeline-item-delete-button { + position: absolute; + right: 0; + bottom: 0; +} + +.timeline-content { + white-space: pre-line; +} + +.timeline-content-image { + max-width: 80%; + max-height: 200px; +} + +.timeline-date-item { + position: relative; + padding: 0.3em 0 0.3em 4em; +} + +.timeline-date-item-badge { + display: inline-block; + padding: 0.1em 0.4em; + border-radius: 0.4em; + background: #7c7c7c; + color: white; + font-size: 0.8em; +} + +.timeline-post-item-options-mask { + background: rgba(255, 255, 255, 0.85); + z-index: 100; + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + + display: flex; + justify-content: space-around; + align-items: center; + + border-radius: var(--cru-card-border-radius); +} + +.timeline-sync-state-badge { + font-size: 0.8em; + padding: 3px 8px; + border-radius: 5px; + background: #e8fbff; +} + +.timeline-sync-state-badge-pin { + display: inline-block; + width: 0.4em; + height: 0.4em; + border-radius: 50%; + vertical-align: middle; + margin-right: 0.6em; +} + +.timeline-card { + position: fixed; + z-index: 1029; + top: 56px; + right: 0; + margin: 0.5em; +} + +.timeline-top { + position: sticky; + top: 56px; +} -- cgit v1.2.3 From d9c1d512fa64ef4f8c08ca34f7a5842642879bcc Mon Sep 17 00:00:00 2001 From: crupest Date: Mon, 31 Jul 2023 00:08:48 +0800 Subject: ... --- FrontEnd/src/pages/timeline/Timeline.css | 7 - FrontEnd/src/pages/timeline/Timeline.tsx | 15 +- FrontEnd/src/pages/timeline/TimelineCard.css | 18 ++ FrontEnd/src/pages/timeline/TimelineCard.tsx | 59 ++--- .../src/pages/timeline/TimelineDeleteDialog.tsx | 4 +- .../timeline/TimelinePropertyChangeDialog.tsx | 4 +- FrontEnd/src/views/timeline/CollapseButton.tsx | 21 -- .../src/views/timeline/ConnectionStatusBadge.css | 36 --- .../src/views/timeline/ConnectionStatusBadge.tsx | 41 ---- FrontEnd/src/views/timeline/MarkdownPostEdit.css | 21 -- FrontEnd/src/views/timeline/MarkdownPostEdit.tsx | 215 ----------------- .../views/timeline/PostPropertyChangeDialog.tsx | 42 ---- FrontEnd/src/views/timeline/Timeline.css | 244 ------------------- FrontEnd/src/views/timeline/Timeline.tsx | 207 ---------------- FrontEnd/src/views/timeline/TimelineCard.tsx | 167 ------------- FrontEnd/src/views/timeline/TimelineDateLabel.tsx | 19 -- .../src/views/timeline/TimelineDeleteDialog.tsx | 61 ----- FrontEnd/src/views/timeline/TimelineEmptyItem.tsx | 25 -- FrontEnd/src/views/timeline/TimelineLine.tsx | 51 ---- FrontEnd/src/views/timeline/TimelineLoading.tsx | 16 -- FrontEnd/src/views/timeline/TimelineMember.css | 8 - FrontEnd/src/views/timeline/TimelineMember.tsx | 202 ---------------- .../src/views/timeline/TimelinePostContentView.tsx | 187 --------------- FrontEnd/src/views/timeline/TimelinePostEdit.css | 10 - FrontEnd/src/views/timeline/TimelinePostEdit.tsx | 267 --------------------- .../src/views/timeline/TimelinePostEditCard.tsx | 31 --- .../src/views/timeline/TimelinePostEditNoLogin.tsx | 18 -- .../src/views/timeline/TimelinePostListView.tsx | 76 ------ FrontEnd/src/views/timeline/TimelinePostView.tsx | 159 ------------ .../timeline/TimelinePropertyChangeDialog.tsx | 82 ------- FrontEnd/src/views/timeline/index.tsx | 23 -- 31 files changed, 55 insertions(+), 2281 deletions(-) create mode 100644 FrontEnd/src/pages/timeline/TimelineCard.css delete mode 100644 FrontEnd/src/views/timeline/CollapseButton.tsx delete mode 100644 FrontEnd/src/views/timeline/ConnectionStatusBadge.css delete mode 100644 FrontEnd/src/views/timeline/ConnectionStatusBadge.tsx delete mode 100644 FrontEnd/src/views/timeline/MarkdownPostEdit.css delete mode 100644 FrontEnd/src/views/timeline/MarkdownPostEdit.tsx delete mode 100644 FrontEnd/src/views/timeline/PostPropertyChangeDialog.tsx delete mode 100644 FrontEnd/src/views/timeline/Timeline.css delete mode 100644 FrontEnd/src/views/timeline/Timeline.tsx delete mode 100644 FrontEnd/src/views/timeline/TimelineCard.tsx delete mode 100644 FrontEnd/src/views/timeline/TimelineDateLabel.tsx delete mode 100644 FrontEnd/src/views/timeline/TimelineDeleteDialog.tsx delete mode 100644 FrontEnd/src/views/timeline/TimelineEmptyItem.tsx delete mode 100644 FrontEnd/src/views/timeline/TimelineLine.tsx delete mode 100644 FrontEnd/src/views/timeline/TimelineLoading.tsx delete mode 100644 FrontEnd/src/views/timeline/TimelineMember.css delete mode 100644 FrontEnd/src/views/timeline/TimelineMember.tsx delete mode 100644 FrontEnd/src/views/timeline/TimelinePostContentView.tsx delete mode 100644 FrontEnd/src/views/timeline/TimelinePostEdit.css delete mode 100644 FrontEnd/src/views/timeline/TimelinePostEdit.tsx delete mode 100644 FrontEnd/src/views/timeline/TimelinePostEditCard.tsx delete mode 100644 FrontEnd/src/views/timeline/TimelinePostEditNoLogin.tsx delete mode 100644 FrontEnd/src/views/timeline/TimelinePostListView.tsx delete mode 100644 FrontEnd/src/views/timeline/TimelinePostView.tsx delete mode 100644 FrontEnd/src/views/timeline/TimelinePropertyChangeDialog.tsx delete mode 100644 FrontEnd/src/views/timeline/index.tsx (limited to 'FrontEnd/src/pages/timeline/Timeline.css') diff --git a/FrontEnd/src/pages/timeline/Timeline.css b/FrontEnd/src/pages/timeline/Timeline.css index 4dd4fdcc..f071f163 100644 --- a/FrontEnd/src/pages/timeline/Timeline.css +++ b/FrontEnd/src/pages/timeline/Timeline.css @@ -230,13 +230,6 @@ margin-right: 0.6em; } -.timeline-card { - position: fixed; - z-index: 1029; - top: 56px; - right: 0; - margin: 0.5em; -} .timeline-top { position: sticky; diff --git a/FrontEnd/src/pages/timeline/Timeline.tsx b/FrontEnd/src/pages/timeline/Timeline.tsx index 3a7fbd00..f93e1623 100644 --- a/FrontEnd/src/pages/timeline/Timeline.tsx +++ b/FrontEnd/src/pages/timeline/Timeline.tsx @@ -41,7 +41,7 @@ const Timeline: React.FC = (props) => { const [timeline, setTimeline] = React.useState(null); const [posts, setPosts] = React.useState(null); const [signalrState, setSignalrState] = React.useState( - HubConnectionState.Connecting + HubConnectionState.Connecting, ); const [error, setError] = React.useState< "offline" | "forbid" | "notfound" | "error" | null @@ -81,7 +81,7 @@ const Timeline: React.FC = (props) => { console.error(error); setError("error"); } - } + }, ); }, [timelineOwner, timelineName, timelineReloadKey]); @@ -91,7 +91,7 @@ const Timeline: React.FC = (props) => { .then( (page) => { setPosts( - page.items.filter((p): p is HttpTimelinePostInfo => !p.deleted) + page.items.filter((p): p is HttpTimelinePostInfo => !p.deleted), ); setTotalPage(page.totalPageCount); }, @@ -106,14 +106,14 @@ const Timeline: React.FC = (props) => { console.error(error); setError("error"); } - } + }, ); }, [timelineOwner, timelineName, postsReloadKey]); React.useEffect(() => { const timelinePostUpdate$ = getTimelinePostUpdate$( timelineOwner, - timelineName + timelineName, ); const subscription = timelinePostUpdate$.subscribe(({ update, state }) => { if (update) { @@ -134,7 +134,7 @@ const Timeline: React.FC = (props) => { .then( (page) => { const ps = page.items.filter( - (p): p is HttpTimelinePostInfo => !p.deleted + (p): p is HttpTimelinePostInfo => !p.deleted, ); setPosts((old) => [...(old ?? []), ...ps]); }, @@ -149,7 +149,7 @@ const Timeline: React.FC = (props) => { console.error(error); setError("error"); } - } + }, ); }, currentPage < totalPage); @@ -183,7 +183,6 @@ const Timeline: React.FC = (props) => { {timeline == null && posts == null && } {timeline && ( void; } -const TimelineCard: React.FC = (props) => { - const { timeline, connectionStatus, onReload, className } = props; +export default function TimelineCard(props: TimelinePageCardProps) { + const { timeline, connectionStatus, onReload } = props; - const { t } = useTranslation(); + const user = useUser(); - const [dialog, setDialog] = React.useState< - "member" | "property" | "delete" | null - >(null); + const c = useC(); - const [collapse, setCollapse] = React.useState(true); + const [collapse, setCollapse] = useState(true); const toggleCollapse = (): void => { setCollapse((o) => !o); }; const isSmallScreen = useIsSmallScreen(); - const user = useUser(); + const { createDialogSwitch, dialog, dialogPropsMap, switchDialog } = + useDialog(["member", "property", "delete"]); const content = ( - <> -

+
+

{timeline.title} - {timeline.nameV2} + {timeline.nameV2}

= (props) => {

{timeline.description}

- {t(timelineVisibilityTooltipTranslationMap[timeline.visibility])} + {c(timelineVisibilityTooltipTranslationMap[timeline.visibility])}
{user != null ? ( @@ -92,7 +93,7 @@ const TimelineCard: React.FC = (props) => { setDialog("member")} + onClick={createDialogSwitch("member")} /> {timeline.manageable ? ( = (props) => { { type: "button", text: "timeline.manageItem.property", - onClick: () => setDialog("property"), + onClick: createDialogSwitch("property"), }, { type: "divider" }, { type: "button", - onClick: () => setDialog("delete"), + onClick: createDialogSwitch("delete"), color: "danger", text: "timeline.manageItem.delete", }, @@ -116,12 +117,12 @@ const TimelineCard: React.FC = (props) => { ) : null}
- +
); return ( <> - +
= (props) => { setDialog(null)} - open={dialog === "member"} onChange={onReload} + {...dialogPropsMap["member"]} /> setDialog(null)} - open={dialog === "property"} onChange={onReload} + {...dialogPropsMap["property"]} /> - setDialog(null)} - /> + ); -}; - -export default TimelineCard; +} diff --git a/FrontEnd/src/pages/timeline/TimelineDeleteDialog.tsx b/FrontEnd/src/pages/timeline/TimelineDeleteDialog.tsx index d5b22aee..0a5a2491 100644 --- a/FrontEnd/src/pages/timeline/TimelineDeleteDialog.tsx +++ b/FrontEnd/src/pages/timeline/TimelineDeleteDialog.tsx @@ -9,7 +9,7 @@ import OperationDialog from "@/views/common/dialog/OperationDialog"; interface TimelineDeleteDialog { timeline: HttpTimelineInfo; open: boolean; - close: () => void; + onClose: () => void; } const TimelineDeleteDialog: React.FC = (props) => { @@ -20,7 +20,7 @@ const TimelineDeleteDialog: React.FC = (props) => { return ( ( diff --git a/FrontEnd/src/pages/timeline/TimelinePropertyChangeDialog.tsx b/FrontEnd/src/pages/timeline/TimelinePropertyChangeDialog.tsx index e26df3eb..b57135bb 100644 --- a/FrontEnd/src/pages/timeline/TimelinePropertyChangeDialog.tsx +++ b/FrontEnd/src/pages/timeline/TimelinePropertyChangeDialog.tsx @@ -12,7 +12,7 @@ import OperationDialog from "@/views/common/dialog/OperationDialog"; export interface TimelinePropertyChangeDialogProps { open: boolean; - close: () => void; + onClose: () => void; timeline: HttpTimelineInfo; onChange: () => void; } @@ -64,7 +64,7 @@ const TimelinePropertyChangeDialog: React.FC< }, }} open={props.open} - onClose={props.close} + onClose={props.onClose} onProcess={({ title, visibility, description }) => { const req: HttpTimelinePatchRequest = {}; if (title !== timeline.title) { diff --git a/FrontEnd/src/views/timeline/CollapseButton.tsx b/FrontEnd/src/views/timeline/CollapseButton.tsx deleted file mode 100644 index 374ccc2e..00000000 --- a/FrontEnd/src/views/timeline/CollapseButton.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import * as React from "react"; - -import IconButton from "../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/views/timeline/ConnectionStatusBadge.css b/FrontEnd/src/views/timeline/ConnectionStatusBadge.css deleted file mode 100644 index 7fe83b9b..00000000 --- a/FrontEnd/src/views/timeline/ConnectionStatusBadge.css +++ /dev/null @@ -1,36 +0,0 @@ -.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/views/timeline/ConnectionStatusBadge.tsx b/FrontEnd/src/views/timeline/ConnectionStatusBadge.tsx deleted file mode 100644 index 2b820454..00000000 --- a/FrontEnd/src/views/timeline/ConnectionStatusBadge.tsx +++ /dev/null @@ -1,41 +0,0 @@ -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/views/timeline/MarkdownPostEdit.css b/FrontEnd/src/views/timeline/MarkdownPostEdit.css deleted file mode 100644 index e36be992..00000000 --- a/FrontEnd/src/views/timeline/MarkdownPostEdit.css +++ /dev/null @@ -1,21 +0,0 @@ -.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/views/timeline/MarkdownPostEdit.tsx b/FrontEnd/src/views/timeline/MarkdownPostEdit.tsx deleted file mode 100644 index 6401cfaa..00000000 --- a/FrontEnd/src/views/timeline/MarkdownPostEdit.tsx +++ /dev/null @@ -1,215 +0,0 @@ -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 "../common/button/FlatButton"; -import TabPages from "../common/tab/TabPages"; -import ConfirmDialog from "../common/dialog/ConfirmDialog"; -import Spinner from "../common/Spinner"; -import IconButton from "../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: ( -