aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd/src/app/views
diff options
context:
space:
mode:
Diffstat (limited to 'FrontEnd/src/app/views')
-rw-r--r--FrontEnd/src/app/views/timeline-common/ConnectionStatusBadge.tsx39
-rw-r--r--FrontEnd/src/app/views/timeline-common/Timeline.tsx17
-rw-r--r--FrontEnd/src/app/views/timeline-common/TimelinePageCardTemplate.tsx3
-rw-r--r--FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx7
-rw-r--r--FrontEnd/src/app/views/timeline-common/timeline-common.sass29
5 files changed, 91 insertions, 4 deletions
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