diff options
author | crupest <crupest@outlook.com> | 2021-05-17 21:41:19 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2021-05-17 21:41:19 +0800 |
commit | a3f8daad1706697c6106045f271b74fd241ddcb3 (patch) | |
tree | a88666ef885f9a694ceb1b657b56f82d6b0d0481 | |
parent | c7528bfdfa920f1e2e5de2876c4bb7691419d7d6 (diff) | |
download | timeline-a3f8daad1706697c6106045f271b74fd241ddcb3.tar.gz timeline-a3f8daad1706697c6106045f271b74fd241ddcb3.tar.bz2 timeline-a3f8daad1706697c6106045f271b74fd241ddcb3.zip |
feat: Connection badge.
7 files changed, 105 insertions, 4 deletions
diff --git a/FrontEnd/src/app/locales/en/translation.json b/FrontEnd/src/app/locales/en/translation.json index 1261b086..49268961 100644 --- a/FrontEnd/src/app/locales/en/translation.json +++ b/FrontEnd/src/app/locales/en/translation.json @@ -10,6 +10,13 @@ "network": "Network error.", "unknown": "Unknown error." }, + "connectionState": { + "Connected": "Connected", + "Connecting": "Connecting", + "Disconnected": "Disconnected", + "Disconnecting": "Disconnecting", + "Reconnecting": "Reconnecting" + }, "serviceWorker": { "availableOffline": "Timeline is now cached in your computer and you can use it offline. 🎉🎉🎉", "upgradePrompt": "App is getting a new version!", diff --git a/FrontEnd/src/app/locales/zh/translation.json b/FrontEnd/src/app/locales/zh/translation.json index b2c651f6..728c3b81 100644 --- a/FrontEnd/src/app/locales/zh/translation.json +++ b/FrontEnd/src/app/locales/zh/translation.json @@ -10,6 +10,13 @@ "network": "网络错误。", "unknown": "未知错误。" }, + "connectionState": { + "Connected": "已连接", + "Connecting": "正在连接", + "Disconnected": "已断开连接", + "Disconnecting": "正在断开连接", + "Reconnecting": "正在重新连接" + }, "serviceWorker": { "availableOffline": "Timeline 已经缓存在本地,你可以离线使用它。🎉🎉🎉", "upgradePrompt": "App 有新版本!", diff --git a/FrontEnd/src/app/views/timeline-common/ConnectionStatusBadge.tsx b/FrontEnd/src/app/views/timeline-common/ConnectionStatusBadge.tsx new file mode 100644 index 00000000..df43d8d2 --- /dev/null +++ b/FrontEnd/src/app/views/timeline-common/ConnectionStatusBadge.tsx @@ -0,0 +1,39 @@ +import React from "react"; +import classnames from "classnames"; +import { HubConnectionState } from "@microsoft/signalr"; +import { useTranslation } from "react-i18next"; + +export interface ConnectionStatusBadgeProps { + status: HubConnectionState; + className?: string; + style?: React.CSSProperties; +} + +const classNameMap: Record<HubConnectionState, string> = { + Connected: "success", + Connecting: "warning", + Disconnected: "danger", + Disconnecting: "warning", + Reconnecting: "warning", +}; + +const ConnectionStatusBadge: React.FC<ConnectionStatusBadgeProps> = (props) => { + const { status, className, style } = props; + + const { t } = useTranslation(); + + return ( + <div + className={classnames( + "connection-status-badge", + classNameMap[status], + className + )} + style={style} + > + {t(`connectionState.${status}`)} + </div> + ); +}; + +export default ConnectionStatusBadge; diff --git a/FrontEnd/src/app/views/timeline-common/Timeline.tsx b/FrontEnd/src/app/views/timeline-common/Timeline.tsx index 65378563..7b56d129 100644 --- a/FrontEnd/src/app/views/timeline-common/Timeline.tsx +++ b/FrontEnd/src/app/views/timeline-common/Timeline.tsx @@ -24,7 +24,7 @@ export interface TimelineProps { } const Timeline: React.FC<TimelineProps> = (props) => { - const { timelineName, className, style, reloadKey, onReload } = props; + const { timelineName, className, style, reloadKey } = props; const [state, setState] = React.useState< @@ -37,6 +37,12 @@ const Timeline: React.FC<TimelineProps> = (props) => { setPosts([]); }, [timelineName]); + const onReload = React.useRef<() => void>(props.onReload); + + React.useEffect(() => { + onReload.current = props.onReload; + }, [props.onReload]); + const onConnectionStateChanged = React.useRef<((state: HubConnectionState) => void) | null>(null); @@ -50,7 +56,7 @@ const Timeline: React.FC<TimelineProps> = (props) => { const subscription = timelinePostUpdate$.subscribe( ({ update, state }) => { if (update) { - onReload(); + onReload.current(); } onConnectionStateChanged.current?.(state); } @@ -59,7 +65,7 @@ const Timeline: React.FC<TimelineProps> = (props) => { subscription.unsubscribe(); }; } - }, [timelineName, state, onReload, onConnectionStateChanged]); + }, [timelineName, state]); React.useEffect(() => { if (timelineName != null) { @@ -125,7 +131,10 @@ const Timeline: React.FC<TimelineProps> = (props) => { return ( <> <TimelineTop height={40} /> - <TimelinePagedPostListView posts={posts} onReload={onReload} /> + <TimelinePagedPostListView + posts={posts} + onReload={onReload.current} + /> </> ); } diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePageCardTemplate.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePageCardTemplate.tsx index 6adde8d4..623d643f 100644 --- a/FrontEnd/src/app/views/timeline-common/TimelinePageCardTemplate.tsx +++ b/FrontEnd/src/app/views/timeline-common/TimelinePageCardTemplate.tsx @@ -16,6 +16,7 @@ import { TimelinePageCardProps } from "./TimelinePageTemplate"; import CollapseButton from "./CollapseButton"; import { TimelineMemberDialog } from "./TimelineMember"; import TimelinePropertyChangeDialog from "./TimelinePropertyChangeDialog"; +import ConnectionStatusBadge from "./ConnectionStatusBadge"; import { MenuItems, PopupMenu } from "../common/Menu"; import FullPage from "../common/FullPage"; @@ -32,6 +33,7 @@ const TimelinePageCardTemplate: React.FC<TimelineCardTemplateProps> = ({ toggleCollapse, infoArea, manageItems, + connectionStatus, onReload, className, dialog, @@ -113,6 +115,7 @@ const TimelinePageCardTemplate: React.FC<TimelineCardTemplateProps> = ({ style={{ zIndex: collapse ? 1029 : 1031 }} > <div className="float-end d-flex align-items-center"> + <ConnectionStatusBadge status={connectionStatus} className="me-2" /> <CollapseButton collapse={collapse} onClick={toggleCollapse} /> </div> {isSmallScreen ? ( diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx index 6e9eba25..d3bbc0bb 100644 --- a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx +++ b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx @@ -1,6 +1,7 @@ import React from "react"; import { useTranslation } from "react-i18next"; import { Container } from "react-bootstrap"; +import { HubConnectionState } from "@microsoft/signalr"; import { HttpNetworkError, HttpNotFoundError } from "@/http/common"; import { getHttpTimelineClient, HttpTimelineInfo } from "@/http/timeline"; @@ -17,6 +18,7 @@ export interface TimelinePageCardProps { timeline: HttpTimelineInfo; collapse: boolean; toggleCollapse: () => void; + connectionStatus: HubConnectionState; className?: string; onReload: () => void; } @@ -40,6 +42,9 @@ const TimelinePageTemplate: React.FC<TimelinePageTemplateProps> = (props) => { ); const [timeline, setTimeline] = React.useState<HttpTimelineInfo | null>(null); + const [connectionStatus, setConnectionStatus] = + React.useState<HubConnectionState>(HubConnectionState.Connecting); + useReverseScrollPositionRemember(); React.useEffect(() => { @@ -135,6 +140,7 @@ const TimelinePageTemplate: React.FC<TimelinePageTemplateProps> = (props) => { collapse={cardCollapse} toggleCollapse={toggleCardCollapse} onReload={onReload} + connectionStatus={connectionStatus} /> ) : null} <Container @@ -158,6 +164,7 @@ const TimelinePageTemplate: React.FC<TimelinePageTemplateProps> = (props) => { timelineName={timeline?.name} reloadKey={timelineReloadKey} onReload={reloadTimeline} + onConnectionStateChanged={setConnectionStatus} /> ); } diff --git a/FrontEnd/src/app/views/timeline-common/timeline-common.sass b/FrontEnd/src/app/views/timeline-common/timeline-common.sass index 0b0bd24d..4400fead 100644 --- a/FrontEnd/src/app/views/timeline-common/timeline-common.sass +++ b/FrontEnd/src/app/views/timeline-common/timeline-common.sass @@ -228,3 +228,32 @@ $timeline-line-color-current: var(--tl-primary-enhance-color) position: absolute right: 10px top: 2px + +.connection-status-badge + font-size: 0.8em + border-radius: 5px + padding: 0.1em 1em + background-color: rgb(234 242 255) + + &::before + width: 10px + height: 10px + border-radius: 50% + display: inline-block + content: '' + margin-right: 0.6em + + &.success + color: #006100 + &::before + background-color: #006100 + + &.warning + color: #e4a700 + &::before + background-color: #e4a700 + + &.danger + color: #fd1616 + &::before + background-color: #fd1616 |