From d2498f389d59d5d2779a51302fdf3c4c03201c59 Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Fri, 30 Aug 2024 15:56:08 +0530 Subject: [PATCH] Adds i18n; make timeouts configurable; Refactor code to improve code quality --- src/CAREUI/misc/RemainingTime.tsx | 23 ++++ src/Common/hooks/useConfig.ts | 10 ++ src/Components/CameraFeed/StillWatching.tsx | 129 +++++++++----------- src/Locale/en/Asset.json | 9 +- 4 files changed, 97 insertions(+), 74 deletions(-) create mode 100644 src/CAREUI/misc/RemainingTime.tsx diff --git a/src/CAREUI/misc/RemainingTime.tsx b/src/CAREUI/misc/RemainingTime.tsx new file mode 100644 index 00000000000..818ea42718a --- /dev/null +++ b/src/CAREUI/misc/RemainingTime.tsx @@ -0,0 +1,23 @@ +import React from "react"; + +export default function RemainingTime({ time }: { time: number }) { + const [remaining, setRemaining] = React.useState(time - new Date().getTime()); + + React.useEffect(() => { + const interval = setInterval(() => { + setRemaining(time - new Date().getTime()); + }, 1000); + + return () => { + clearInterval(interval); + }; + }, [time]); + + const seconds = remaining / 1e3; + + if (seconds < 0) { + return "0 sec."; + } + + return `${seconds.toFixed(0)}s.`; +} diff --git a/src/Common/hooks/useConfig.ts b/src/Common/hooks/useConfig.ts index 38e2336d583..92e4c31223d 100644 --- a/src/Common/hooks/useConfig.ts +++ b/src/Common/hooks/useConfig.ts @@ -1,4 +1,5 @@ import { createContext, useContext } from "react"; +import { StillWatchingConfig } from "../../Components/CameraFeed/StillWatching"; export const AppConfigContext = createContext(null); @@ -83,8 +84,17 @@ export interface IConfig { * Env to toggle peacetime and wartime shifting */ wartime_shifting: boolean; + + /** + * The interval at which the JWT access token is refreshed in milliseconds. + */ jwt_token_refresh_interval?: number; + /** + * Configurations related to the `StillWatching` component. + */ + still_watching?: StillWatchingConfig; + /* * Minimum date for a possible consultation encounter. */ diff --git a/src/Components/CameraFeed/StillWatching.tsx b/src/Components/CameraFeed/StillWatching.tsx index 5d82a17d415..e8c380611c0 100644 --- a/src/Components/CameraFeed/StillWatching.tsx +++ b/src/Components/CameraFeed/StillWatching.tsx @@ -2,41 +2,34 @@ import { useCallback, useEffect, useState } from "react"; import ConfirmDialog from "../Common/ConfirmDialog"; import ButtonV2 from "../Common/components/ButtonV2"; import CareIcon from "../../CAREUI/icons/CareIcon"; +import { useTranslation } from "react-i18next"; +import useConfig from "../../Common/hooks/useConfig"; +import RemainingTime from "../../CAREUI/misc/RemainingTime"; -/** - * Calculates the linear backoff duration with saturation after a specified number of attempts. - * - * The function multiplies the `retryCount` by a `baseDelay` to calculate the backoff duration. - * If the `retryCount` exceeds `maxRetries`, the delay saturates at `baseDelay * maxRetries`. - * - * @param {number} retryCount - The current attempt number (should be non-negative). - * @param {number} [baseDelay=300000] - The base delay in milliseconds for each retry. Defaults to 5 minutes (300,000 ms). - * @param {number} [maxRetries=3] - The number of retries after which the delay saturates. Defaults to 3. - * @returns {number} The calculated delay duration in milliseconds. - */ -const calculateLinearBackoffWithSaturation = ( - retryCount: number, - baseDelay = 3 * 60e3, - maxRetries = 3, -) => { - return baseDelay * Math.min(retryCount, maxRetries); +export type StillWatchingConfig = { + idleTimeout?: number; + promptDuration?: number; }; -type Props = { - children: React.ReactNode; -}; +const DEFAULT_CONFIG = { + idleTimeout: 3 * 60e3, + promptDuration: 30e3, +} satisfies StillWatchingConfig; -export default function StillWatching(props: Props) { - const [state, setState] = useState<"watching" | "prompted" | "timed-out">( - "watching", - ); +type State = "watching" | "prompted" | "timed-out"; + +const useStillWatching = (config: StillWatchingConfig) => { + const { idleTimeout, promptDuration } = { ...DEFAULT_CONFIG, ...config }; + + const [state, setState] = useState("watching"); const [sequence, setSequence] = useState(1); const getNextTimeout = useCallback(() => { return ( - new Date().getTime() + calculateLinearBackoffWithSaturation(sequence) + new Date().getTime() + + (idleTimeout + promptDuration) * Math.min(sequence, 3) ); - }, [sequence]); + }, [sequence, idleTimeout, promptDuration]); const [timeoutOn, setTimeoutOn] = useState(getNextTimeout); @@ -53,7 +46,7 @@ export default function StillWatching(props: Props) { clearInterval(interval); return; } - if (remainingTime < 30e3) { + if (remainingTime < promptDuration) { setState("prompted"); return; } @@ -64,65 +57,57 @@ export default function StillWatching(props: Props) { return () => { clearInterval(interval); }; - }, [timeoutOn, state]); + }, [timeoutOn, state, promptDuration]); + + return { + state, + timeoutOn, + reset: (hardReset?: boolean) => { + if (hardReset) { + setSequence((seq) => seq + 1); + return; + } + + if (state === "watching") { + setTimeoutOn(getNextTimeout()); + } + }, + }; +}; + +export default function StillWatching(props: { children: React.ReactNode }) { + const { t } = useTranslation(); + const { still_watching: config = {} } = useConfig(); + const { state, timeoutOn, reset } = useStillWatching(config); return ( -
{ - if (state === "watching") { - setTimeoutOn(getNextTimeout()); - } - }} - > +
reset()}> - Continue watching ( - s) + {t("continue_watching")} () } - onConfirm={() => setSequence((seq) => seq + 1)} - onClose={() => setSequence((seq) => seq + 1)} + onConfirm={() => reset(true)} + onClose={() => reset(true)} /> {state === "timed-out" ? ( - setSequence((seq) => seq + 1)} /> +
+ + {t("stream_stopped_due_to_inativity")} + + reset(true)}> + + {t("resume")} + +
) : ( props.children )}
); } - -const RemainingTime = (props: { timeoutOn: number }) => { - const [diff, setDiff] = useState(props.timeoutOn - new Date().getTime()); - - useEffect(() => { - const interval = setInterval(() => { - setDiff(props.timeoutOn - new Date().getTime()); - }, 1000); - - return () => { - clearInterval(interval); - }; - }, [props.timeoutOn]); - - return (diff / 1e3).toFixed(0); -}; - -const TimedOut = (props: { onResume: () => void }) => { - return ( -
- - Live feed has stopped streaming due to inactivity. - - - - Resume - -
- ); -}; diff --git a/src/Locale/en/Asset.json b/src/Locale/en/Asset.json index f24549ee0b6..182bb25a4fe 100644 --- a/src/Locale/en/Asset.json +++ b/src/Locale/en/Asset.json @@ -11,5 +11,10 @@ "update_asset_service_record": "Update Asset Service Record", "eg_details_on_functionality_service_etc": "Eg. Details on functionality, service, etc.", "updating": "Updating", - "update": "Update" -} + "update": "Update", + "are_you_still_watching": "Are you still watching?", + "stream_stop_due_to_inativity": "The live feed will stop streaming due to inactivity", + "stream_stopped_due_to_inativity": "The live feed has stopped streaming due to inactivity", + "continue_watching": "Continue watching", + "resume": "Resume" +} \ No newline at end of file