-
- {canUnpause && (
-
- )}
- {canPause && (
-
- )}
-
-
-
+ return
+
+ {canUnpause && }
+ {canPause && }
+
+
+
- );
-};
+}
-const Speed: React.FC<{ statsResponse: TorrentStats }> = ({
- statsResponse,
-}) => {
- switch (statsResponse.state) {
- case STATE_PAUSED:
- return "Paused";
- case STATE_INITIALIZING:
- return "Checking files";
- case STATE_ERROR:
- return "Error";
- }
- // Unknown state
- if (statsResponse.state != "live" || statsResponse.live === null) {
- return statsResponse.state;
- }
+const Speed: React.FC<{ statsResponse: TorrentStats }> = ({ statsResponse }) => {
+ switch (statsResponse.state) {
+ case STATE_PAUSED: return 'Paused';
+ case STATE_INITIALIZING: return 'Checking files';
+ case STATE_ERROR: return 'Error';
+ }
+ // Unknown state
+ if (statsResponse.state != 'live' || statsResponse.live === null) {
+ return statsResponse.state;
+ }
- return (
- <>
- {!statsResponse.finished && (
-
- ↓ {statsResponse.live.download_speed.human_readable}
-
- )}
-
- ↑ {statsResponse.live.upload_speed.human_readable}
- {statsResponse.live.snapshot.uploaded_bytes > 0 && (
-
- {" "}
- ({formatBytes(statsResponse.live.snapshot.uploaded_bytes)})
-
- )}
-
+ return <>
+ {!statsResponse.finished &&
+ ↓ {statsResponse.live.download_speed.human_readable}
}
+
+ ↑ {statsResponse.live.upload_speed.human_readable}
+ {statsResponse.live.snapshot.uploaded_bytes > 0 &&
+ ({formatBytes(statsResponse.live.snapshot.uploaded_bytes)})}
>
- );
-};
+}
const TorrentRow: React.FC<{
- id: number;
- detailsResponse: TorrentDetails | null;
- statsResponse: TorrentStats | null;
+ id: number,
+ detailsResponse: TorrentDetails | null,
+ statsResponse: TorrentStats | null
}> = ({ id, detailsResponse, statsResponse }) => {
- const state = statsResponse?.state ?? "";
- const error = statsResponse?.error;
- const totalBytes = statsResponse?.total_bytes ?? 1;
- const progressBytes = statsResponse?.progress_bytes ?? 0;
- const finished = statsResponse?.finished || false;
- const progressPercentage = error ? 100 : (progressBytes / totalBytes) * 100;
- const isAnimated =
- (state == STATE_INITIALIZING || state == STATE_LIVE) && !finished;
- const progressLabel = error ? "Error" : `${progressPercentage.toFixed(2)}%`;
- const progressBarVariant = error
- ? "danger"
- : finished
- ? "success"
- : state == STATE_INITIALIZING
- ? "warning"
- : "primary";
+ const state = statsResponse?.state ?? "";
+ const error = statsResponse?.error;
+ const totalBytes = statsResponse?.total_bytes ?? 1;
+ const progressBytes = statsResponse?.progress_bytes ?? 0;
+ const finished = statsResponse?.finished || false;
+ const progressPercentage = error ? 100 : (progressBytes / totalBytes) * 100;
+ const isAnimated = (state == STATE_INITIALIZING || state == STATE_LIVE) && !finished;
+ const progressLabel = error ? 'Error' : `${progressPercentage.toFixed(2)}%`;
+ const progressBarVariant = error ? 'danger' : finished ? 'success' : state == STATE_INITIALIZING ? 'warning' : 'primary';
- const formatPeersString = () => {
- let peer_stats = statsResponse?.live?.snapshot.peer_stats;
- if (!peer_stats) {
- return "";
+ const formatPeersString = () => {
+ let peer_stats = statsResponse?.live?.snapshot.peer_stats;
+ if (!peer_stats) {
+ return '';
+ }
+ return `${peer_stats.live} / ${peer_stats.seen}`;
}
- return `${peer_stats.live} / ${peer_stats.seen}`;
- };
- let classNames = [];
+ let classNames = [];
- if (error) {
- classNames.push("bg-warning");
- } else {
- if (id % 2 == 0) {
- classNames.push("bg-light");
+ if (error) {
+ classNames.push('bg-warning');
+ } else {
+ if (id % 2 == 0) {
+ classNames.push('bg-light');
+ }
}
- }
- return (
-
-
- {detailsResponse ? (
- <>
-
- {getLargestFileName(detailsResponse)}
-
- {error && (
-
- Error: {error}
-
- )}
- >
- ) : (
-
- )}
-
- {statsResponse ? (
- <>
- {`${formatBytes(totalBytes)} `}
-
-
-
-
-
-
- {getCompletionETA(statsResponse)}
-
- {formatPeersString()}
-
-
-
-
- >
- ) : (
-
-
-
- )}
-
- );
-};
+ return (
+
+
+ {detailsResponse ?
+ <>
+
+ {getLargestFileName(detailsResponse)}
+
+ {error && Error: {error}
}
+ >
+ : }
+
+ {statsResponse ?
+ <>
+ {`${formatBytes(totalBytes)} `}
+
+
+
+
+
+
+ {getCompletionETA(statsResponse)}
+ {formatPeersString()}
+
+
+
+ >
+ :
+ }
+
+
+ );
+}
const Column: React.FC<{
- label: string;
- size?: number;
- children?: any;
+ label: string,
+ size?: number,
+ children?: any
}> = ({ size, label, children }) => (
-
- {label}
- {children}
-
+
+ {label}
+ {children}
+
);
const Torrent: React.FC<{
- id: number;
- torrent: TorrentId;
+ id: number,
+ torrent: TorrentId
}> = ({ id, torrent }) => {
- const [detailsResponse, updateDetailsResponse] =
- useState(null);
- const [statsResponse, updateStatsResponse] = useState(
- null
- );
- const [forceStatsRefresh, setForceStatsRefresh] = useState(0);
- const API = useContext(APIContext);
+ const [detailsResponse, updateDetailsResponse] = useState(null);
+ const [statsResponse, updateStatsResponse] = useState(null);
+ const [forceStatsRefresh, setForceStatsRefresh] = useState(0);
+ const API = useContext(APIContext);
- const forceStatsRefreshCallback = () => {
- setForceStatsRefresh(forceStatsRefresh + 1);
- };
-
- // Update details once.
- useEffect(() => {
- if (detailsResponse === null) {
- return loopUntilSuccess(async () => {
- await API.getTorrentDetails(torrent.id).then(updateDetailsResponse);
- }, 1000);
+ const forceStatsRefreshCallback = () => {
+ setForceStatsRefresh(forceStatsRefresh + 1);
}
- }, [detailsResponse]);
- // Update stats once then forever.
- useEffect(
- () =>
- customSetInterval(async () => {
+ // Update details once.
+ useEffect(() => {
+ if (detailsResponse === null) {
+ return loopUntilSuccess(async () => {
+ await API.getTorrentDetails(torrent.id).then(updateDetailsResponse);
+ }, 1000);
+ }
+ }, [detailsResponse]);
+
+ // Update stats once then forever.
+ useEffect(() => customSetInterval((async () => {
const errorInterval = 10000;
const liveInterval = 1000;
const nonLiveInterval = 10000;
- return API.getTorrentStats(torrent.id)
- .then((stats) => {
+ return API.getTorrentStats(torrent.id).then((stats) => {
updateStatsResponse(stats);
return stats;
- })
- .then(
- (stats) => {
- if (
- stats.state == STATE_INITIALIZING ||
- stats.state == STATE_LIVE
- ) {
+ }).then((stats) => {
+ if (stats.state == STATE_INITIALIZING || stats.state == STATE_LIVE) {
return liveInterval;
- }
- return nonLiveInterval;
- },
- () => {
- return errorInterval;
}
- );
- }, 0),
- [forceStatsRefresh]
- );
+ return nonLiveInterval;
+ }, () => {
+ return errorInterval;
+ });
+ }), 0), [forceStatsRefresh]);
- return (
-
-
-
- );
-};
+ return
+
+
+}
-const TorrentsList = (props: {
- torrents: Array | null;
- loading: boolean;
-}) => {
- if (props.torrents === null && props.loading) {
- return ;
- }
- // The app either just started, or there was an error loading torrents.
- if (props.torrents === null) {
- return;
- }
+const TorrentsList = (props: { torrents: Array | null, loading: boolean }) => {
+ if (props.torrents === null && props.loading) {
+ return
+ }
+ // The app either just started, or there was an error loading torrents.
+ if (props.torrents === null) {
+ return;
+ }
- if (props.torrents.length === 0) {
- return (
-
-
No existing torrents found. Add them through buttons below.
-
- );
- }
- return (
-
- {props.torrents.map((t: TorrentId) => (
-
- ))}
-
- );
+ if (props.torrents.length === 0) {
+ return
+
No existing torrents found. Add them through buttons below.
+
;
+ }
+ return
+ {props.torrents.map((t: TorrentId) =>
+
+ )}
+
;
};
export const RqbitWebUI = (props: { title: string }) => {
- const [closeableError, setCloseableError] = useState(null);
- const [otherError, setOtherError] = useState(null);
+ const [closeableError, setCloseableError] = useState(null);
+ const [otherError, setOtherError] = useState(null);
- const [torrents, setTorrents] = useState | null>(null);
- const [torrentsLoading, setTorrentsLoading] = useState(false);
- const API = useContext(APIContext);
+ const [torrents, setTorrents] = useState | null>(null);
+ const [torrentsLoading, setTorrentsLoading] = useState(false);
+ const API = useContext(APIContext);
- const refreshTorrents = async () => {
- setTorrentsLoading(true);
- let torrents = await API.listTorrents().finally(() =>
- setTorrentsLoading(false)
- );
- setTorrents(torrents.torrents);
- };
+ const refreshTorrents = async () => {
+ setTorrentsLoading(true);
+ let torrents = await API.listTorrents().finally(() => setTorrentsLoading(false));
+ setTorrents(torrents.torrents);
+ };
- useEffect(() => {
- return customSetInterval(
- async () =>
- refreshTorrents().then(
- () => {
- setOtherError(null);
- return 5000;
- },
- (e) => {
- setOtherError({ text: "Error refreshing torrents", details: e });
- console.error(e);
- return 5000;
- }
- ),
- 0
- );
- }, []);
+ useEffect(() => {
+ return customSetInterval(async () =>
+ refreshTorrents().then(() => {
+ setOtherError(null);
+ return 5000;
+ }, (e) => {
+ setOtherError({ text: 'Error refreshing torrents', details: e });
+ console.error(e);
+ return 5000;
+ }), 0);
+ }, []);
- const context: ContextType = {
- setCloseableError,
- refreshTorrents,
- };
+ const context: ContextType = {
+ setCloseableError,
+ refreshTorrents,
+ }
- return (
-
-
-
{props.title}
-
-
-
- );
-};
+ return
+
+
{props.title}
+
+
+
+}
-const ErrorDetails = (props: {
- details: ApiErrorDetails | null | undefined;
-}) => {
- let { details } = props;
- if (!details) {
- return null;
- }
- return (
- <>
- {details.statusText && (
-
- {details.statusText}
-
- )}
- {details.text}
+const ErrorDetails = (props: { details: ApiErrorDetails | null | undefined }) => {
+ let { details } = props;
+ if (!details) {
+ return null;
+ }
+ return <>
+ {details.statusText && {details.statusText}
}
+ {details.text}
>
- );
-};
+}
-export const ErrorComponent = (props: {
- error: Error | null;
- remove?: () => void;
-}) => {
- let { error, remove } = props;
+export const ErrorComponent = (props: { error: Error | null, remove?: () => void }) => {
+ let { error, remove } = props;
- if (error == null) {
- return null;
- }
+ if (error == null) {
+ return null;
+ }
- return (
-
- {error.text}
+ return (
+ {error.text}
-
-
- );
+
+ );
};
const UploadButton: React.FC<{
- buttonText: string;
- onClick: () => void;
- data: string | File | null;
- resetData: () => void;
- variant: string;
+ buttonText: string,
+ onClick: () => void,
+ data: string | File | null,
+ resetData: () => void,
+ variant: string,
}> = ({ buttonText, onClick, data, resetData, variant }) => {
- const [loading, setLoading] = useState(false);
- const [listTorrentResponse, setListTorrentResponse] =
- useState(null);
- const [listTorrentError, setListTorrentError] = useState(null);
- const API = useContext(APIContext);
+ const [loading, setLoading] = useState(false);
+ const [listTorrentResponse, setListTorrentResponse] = useState(null);
+ const [listTorrentError, setListTorrentError] = useState(null);
+ const API = useContext(APIContext);
- // Get the torrent file list if there's data.
- useEffect(() => {
- if (data === null) {
- return;
+ // Get the torrent file list if there's data.
+ useEffect(() => {
+ if (data === null) {
+ return;
+ }
+
+ let t = setTimeout(async () => {
+ setLoading(true);
+ try {
+ const response = await API.uploadTorrent(data, { list_only: true });
+ setListTorrentResponse(response);
+ } catch (e) {
+ setListTorrentError({ text: 'Error listing torrent files', details: e as ApiErrorDetails });
+ } finally {
+ setLoading(false);
+ }
+ }, 0);
+ return () => clearTimeout(t);
+ }, [data]);
+
+ const clear = () => {
+ resetData();
+ setListTorrentError(null);
+ setListTorrentResponse(null);
+ setLoading(false);
}
- let t = setTimeout(async () => {
- setLoading(true);
- try {
- const response = await API.uploadTorrent(data, { list_only: true });
- setListTorrentResponse(response);
- } catch (e) {
- setListTorrentError({
- text: "Error listing torrent files",
- details: e as ApiErrorDetails,
- });
- } finally {
- setLoading(false);
- }
- }, 0);
- return () => clearTimeout(t);
- }, [data]);
+ return (
+ <>
+
- const clear = () => {
- resetData();
- setListTorrentError(null);
- setListTorrentResponse(null);
- setLoading(false);
- };
-
- return (
- <>
-
-
- {data && (
-
- )}
- >
- );
+ {data && }
+ >
+ );
};
const UrlPromptModal: React.FC<{
- show: boolean;
- setUrl: (_: string) => void;
- cancel: () => void;
+ show: boolean,
+ setUrl: (_: string) => void,
+ cancel: () => void,
}> = ({ show, setUrl, cancel }) => {
- let [inputValue, setInputValue] = useState("");
- return (
-
-
- Add torrent
-
-
-
- Enter magnet or HTTP(S) URL to the .torrent
- {
- setInputValue(u.target.value);
- }}
- />
-
-
-
-
-
-
-
-
- );
-};
+ let [inputValue, setInputValue] = useState('');
+ return
+
+ Add torrent
+
+
+
+ Enter magnet or HTTP(S) URL to the .torrent
+ { setInputValue(u.target.value) }} />
+
+
+
+
+
+
+
+
+}
const MagnetInput = () => {
- let [magnet, setMagnet] = useState(null);
+ let [magnet, setMagnet] = useState(null);
- let [showModal, setShowModal] = useState(false);
+ let [showModal, setShowModal] = useState(false);
- return (
- <>
- {
- setShowModal(true);
- }}
- data={magnet}
- resetData={() => setMagnet(null)}
- />
+ return (
+ <>
+ {
+ setShowModal(true);
+ }}
+ data={magnet}
+ resetData={() => setMagnet(null)}
+ />
- {
- setShowModal(false);
- setMagnet(url);
- }}
- cancel={() => {
- setShowModal(false);
- setMagnet(null);
- }}
- />
- >
- );
+ {
+ setShowModal(false);
+ setMagnet(url);
+ }}
+ cancel={() => {
+ setShowModal(false);
+ setMagnet(null);
+ }} />
+ >
+ );
};
const FileInput = () => {
- const inputRef = useRef() as RefObject;
- const [file, setFile] = useState(null);
+ const inputRef = useRef() as RefObject;
+ const [file, setFile] = useState(null);
- const onFileChange = async () => {
- if (!inputRef?.current?.files) {
- return;
+ const onFileChange = async () => {
+ if (!inputRef?.current?.files) {
+ return;
+ }
+ const file = inputRef.current.files[0];
+ setFile(file);
+ };
+
+ const reset = () => {
+ if (!inputRef?.current) {
+ return;
+ }
+ inputRef.current.value = '';
+ setFile(null);
}
- const file = inputRef.current.files[0];
- setFile(file);
- };
- const reset = () => {
- if (!inputRef?.current) {
- return;
+ const onClick = () => {
+ if (!inputRef?.current) {
+ return;
+ }
+ inputRef.current.click();
}
- inputRef.current.value = "";
- setFile(null);
- };
- const onClick = () => {
- if (!inputRef?.current) {
- return;
- }
- inputRef.current.click();
- };
-
- return (
- <>
-
-
- >
- );
+ return (
+ <>
+
+
+ >
+ );
};
const FileSelectionModal = (props: {
- onHide: () => void;
- listTorrentResponse: AddTorrentResponse | null;
- listTorrentError: Error | null;
- listTorrentLoading: boolean;
- data: string | File;
+ onHide: () => void,
+ listTorrentResponse: AddTorrentResponse | null,
+ listTorrentError: Error | null,
+ listTorrentLoading: boolean,
+ data: string | File,
}) => {
- let {
- onHide,
- listTorrentResponse,
- listTorrentError,
- listTorrentLoading,
- data,
- } = props;
+ let { onHide, listTorrentResponse, listTorrentError, listTorrentLoading, data } = props;
- const [selectedFiles, setSelectedFiles] = useState([]);
- const [uploading, setUploading] = useState(false);
- const [uploadError, setUploadError] = useState(null);
- const [unpopularTorrent, setUnpopularTorrent] = useState(false);
- const [outputFolder, setOutputFolder] = useState("");
- const ctx = useContext(AppContext);
- const API = useContext(APIContext);
+ const [selectedFiles, setSelectedFiles] = useState([]);
+ const [uploading, setUploading] = useState(false);
+ const [uploadError, setUploadError] = useState(null);
+ const [unpopularTorrent, setUnpopularTorrent] = useState(false);
+ const [outputFolder, setOutputFolder] = useState('');
+ const ctx = useContext(AppContext);
+ const API = useContext(APIContext);
- useEffect(() => {
- console.log(listTorrentResponse);
- setSelectedFiles(
- listTorrentResponse
- ? listTorrentResponse.details.files.map((_, id) => id)
- : []
- );
- setOutputFolder(listTorrentResponse?.output_folder || "");
- }, [listTorrentResponse]);
+ useEffect(() => {
+ console.log(listTorrentResponse);
+ setSelectedFiles(listTorrentResponse ? listTorrentResponse.details.files.map((_, id) => id) : []);
+ setOutputFolder(listTorrentResponse?.output_folder || '');
+ }, [listTorrentResponse]);
- const clear = () => {
- onHide();
- setSelectedFiles([]);
- setUploadError(null);
- setUploading(false);
- };
-
- const handleToggleFile = (toggledId: number) => {
- if (selectedFiles.includes(toggledId)) {
- setSelectedFiles(selectedFiles.filter((i) => i !== toggledId));
- } else {
- setSelectedFiles([...selectedFiles, toggledId]);
+ const clear = () => {
+ onHide();
+ setSelectedFiles([]);
+ setUploadError(null);
+ setUploading(false);
}
- };
- const handleUpload = async () => {
- if (!listTorrentResponse) {
- return;
- }
- setUploading(true);
- let initialPeers = listTorrentResponse.seen_peers
- ? listTorrentResponse.seen_peers.slice(0, 32)
- : null;
- let opts: AddTorrentOptions = {
- overwrite: true,
- only_files: selectedFiles,
- initial_peers: initialPeers,
- output_folder: outputFolder,
- };
- if (unpopularTorrent) {
- opts.peer_opts = {
- connect_timeout: 20,
- read_write_timeout: 60,
- };
- }
- API.uploadTorrent(data, opts)
- .then(
- () => {
- onHide();
- ctx.refreshTorrents();
- },
- (e) => {
- setUploadError({ text: "Error starting torrent", details: e });
+ const handleToggleFile = (toggledId: number) => {
+ if (selectedFiles.includes(toggledId)) {
+ setSelectedFiles(selectedFiles.filter((i) => i !== toggledId));
+ } else {
+ setSelectedFiles([...selectedFiles, toggledId]);
}
- )
- .finally(() => setUploading(false));
- };
+ };
- const getBody = () => {
- if (listTorrentLoading) {
- return ;
- } else if (listTorrentError) {
- return ;
- } else if (listTorrentResponse) {
- return (
-
- );
- }
- };
+ const handleUpload = async () => {
+ if (!listTorrentResponse) {
+ return;
+ }
+ setUploading(true);
+ let initialPeers = listTorrentResponse.seen_peers ? listTorrentResponse.seen_peers.slice(0, 32) : null;
+ let opts: AddTorrentOptions = {
+ overwrite: true,
+ only_files: selectedFiles,
+ initial_peers: initialPeers,
+ output_folder: outputFolder,
+ };
+ if (unpopularTorrent) {
+ opts.peer_opts = {
+ connect_timeout: 20,
+ read_write_timeout: 60,
+ };
+ }
+ API.uploadTorrent(data, opts).then(() => {
+ onHide();
+ ctx.refreshTorrents();
+ },
+ (e) => {
+ setUploadError({ text: 'Error starting torrent', details: e });
+ }
+ ).finally(() => setUploading(false));
+ };
- return (
-
-
- Add torrent
-
-
- {getBody()}
-
-
-
- {uploading && }
-
-
-
-
- );
+ const getBody = () => {
+ if (listTorrentLoading) {
+ return ;
+ } else if (listTorrentError) {
+ return ;
+ } else if (listTorrentResponse) {
+ return
+ }
+ };
+
+ return (
+
+
+ Add torrent
+
+
+ {getBody()}
+
+
+
+ {uploading && }
+
+
+
+
+ );
};
const Buttons = () => {
- return (
-
-
-
-
- );
+ return (
+
+
+
+
+ );
};
const RootContent = (props: {
- closeableError: ApiErrorDetails | null;
- otherError: ApiErrorDetails | null;
- torrents: Array | null;
- torrentsLoading: boolean;
+ closeableError: ApiErrorDetails | null,
+ otherError: ApiErrorDetails | null,
+ torrents: Array | null,
+ torrentsLoading: boolean
}) => {
- let ctx = useContext(AppContext);
- return (
-
- ctx.setCloseableError(null)}
- />
-
-
-
+ let ctx = useContext(AppContext);
+ return
+ ctx.setCloseableError(null)} />
+
+
+
- );
};
function formatBytes(bytes: number): string {
- if (bytes === 0) return "0 Bytes";
+ if (bytes === 0) return '0 Bytes';
- const k = 1024;
- const sizes = ["Bytes", "KB", "MB", "GB"];
+ const k = 1024;
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
- const i = Math.floor(Math.log(bytes) / Math.log(k));
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
- return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
function getLargestFileName(torrentDetails: TorrentDetails): string {
- const largestFile = torrentDetails.files
- .filter((f) => f.included)
- .reduce((prev: any, current: any) =>
- prev.length > current.length ? prev : current
+ const largestFile = torrentDetails.files.filter(
+ (f) => f.included
+ ).reduce(
+ (prev: any, current: any) => (prev.length > current.length) ? prev : current
);
- return largestFile.name;
+ return largestFile.name;
}
function getCompletionETA(stats: TorrentStats): string {
- let duration = stats?.live?.time_remaining?.duration?.secs;
- if (duration == null) {
- return "N/A";
- }
- return formatSecondsToTime(duration);
+ let duration = stats?.live?.time_remaining?.duration?.secs;
+ if (duration == null) {
+ return 'N/A';
+ }
+ return formatSecondsToTime(duration);
}
function formatSecondsToTime(seconds: number): string {
- const hours = Math.floor(seconds / 3600);
- const minutes = Math.floor((seconds % 3600) / 60);
- const remainingSeconds = seconds % 60;
+ const hours = Math.floor(seconds / 3600);
+ const minutes = Math.floor((seconds % 3600) / 60);
+ const remainingSeconds = seconds % 60;
- const formatUnit = (value: number, unit: string) =>
- value > 0 ? `${value}${unit}` : "";
+ const formatUnit = (value: number, unit: string) => (value > 0 ? `${value}${unit}` : '');
- if (hours > 0) {
- return `${formatUnit(hours, "h")} ${formatUnit(minutes, "m")}`.trim();
- } else if (minutes > 0) {
- return `${formatUnit(minutes, "m")} ${formatUnit(
- remainingSeconds,
- "s"
- )}`.trim();
- } else {
- return `${formatUnit(remainingSeconds, "s")}`.trim();
- }
+ if (hours > 0) {
+ return `${formatUnit(hours, 'h')} ${formatUnit(minutes, 'm')}`.trim();
+ } else if (minutes > 0) {
+ return `${formatUnit(minutes, 'm')} ${formatUnit(remainingSeconds, 's')}`.trim();
+ } else {
+ return `${formatUnit(remainingSeconds, 's')}`.trim();
+ }
}
// Run a function with initial interval, then run it forever with the interval that the
// callback returns.
// Returns a callback to clear it.
-export function customSetInterval(
- asyncCallback: () => Promise,
- initialInterval: number
-): () => void {
- let timeoutId: number;
- let currentInterval: number = initialInterval;
+export function customSetInterval(asyncCallback: () => Promise, initialInterval: number): () => void {
+ let timeoutId: number;
+ let currentInterval: number = initialInterval;
- const executeCallback = async () => {
- currentInterval = await asyncCallback();
- if (currentInterval === null || currentInterval === undefined) {
- throw "asyncCallback returned null or undefined";
+ const executeCallback = async () => {
+ currentInterval = await asyncCallback();
+ if (currentInterval === null || currentInterval === undefined) {
+ throw 'asyncCallback returned null or undefined';
+ }
+ scheduleNext();
}
+
+ let scheduleNext = () => {
+ timeoutId = setTimeout(executeCallback, currentInterval);
+ }
+
scheduleNext();
- };
- let scheduleNext = () => {
- timeoutId = setTimeout(executeCallback, currentInterval);
- };
-
- scheduleNext();
-
- return () => {
- clearTimeout(timeoutId);
- };
+ return () => {
+ clearTimeout(timeoutId);
+ };
}
-export function loopUntilSuccess(
- callback: () => Promise,
- interval: number
-): () => void {
- let timeoutId: number;
+export function loopUntilSuccess(callback: () => Promise, interval: number): () => void {
+ let timeoutId: number;
- const executeCallback = async () => {
- let retry = await callback().then(
- () => false,
- () => true
- );
- if (retry) {
- scheduleNext();
+ const executeCallback = async () => {
+ let retry = await callback().then(() => false, () => true);
+ if (retry) {
+ scheduleNext();
+ }
}
- };
- let scheduleNext = (overrideInterval?: number) => {
- timeoutId = setTimeout(
- executeCallback,
- overrideInterval !== undefined ? overrideInterval : interval
- );
- };
+ let scheduleNext = (overrideInterval?: number) => {
+ timeoutId = setTimeout(executeCallback, overrideInterval !== undefined ? overrideInterval : interval);
+ }
- scheduleNext(0);
+ scheduleNext(0);
- return () => clearTimeout(timeoutId);
-}
+ return () => clearTimeout(timeoutId);
+}
\ No newline at end of file