Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/next/components/share/configure-public-path.tsx
1449 views
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 { useEffect, useState, type JSX } from "react";
7
import { Alert, Divider, Radio, Input, Select, Space } from "antd";
8
import useDatabase from "lib/hooks/database";
9
import useCustomize from "lib/use-customize";
10
import Loading from "./loading";
11
import { LICENSES } from "@cocalc/frontend/share/licenses";
12
import SaveButton from "components/misc/save-button";
13
import EditRow from "components/misc/edit-row";
14
import A from "components/misc/A";
15
import SelectSiteLicense from "components/misc/select-site-license";
16
import { Icon } from "@cocalc/frontend/components/icon";
17
import LaTeX from "components/landing/latex";
18
import {
19
SHARE_AUTHENTICATED_EXPLANATION,
20
SHARE_AUTHENTICATED_ICON,
21
SHARE_FLAGS,
22
} from "@cocalc/util/consts/ui";
23
24
const { Option } = Select;
25
26
interface Props {
27
id: string;
28
project_id: string;
29
path: string;
30
}
31
32
const QUERY = {
33
name: null,
34
description: null,
35
disabled: null,
36
unlisted: null,
37
authenticated: null,
38
license: null,
39
compute_image: null,
40
};
41
42
interface Info {
43
name?: string;
44
description?: string;
45
disabled?: boolean;
46
unlisted?: boolean;
47
authenticated?: boolean;
48
license?: string;
49
compute_image?: string;
50
site_license_id?: string;
51
}
52
53
function get_visibility(edited) {
54
if (edited.disabled) return "private";
55
if (edited.unlisted) return "unlisted";
56
if (edited.authenticated) return "authenticated";
57
return "listed";
58
}
59
60
export default function ConfigurePublicPath({ id, project_id, path }: Props) {
61
const publicPaths = useDatabase({
62
public_paths: { ...QUERY, id, project_id, path },
63
});
64
const siteLicense = useDatabase({
65
public_paths_site_license_id: {
66
site_license_id: null,
67
id,
68
project_id,
69
path,
70
},
71
});
72
const { onCoCalcCom } = useCustomize();
73
const [loaded, setLoaded] = useState<boolean>(false);
74
const [edited, setEdited] = useState<Info>({});
75
const [original, setOriginal] = useState<Info>({});
76
const [error, setError] = useState<string>("");
77
78
// After loading finishes, either editor or error is set.
79
useEffect(() => {
80
if (publicPaths.loading || siteLicense.loading) return;
81
if (publicPaths.error) {
82
setError(publicPaths.error);
83
return;
84
}
85
if (siteLicense.error) {
86
setError(siteLicense.error);
87
return;
88
}
89
const { site_license_id } = siteLicense.value.public_paths_site_license_id;
90
const { public_paths } = publicPaths.value;
91
const x = { ...public_paths, site_license_id };
92
setEdited(x);
93
setOriginal(x);
94
setLoaded(true);
95
}, [publicPaths.loading, siteLicense.loading]);
96
97
if (!loaded) {
98
return <Loading delay={0.2} />;
99
}
100
101
// cheap to compute, so we compute every time.
102
const visibility = get_visibility(edited);
103
// we don't show "authenticated" on cocalc.com, unless it is set to it
104
const showAuthenticated = !onCoCalcCom || edited.authenticated;
105
106
const save =
107
edited == null || original == null ? null : (
108
<SaveButton
109
edited={edited}
110
original={original}
111
setOriginal={setOriginal}
112
table="public_paths"
113
/>
114
);
115
116
return (
117
<div
118
style={{
119
width: "100%",
120
border: "1px solid #eee",
121
padding: "15px",
122
marginTop: "15px",
123
}}
124
>
125
{error && <Alert type="error" message={error} showIcon />}
126
{save}
127
<Divider>How you are sharing "{path}"</Divider>
128
<Space direction="vertical" style={{ width: "100%" }}>
129
<EditRow
130
label="Describe what you are sharing"
131
description={
132
<>
133
Use relevant keywords, inspire curiosity by providing just enough
134
information to explain what this is about, and keep your
135
description to about two lines. Use Markdown and <LaTeX />. You
136
can change this at any time.
137
</>
138
}
139
>
140
<Input.TextArea
141
style={{ width: "100%" }}
142
value={edited.description}
143
onChange={(e) =>
144
setEdited({ ...edited, description: e.target.value })
145
}
146
autoSize={{ minRows: 2, maxRows: 6 }}
147
/>
148
</EditRow>
149
<EditRow
150
label="Choose a name for a nicer URL"
151
description="An optional name can provide a much nicer and more memorable URL. You must also name your project (in project settings) and the owner of the project to get a nice URL. (WARNING: Changing this once it is set can break links, since automatic redirection is not implemented.)"
152
>
153
<Input
154
style={{ maxWidth: "100%", width: "30em" }}
155
value={edited.name}
156
onChange={(e) => setEdited({ ...edited, name: e.target.value })}
157
/>
158
</EditRow>
159
<EditRow
160
label={
161
showAuthenticated
162
? "Listed, Unlisted, Authenticated or Private?"
163
: "Listed, Unlisted or Private?"
164
}
165
description="You make files or directories public to the world, either indexed by
166
search engines (listed), only visible with the link (unlisted), or only those who are authenticated.
167
Public files are automatically copied to the public server within about 30 seconds
168
after you explicitly edit them. You can also set a site license for unlisted public shares."
169
>
170
<Space direction="vertical">
171
<Radio.Group
172
value={visibility}
173
onChange={(e) => {
174
switch (e.target.value) {
175
case "listed":
176
setEdited({ ...edited, ...SHARE_FLAGS.LISTED });
177
break;
178
case "unlisted":
179
setEdited({ ...edited, ...SHARE_FLAGS.UNLISTED });
180
break;
181
case "authenticated":
182
setEdited({ ...edited, ...SHARE_FLAGS.AUTHENTICATED });
183
break;
184
case "private":
185
setEdited({ ...edited, ...SHARE_FLAGS.DISABLED, name: "" });
186
break;
187
}
188
}}
189
>
190
<Space direction="vertical">
191
<Radio value={"listed"}>
192
<Icon name="eye" /> <em>Public (listed): </em> anybody can
193
find this via search.
194
</Radio>
195
<Radio value={"unlisted"}>
196
<Icon name="eye-slash" /> <em>Public (unlisted):</em> only
197
people with the link can view this.
198
</Radio>
199
{showAuthenticated && (
200
<Radio value={"authenticated"}>
201
<Icon name={SHARE_AUTHENTICATED_ICON} />{" "}
202
<em>Authenticated:</em> {SHARE_AUTHENTICATED_EXPLANATION}.
203
</Radio>
204
)}
205
<Radio value={"private"}>
206
<Icon name="lock" /> <em>Private:</em> only collaborators on
207
the project can view this.
208
</Radio>
209
</Space>
210
</Radio.Group>
211
</Space>
212
</EditRow>
213
<EditRow
214
label="Upgrade with a license?"
215
description={
216
<>
217
You can select a license that you manage, and anybody who edits a
218
copy of this share will have this license applied to their
219
project.
220
</>
221
}
222
>
223
<SelectSiteLicense
224
defaultLicenseId={original.site_license_id}
225
onChange={(site_license_id) => {
226
setEdited({ ...edited, site_license_id });
227
}}
228
/>
229
</EditRow>
230
<EditRow
231
label="Permission"
232
description={
233
<>
234
An optional{" "}
235
<A href="https://opensource.org/licenses">open source license</A>{" "}
236
tells people how they may use what you are sharing.
237
</>
238
}
239
>
240
<License
241
license={edited.license}
242
onChange={(license) => setEdited({ ...edited, license })}
243
/>
244
</EditRow>
245
{/*TODO Image: {edited.compute_image} */}
246
</Space>
247
{save}
248
</div>
249
);
250
}
251
252
function License({ license, onChange }) {
253
const options: JSX.Element[] = [];
254
for (const value in LICENSES) {
255
options.push(
256
<Option key={value} value={value}>
257
{LICENSES[value]}
258
</Option>,
259
);
260
}
261
return (
262
<Select
263
showSearch
264
value={license}
265
style={{ width: "100%" }}
266
placeholder="Select an open source license"
267
optionFilterProp="children"
268
onChange={onChange}
269
filterOption={(input, option) =>
270
option
271
? `${option.children}`.toLowerCase().includes(input.toLowerCase())
272
: false
273
}
274
>
275
{options}
276
</Select>
277
);
278
}
279
280