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/next/components/licenses/licensed-projects.tsx
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2022 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import { useState } from "react";
7
import { Alert, Input, Popover, Table } from "antd";
8
9
import useAPI from "lib/hooks/api";
10
import Loading from "components/share/loading";
11
import editURL from "lib/share/edit-url";
12
import A from "components/misc/A";
13
import { cmp, keys } from "@cocalc/util/misc";
14
import License from "./license";
15
import { r_join } from "@cocalc/frontend/components/r_join";
16
import { Icon } from "@cocalc/frontend/components/icon";
17
import { search_split, search_match } from "@cocalc/util/misc";
18
import Timestamp from "components/misc/timestamp";
19
import { Paragraph, Title as AntdTitle } from "components/misc";
20
21
function Title({ title, project_id }) {
22
return (
23
<span style={{ wordWrap: "break-word", wordBreak: "break-word" }}>
24
<A href={editURL({ project_id, type: "collaborator" })} external>
25
{title}
26
</A>
27
</span>
28
);
29
}
30
31
function Licenses({ site_license, project_id }) {
32
return (
33
<div style={{ wordWrap: "break-word", wordBreak: "break-word" }}>
34
{r_join(
35
keys(site_license).map((license_id) => (
36
<License
37
key={license_id}
38
license_id={license_id}
39
contrib={{ [project_id]: site_license[license_id] }}
40
/>
41
)),
42
<br />
43
)}
44
</div>
45
);
46
}
47
48
export function LastEdited({ last_edited }) {
49
return <Timestamp epoch={last_edited} />;
50
}
51
52
function IsHidden({ hidden }) {
53
if (hidden) {
54
return (
55
<div style={{ textAlign: "center" }}>
56
<Icon name="check" />
57
</div>
58
);
59
} else {
60
return null;
61
}
62
}
63
64
const columns = [
65
{
66
responsive: ["xs"] as any,
67
title: "Invoices and Receipts",
68
render: (_, project) => (
69
<div>
70
Project: <Title {...project} />
71
<div>
72
Last Edited: <LastEdited {...project} />
73
</div>
74
Licenses:
75
<div
76
style={{
77
margin: "5px 0 0 30px",
78
border: "1px solid #eee",
79
padding: "5px",
80
borderRadius: "5px",
81
}}
82
>
83
<Licenses {...project} />
84
</div>
85
</div>
86
),
87
},
88
{
89
responsive: ["sm"] as any,
90
title: "Project",
91
width: "30%",
92
render: (_, project) => <Title {...project} />,
93
sorter: { compare: (a, b) => cmp(a.title, b.title) },
94
},
95
{
96
responsive: ["sm"] as any,
97
title: (
98
<Popover
99
placement="bottom"
100
title="Licenses"
101
content={
102
<div style={{ maxWidth: "75ex" }}>
103
These licenses are all applied to the project. They may or may not
104
contribute any upgrades, depending on how the license is being used
105
across all projects.
106
</div>
107
}
108
>
109
Licenses
110
</Popover>
111
),
112
render: (_, project) => <Licenses {...project} />,
113
sorter: {
114
compare: (a, b) =>
115
cmp(`${keys(a.site_licenses)}`, `${keys(b.site_licenses)}`),
116
},
117
},
118
{
119
responsive: ["sm"] as any,
120
title: (
121
<Popover
122
placement="bottom"
123
title="Last Edited"
124
content={
125
<div style={{ maxWidth: "75ex" }}>
126
When the project was last edited.
127
</div>
128
}
129
>
130
Project Last Edited
131
</Popover>
132
),
133
render: (_, project) => <LastEdited {...project} />,
134
sorter: { compare: (a, b) => cmp(a.last_edited, b.last_edited) },
135
},
136
{
137
responsive: ["sm"] as any,
138
title: (
139
<Popover
140
placement="bottom"
141
title="Project Hidden"
142
content={
143
<div style={{ maxWidth: "75ex" }}>
144
Whether or not the project is "hidden" from you, so it doesn't
145
appear in your default list of projects. Typically all student
146
projects in a course you teach are hidden. There is a checkmark
147
below for hidden projects.
148
</div>
149
}
150
>
151
Project Hidden
152
</Popover>
153
),
154
width: "10%",
155
render: (_, project) => <IsHidden {...project} />,
156
sorter: { compare: (a, b) => cmp(a.hidden, b.hidden) },
157
},
158
];
159
160
export default function LicensedProjects() {
161
const [search, setSearch] = useState<string>("");
162
let { result, error } = useAPI("licenses/get-projects");
163
if (error) {
164
return <Alert type="error" message={error} />;
165
}
166
if (!result) {
167
return <Loading />;
168
}
169
if (search) {
170
result = doSearch(result, search);
171
}
172
return (
173
<>
174
<AntdTitle level={2}>Your Licensed Projects ({result.length})</AntdTitle>
175
<Paragraph>
176
These are the licensed projects that you are a collaborator on. You
177
might not be a manager of some of the licenses listed below. If you're
178
teaching a course, the student projects are likely hidden from your
179
normal project list, but are included below.
180
</Paragraph>
181
<Paragraph style={{ margin: "15px 0" }}>
182
<Input.Search
183
placeholder="Search..."
184
allowClear
185
onChange={(e) => setSearch(e.target.value)}
186
style={{ width: "40ex" }}
187
/>
188
</Paragraph>
189
<Table
190
columns={columns}
191
dataSource={result}
192
rowKey={"project_id"}
193
pagination={{ hideOnSinglePage: true, pageSize: 100 }}
194
/>
195
</>
196
);
197
}
198
199
function doSearch(data: object[], search: string): object[] {
200
const v = search_split(search.toLowerCase().trim());
201
const w: object[] = [];
202
for (const x of data) {
203
if (x["search"] == null) {
204
x["search"] = `${x["title"]}${JSON.stringify(
205
keys(x["site_license"])
206
)}`.toLowerCase();
207
}
208
if (search_match(x["search"], v)) {
209
w.push(x);
210
}
211
}
212
return w;
213
}
214
215