Skip to content

Commit

Permalink
Adds i18n; make timeouts configurable; Refactor code to improve code …
Browse files Browse the repository at this point in the history
…quality
  • Loading branch information
rithviknishad committed Aug 30, 2024
1 parent fdbb0d1 commit d2498f3
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 74 deletions.
23 changes: 23 additions & 0 deletions src/CAREUI/misc/RemainingTime.tsx
Original file line number Diff line number Diff line change
@@ -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.`;
}
10 changes: 10 additions & 0 deletions src/Common/hooks/useConfig.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createContext, useContext } from "react";
import { StillWatchingConfig } from "../../Components/CameraFeed/StillWatching";

export const AppConfigContext = createContext<IConfig | null>(null);

Expand Down Expand Up @@ -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.
*/
Expand Down
129 changes: 57 additions & 72 deletions src/Components/CameraFeed/StillWatching.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<State>("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);

Expand All @@ -53,7 +46,7 @@ export default function StillWatching(props: Props) {
clearInterval(interval);
return;
}
if (remainingTime < 30e3) {
if (remainingTime < promptDuration) {
setState("prompted");
return;
}
Expand All @@ -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 (
<div
onClick={() => {
if (state === "watching") {
setTimeoutOn(getNextTimeout());
}
}}
>
<div onClick={() => reset()}>
<ConfirmDialog
show={state === "prompted"}
title="Are you still watching?"
description="The stream will stop playing due to inactivity"
title={t("are_you_still_watching")}
description={t("stream_stop_due_to_inativity")}
action={
<>
<CareIcon icon="l-play-circle" className="text-lg" />
Continue watching (<RemainingTime timeoutOn={timeoutOn} />
s)
{t("continue_watching")} (<RemainingTime time={timeoutOn} />)
</>
}
onConfirm={() => setSequence((seq) => seq + 1)}
onClose={() => setSequence((seq) => seq + 1)}
onConfirm={() => reset(true)}
onClose={() => reset(true)}
/>
{state === "timed-out" ? (
<TimedOut onResume={() => setSequence((seq) => seq + 1)} />
<div className="flex h-[50vh] w-full flex-col items-center justify-center gap-4 rounded-lg border-4 border-dashed border-secondary-400">
<span className="text-xl font-bold text-secondary-700">
{t("stream_stopped_due_to_inativity")}
</span>
<ButtonV2 onClick={() => reset(true)}>
<CareIcon icon="l-play-circle" className="text-lg" />
{t("resume")}
</ButtonV2>
</div>
) : (
props.children
)}
</div>
);
}

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 (
<div className="flex h-[50vh] w-full flex-col items-center justify-center gap-4 rounded-lg border-4 border-dashed border-secondary-400">
<span className="text-xl font-bold text-secondary-700">
Live feed has stopped streaming due to inactivity.
</span>
<ButtonV2 onClick={props.onResume}>
<CareIcon icon="l-play-circle" className="text-lg" />
Resume
</ButtonV2>
</div>
);
};
9 changes: 7 additions & 2 deletions src/Locale/en/Asset.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}

0 comments on commit d2498f3

Please sign in to comment.