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/account/public-paths/public-paths.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 { Alert, Button, Checkbox, Space, Spin, Table } from "antd";
7
import { join } from "path";
8
import { FormattedMessage, useIntl } from "react-intl";
9
10
import {
11
React,
12
redux,
13
useActions,
14
useEffect,
15
useIsMountedRef,
16
useMemo,
17
useState,
18
useTypedRedux,
19
} from "@cocalc/frontend/app-framework";
20
import { A, Icon, Loading, TimeAgo } from "@cocalc/frontend/components";
21
import ShowError from "@cocalc/frontend/components/error";
22
import { Footer } from "@cocalc/frontend/customize";
23
import { appBasePath } from "@cocalc/frontend/customize/app-base-path";
24
import { labels } from "@cocalc/frontend/i18n";
25
import { ComputeImageSelector } from "@cocalc/frontend/project/settings/compute-image-selector";
26
import { LICENSES } from "@cocalc/frontend/share/licenses";
27
import { webapp_client } from "@cocalc/frontend/webapp-client";
28
import { PublicPath as PublicPath0 } from "@cocalc/util/db-schema/public-paths";
29
import { KUCALC_COCALC_COM } from "@cocalc/util/db-schema/site-defaults";
30
import { trunc, trunc_middle } from "@cocalc/util/misc";
31
import { UnpublishEverything } from "./unpublish-everything";
32
33
interface PublicPath extends PublicPath0 {
34
status?: string;
35
}
36
37
type filters = "Listed" | "Unlisted" | "Unpublished" | "Authenticated";
38
const DEFAULT_CHECKED: filters[] = ["Listed", "Unlisted", "Authenticated"];
39
40
export const PublicPaths: React.FC = () => {
41
const intl = useIntl();
42
const account_id = useTypedRedux("account", "account_id");
43
const customize_kucalc = useTypedRedux("customize", "kucalc");
44
const showAuthenticatedOption = customize_kucalc !== KUCALC_COCALC_COM;
45
const [data, set_data] = useState<PublicPath[] | undefined>(undefined);
46
const [error, setError] = useState<string>("");
47
const [loading, set_loading] = useState<boolean>(false);
48
49
const [show_listed, set_show_listed] = useState<boolean>(
50
DEFAULT_CHECKED.indexOf("Listed") != -1,
51
);
52
const [show_authenticated, set_show_authenticated] = useState<boolean>(
53
showAuthenticatedOption && DEFAULT_CHECKED.indexOf("Authenticated") != -1,
54
);
55
const [show_unlisted, set_show_unlisted] = useState<boolean>(
56
DEFAULT_CHECKED.indexOf("Unlisted") != -1,
57
);
58
const [show_unpublished, set_show_unpublished] = useState<boolean>(
59
DEFAULT_CHECKED.indexOf("Unpublished") != -1,
60
);
61
62
const isMountedRef = useIsMountedRef();
63
const project_map = useTypedRedux("projects", "project_map");
64
const actions = useActions("projects");
65
66
const paths: PublicPath[] = useMemo(() => {
67
const v: PublicPath[] = [];
68
if (data != null) {
69
for (const path of data) {
70
if (path.disabled) {
71
if (show_unpublished) {
72
path.status = "Unpublished";
73
v.push(path);
74
}
75
continue;
76
}
77
if (path.unlisted) {
78
if (show_unlisted) {
79
path.status = "Unlisted";
80
v.push(path);
81
}
82
continue;
83
}
84
if (path.authenticated) {
85
if (show_authenticated) {
86
path.status = "Authenticated";
87
v.push(path);
88
}
89
continue;
90
}
91
if (show_listed) {
92
path.status = "Listed";
93
v.push(path);
94
}
95
}
96
}
97
return v;
98
}, [data, show_listed, show_unlisted, show_unpublished, show_authenticated]);
99
100
const COLUMNS = [
101
{
102
title: "Path",
103
dataIndex: "path",
104
key: "path",
105
render: (path, record) => {
106
return (
107
<a
108
onClick={async () => {
109
await actions?.open_project({ project_id: record.project_id });
110
redux
111
.getProjectActions(record.project_id)
112
?.show_public_config(path);
113
}}
114
>
115
{trunc_middle(path, 64)}
116
</a>
117
);
118
},
119
},
120
{
121
title: "Project",
122
dataIndex: "project_id",
123
key: "project_id",
124
render: (project_id) => {
125
const project = project_map?.get(project_id);
126
if (project == null) {
127
actions?.load_all_projects();
128
return <Loading />;
129
}
130
const title = project.get("title") ?? "No Title";
131
return (
132
<a onClick={() => actions?.open_project({ project_id })}>
133
{trunc_middle(title, 64)}
134
</a>
135
);
136
},
137
},
138
{
139
title: "Description",
140
dataIndex: "description",
141
key: "description",
142
render: (description) => <span>{trunc(description, 32)}</span>,
143
},
144
{
145
title: "Last edited",
146
dataIndex: "last_edited",
147
key: "last_edited",
148
render: (date) => <TimeAgo date={date} />,
149
},
150
{
151
title: "License",
152
dataIndex: "license",
153
key: "license",
154
render: (license) => trunc_middle(LICENSES[license] ?? "None", 32),
155
},
156
{
157
title: "Counter",
158
dataIndex: "counter",
159
key: "counter",
160
},
161
{
162
title: "Status",
163
dataIndex: "status",
164
key: "status",
165
},
166
{
167
title: "Image",
168
dataIndex: "compute_image",
169
key: "image",
170
render: (_, record) => {
171
return <ComputeImage {...record} setError={setError} />;
172
},
173
},
174
];
175
176
async function fetch() {
177
set_loading(true);
178
try {
179
const data = (
180
await webapp_client.async_query({
181
query: {
182
all_public_paths: {
183
id: null,
184
project_id: null,
185
path: null,
186
description: null,
187
disabled: null,
188
unlisted: null,
189
authenticated: null,
190
license: null,
191
last_edited: null,
192
created: null,
193
last_saved: null,
194
counter: null,
195
compute_image: null,
196
},
197
},
198
})
199
).query.all_public_paths;
200
if (!isMountedRef.current) {
201
return;
202
}
203
set_loading(false);
204
set_data(data);
205
setError("");
206
} catch (err) {
207
if (!isMountedRef.current) {
208
return;
209
}
210
set_loading(false);
211
setError(err.toString());
212
}
213
}
214
215
useEffect(() => {
216
fetch();
217
}, []);
218
219
function render_checkboxes() {
220
if (loading) return;
221
const options = ["Listed", "Unlisted", "Unpublished"];
222
if (showAuthenticatedOption) {
223
options.splice(2, 0, "Authenticated");
224
}
225
return (
226
<Checkbox.Group
227
options={options}
228
defaultValue={DEFAULT_CHECKED}
229
onChange={(v) => {
230
set_show_listed(v.indexOf("Listed") != -1);
231
set_show_unlisted(v.indexOf("Unlisted") != -1);
232
set_show_unpublished(v.indexOf("Unpublished") != -1);
233
set_show_authenticated(v.indexOf("Authenticated") != -1);
234
}}
235
/>
236
);
237
}
238
239
return (
240
<div style={{ marginBottom: "64px" }}>
241
<Alert
242
showIcon
243
style={{ margin: "30px auto" }}
244
type="info"
245
banner
246
message={
247
<FormattedMessage
248
id="account.public-paths.banner"
249
defaultMessage={`This is an overview of your public files.
250
<A>Visit this page for more details...</A>`}
251
values={{
252
A: (c) => (
253
<A href={join(appBasePath, "share", "accounts", account_id)}>
254
{c}
255
</A>
256
),
257
}}
258
/>
259
}
260
/>
261
<Button onClick={fetch} disabled={loading} style={{ float: "right" }}>
262
<Space>
263
<Icon name="redo" />
264
{intl.formatMessage(loading ? labels.loading : labels.refresh)}
265
</Space>
266
</Button>
267
<h2>
268
{intl.formatMessage(labels.published_files)} ({paths?.length ?? "?"})
269
</h2>
270
<FormattedMessage
271
id="account.public-paths.info"
272
defaultMessage={
273
"Files that have been published in any project that you have actively used."
274
}
275
/>
276
<br />
277
<br />
278
{loading && <Loading />}
279
{render_checkboxes()}
280
<br />
281
<ShowError error={error} setError={setError} />
282
<br />
283
{data != null && (
284
<Table rowKey="id" columns={COLUMNS} dataSource={paths} />
285
)}
286
<UnpublishEverything data={data} refresh={fetch} />
287
<br />
288
<Footer />
289
</div>
290
);
291
};
292
293
function ComputeImage({ compute_image, project_id, path, setError }) {
294
const [selectedImage, setSelectedImage] = useState<string>(compute_image);
295
const [saving, setSaving] = useState<boolean>(false);
296
297
useEffect(() => {
298
setSelectedImage(compute_image);
299
}, [compute_image]);
300
301
return (
302
<>
303
<ComputeImageSelector
304
disabled={saving}
305
selected_image={selectedImage}
306
layout={"compact"}
307
onSelect={async (img) => {
308
setSelectedImage(img);
309
try {
310
setSaving(true);
311
await webapp_client.async_query({
312
query: { public_paths: { project_id, path, compute_image: img } },
313
});
314
} catch (err) {
315
setError(`${err}`);
316
// failed to save -- change back so clear indication
317
// it didn't work, and also so they can try again.
318
setSelectedImage(compute_image);
319
} finally {
320
setSaving(false);
321
}
322
}}
323
/>
324
{saving && (
325
<div>
326
<Spin />
327
</div>
328
)}
329
</>
330
);
331
}
332
333