CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/components/link-retry.tsx
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import { Button, Tooltip } from "antd";
7
8
import {
9
useEffect,
10
useIsMountedRef,
11
useState,
12
} from "@cocalc/frontend/app-framework";
13
import { Gap, Icon, Loading } from "@cocalc/frontend/components";
14
import { open_new_tab } from "@cocalc/frontend/misc";
15
import { retry_until_success } from "@cocalc/util/async-utils";
16
import { COLORS } from "@cocalc/util/theme";
17
18
interface Props {
19
href: string;
20
mode?: "link" | "button";
21
children?;
22
size?: "small" | undefined; // antd button size
23
loadingText?: string;
24
onClick?: () => void;
25
autoStart?: boolean;
26
maxTime?: number;
27
tooltip?: React.ReactNode;
28
}
29
30
const LinkRetry: React.FC<Props> = ({
31
href,
32
size,
33
mode = "link",
34
children,
35
onClick,
36
autoStart,
37
maxTime = 30000,
38
loadingText,
39
tooltip,
40
}: Props) => {
41
const isMountedRef = useIsMountedRef();
42
const [working, setWorking] = useState<boolean>(false);
43
const [loading, setLoading] = useState<boolean>(false);
44
const [error, setError] = useState<boolean>(false);
45
46
useEffect(() => {
47
setError(false);
48
setLoading(false);
49
setWorking(false);
50
}, [href, mode]);
51
52
function open(): void {
53
// open_new_tab takes care of blocked popups -- https://github.com/sagemathinc/cocalc/issues/2599
54
open_new_tab(href);
55
}
56
57
async function start(): Promise<void> {
58
onClick?.();
59
setLoading(true);
60
setError(false);
61
const f = async (): Promise<void> => {
62
await $.ajax({
63
url: href,
64
timeout: 3000,
65
});
66
};
67
try {
68
await retry_until_success({
69
f,
70
max_delay: 500,
71
max_time: maxTime,
72
desc: "opening link",
73
});
74
} catch (err) {
75
if (!isMountedRef.current) {
76
return;
77
}
78
setError(true);
79
setLoading(false);
80
setWorking(false);
81
return;
82
}
83
// Open even if NOT mounted! E.g., user clicks link then switches tabs.
84
open();
85
if (!isMountedRef.current) {
86
// not mounted, so don't mess with setState.
87
return;
88
}
89
setError(false);
90
setLoading(false);
91
setWorking(true);
92
}
93
94
function click(): void {
95
//console.log("click , state = ", { error, working, loading });
96
if (working) {
97
open();
98
} else if (!loading) {
99
start();
100
}
101
}
102
103
useEffect(() => {
104
if (autoStart) {
105
start();
106
}
107
}, [href]);
108
109
function renderError() {
110
if (!error) return;
111
return (
112
<span style={{ color: COLORS.ANTD_RED_WARN }}>
113
<Gap /> (failed to load)
114
</span>
115
);
116
}
117
118
switch (mode) {
119
case "button":
120
const btn = (
121
<Button onClick={click} size={size}>
122
{children}
123
{loading ? <Icon name="cocalc-ring" spin /> : renderError()}
124
</Button>
125
);
126
if (tooltip) {
127
return <Tooltip title={tooltip}>{btn}</Tooltip>;
128
} else {
129
return btn;
130
}
131
case "link":
132
const aLink = (
133
<a onClick={click} style={{ cursor: "pointer" }}>
134
{children}
135
</a>
136
);
137
const a = tooltip ? <Tooltip title={tooltip}>{aLink}</Tooltip> : aLink;
138
return (
139
<span>
140
{a}
141
{mode === "link" && loading && (
142
<span>
143
<Gap /> <Loading text={loadingText} />
144
</span>
145
)}
146
{renderError()}
147
</span>
148
);
149
default:
150
console.warn(`LinkRetry: invalid mode "${mode}"`);
151
}
152
};
153
154
export default LinkRetry;
155
156