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/collaborators/current-collabs.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, Card, Popconfirm } from "antd";
7
import React from "react";
8
import { FormattedMessage, useIntl } from "react-intl";
9
import { CSS, redux, useRedux } from "@cocalc/frontend/app-framework";
10
import {
11
Gap,
12
Icon,
13
Paragraph,
14
SettingBox,
15
Title,
16
} from "@cocalc/frontend/components";
17
import { useStudentProjectFunctionality } from "@cocalc/frontend/course";
18
import { labels } from "@cocalc/frontend/i18n";
19
import { CancelText } from "@cocalc/frontend/i18n/components";
20
import { Project } from "@cocalc/frontend/project/settings/types";
21
import { COLORS } from "@cocalc/util/theme";
22
import { FIX_BORDER } from "../project/page/common";
23
import { User } from "../users";
24
25
interface Props {
26
project: Project;
27
user_map?: any;
28
mode?: "project" | "flyout";
29
}
30
31
export const CurrentCollaboratorsPanel: React.FC<Props> = (props: Props) => {
32
const { project, user_map, mode = "project" } = props;
33
const isFlyout = mode === "flyout";
34
const intl = useIntl();
35
const get_account_id = useRedux("account", "get_account_id");
36
const sort_by_activity = useRedux("projects", "sort_by_activity");
37
const student = useStudentProjectFunctionality(project.get("project_id"));
38
39
function remove_collaborator(account_id: string) {
40
const project_id = project.get("project_id");
41
redux.getActions("projects").remove_collaborator(project_id, account_id);
42
if (account_id === get_account_id()) {
43
return (redux.getActions("page") as any).close_project_tab(project_id); // TODO: better types
44
}
45
}
46
47
function user_remove_confirm_text(account_id: string) {
48
const style: CSS = { maxWidth: "300px" };
49
if (account_id === get_account_id()) {
50
return (
51
<div style={style}>
52
<FormattedMessage
53
id="collaborators.current-collabs.remove_self"
54
defaultMessage={`Are you sure you want to remove <b>yourself</b> from this project?
55
You will no longer have access to this project and cannot add yourself back.`}
56
/>
57
</div>
58
);
59
} else {
60
return (
61
<div style={style}>
62
<FormattedMessage
63
id="collaborators.current-collabs.remove_other"
64
defaultMessage={`Are you sure you want to remove {user} from this project?
65
They will no longer have access to this project.`}
66
values={{
67
user: <User account_id={account_id} user_map={user_map} />,
68
}}
69
/>
70
</div>
71
);
72
}
73
}
74
75
function user_remove_button(account_id: string, group?: string) {
76
if (student.disableCollaborators) return;
77
const text = user_remove_confirm_text(account_id);
78
const isOwner = group === "owner";
79
return (
80
<Popconfirm
81
title={text}
82
onConfirm={() => remove_collaborator(account_id)}
83
okText={"Yes, remove collaborator"}
84
cancelText={<CancelText />}
85
disabled={isOwner}
86
>
87
<Button
88
disabled={isOwner}
89
type={isFlyout ? "link" : "default"}
90
style={{
91
marginBottom: "0",
92
float: "right",
93
...(isFlyout ? { color: COLORS.ANTD_RED_WARN } : {}),
94
}}
95
>
96
<Icon name="user-times" /> {intl.formatMessage(labels.remove)} ...
97
</Button>
98
</Popconfirm>
99
);
100
}
101
102
function render_user(user: any, is_last?: boolean) {
103
const style = {
104
width: "100%",
105
flex: "1 1 auto",
106
...(!is_last ? { marginBottom: "20px" } : {}),
107
};
108
return (
109
<div key={user.account_id} style={style}>
110
<User
111
account_id={user.account_id}
112
user_map={user_map}
113
last_active={user.last_active}
114
show_avatar={true}
115
/>
116
<span>
117
<Gap />({user.group})
118
</span>
119
{user_remove_button(user.account_id, user.group)}
120
</div>
121
);
122
}
123
124
function render_users() {
125
const u = project.get("users");
126
if (u === undefined) {
127
return;
128
}
129
const users = u
130
.map((v, k) => ({ account_id: k, group: v.get("group") }))
131
.toList()
132
.toJS();
133
return sort_by_activity(users, project.get("project_id")).map((u, i) =>
134
render_user(u, i === users.length - 1),
135
);
136
}
137
138
function render_collaborators_list() {
139
const style: CSS = {
140
maxHeight: "20em",
141
overflowY: "auto",
142
overflowX: "hidden",
143
marginBottom: "0",
144
display: "flex",
145
flexDirection: "column",
146
};
147
if (isFlyout) {
148
return (
149
<div style={{ ...style, borderBottom: FIX_BORDER }}>
150
{render_users()}
151
</div>
152
);
153
} else {
154
return (
155
<Card style={{ ...style, backgroundColor: COLORS.GRAY_LLL }}>
156
{render_users()}
157
</Card>
158
);
159
}
160
}
161
162
const introText = intl.formatMessage({
163
id: "collaborators.current-collabs.intro",
164
defaultMessage:
165
"Everybody listed below can collaboratively work with you on any Jupyter Notebook, Linux Terminal or file in this project, and add or remove other collaborators.",
166
});
167
168
switch (mode) {
169
case "project":
170
return (
171
<SettingBox title="Current Collaborators" icon="user">
172
{introText}
173
<hr />
174
{render_collaborators_list()}
175
</SettingBox>
176
);
177
case "flyout":
178
return (
179
<div style={{ paddingLeft: "5px" }}>
180
<Title level={3}>
181
<Icon name="user" />{" "}
182
<FormattedMessage
183
id="collaborators.current-collabs.title"
184
defaultMessage={"Current Collaborators"}
185
description={
186
"Title of a table listing users collaborating on that project"
187
}
188
/>
189
</Title>
190
<Paragraph
191
type="secondary"
192
ellipsis={{ rows: 1, expandable: true, symbol: "more" }}
193
>
194
{introText}
195
</Paragraph>
196
{render_collaborators_list()}
197
</div>
198
);
199
}
200
};
201
202