Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/course/common/student-assignment-info-header.tsx
5916 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 { Button, Col, Popover, Row, Space, Typography } from "antd";
7
import { useIntl } from "react-intl";
8
import type { ReactNode } from "react";
9
10
import { Icon, Tip } from "@cocalc/frontend/components";
11
import { capitalize, unreachable } from "@cocalc/util/misc";
12
import { AssignmentCopyStep } from "../types";
13
import { course, labels } from "@cocalc/frontend/i18n";
14
import { GRADE_FLEX } from "./consts";
15
import { step_direction, step_verb } from "../util";
16
17
interface StudentAssignmentInfoHeaderProps {
18
title: "Assignment" | "Handout" | "Student";
19
peer_grade?: boolean;
20
mode?: "assignment" | "student";
21
actions?: Partial<
22
Record<AssignmentCopyStep | "grade", ReactNode | ReactNode[]>
23
>;
24
filter?: ReactNode;
25
progress?: Partial<Record<AssignmentCopyStep | "grade", ReactNode>>;
26
}
27
28
export function StudentAssignmentInfoHeader({
29
title,
30
peer_grade,
31
mode = "student",
32
actions,
33
filter,
34
progress,
35
}: StudentAssignmentInfoHeaderProps) {
36
const { Text } = Typography;
37
const intl = useIntl();
38
39
function tip_title(key: AssignmentCopyStep | "grade"): {
40
tip: string;
41
title: string;
42
} {
43
switch (key) {
44
case "assignment":
45
return {
46
title: intl.formatMessage({
47
id: "course.student-assignment-info-header.assign.label",
48
defaultMessage: "Assign",
49
description: "Student in an online course",
50
}),
51
tip: intl.formatMessage({
52
id: "course.student-assignment-info-header.assign.tooltip",
53
defaultMessage:
54
"Make an independent copy of all assignment files in each student's project",
55
description: "Student in an online course",
56
}),
57
};
58
case "collect":
59
return {
60
title: intl.formatMessage({
61
id: "course.student-assignment-info-header.collect.label",
62
defaultMessage: "Collect",
63
description: "Student in an online course",
64
}),
65
tip: intl.formatMessage({
66
id: "course.student-assignment-info-header.collect.tooltip",
67
defaultMessage:
68
"Copy current assignment files in the student's project to your project. Students still can edit their versions, but later changes will not be reflected in your copy, unless you collect again.",
69
description: "Student in an online course",
70
}),
71
};
72
case "grade":
73
return {
74
title: intl.formatMessage({
75
id: "course.student-assignment-info-header.grade.label",
76
defaultMessage: "Grade",
77
description: "For a student in an online course",
78
}),
79
tip: intl.formatMessage({
80
id: "course.student-assignment-info-header.grade.tooltip",
81
defaultMessage:
82
"Record student's grade for this assignment. It does not have to be a number.",
83
description: "For a student in an online course",
84
}),
85
};
86
case "peer_assignment":
87
return {
88
title: intl.formatMessage({
89
id: "course.student-assignment-info-header.peer_assignment.label",
90
defaultMessage: "Peer Assign",
91
description: "For a group of students in an online course",
92
}),
93
tip: intl.formatMessage({
94
id: "course.student-assignment-info-header.peer_assignment.tooltip",
95
defaultMessage:
96
"Distribute collected assignments for peer grading: each submission is copied to N randomly chosen classmates (set in Peer Grading). You must assign and collect from all students before you can peer-assign.",
97
description: "For a group of students in an online course",
98
}),
99
};
100
101
case "peer_collect":
102
return {
103
title: intl.formatMessage({
104
id: "course.student-assignment-info-header.peer_collect.label",
105
defaultMessage: "Peer Collect",
106
description: "For a group of students in an online course",
107
}),
108
tip: intl.formatMessage({
109
id: "course.student-assignment-info-header.peer_collect.tooltip",
110
defaultMessage:
111
"Collect peer-graded submissions: copy assignments with peer feedback from student projects to your project. You must peer-assign to all students before you can peer-collect.",
112
description: "For a group of students in an online course",
113
}),
114
};
115
116
case "return_graded":
117
return {
118
title: intl.formatMessage({
119
id: "course.student-assignment-info-header.return.label",
120
defaultMessage: "Return",
121
description: "For a student in an online course",
122
}),
123
tip: intl.formatMessage({
124
id: "course.student-assignment-info-header.return.tooltip",
125
defaultMessage:
126
"Copy grades, comments, and assignment files with feedback from your project to students",
127
description: "For a student in an online course",
128
}),
129
};
130
default:
131
unreachable(key);
132
}
133
throw new Error(`unknown key: ${key}`);
134
}
135
136
function render_info_content(
137
key: AssignmentCopyStep | "grade",
138
title: string,
139
tip: string,
140
) {
141
if (key === "grade") { // This step is quite different from others
142
return (
143
<Space direction="vertical" size="small" style={{ maxWidth: 360 }}>
144
<Text strong>Grade: Scores & Comments</Text>
145
<div>{tip}</div>
146
<Text strong>Actions</Text>
147
<div>
148
<Icon name="forward" /> Run{" "}
149
<a
150
href="https://doc.cocalc.com/teaching-nbgrader.html"
151
target="_blank"
152
rel="noopener noreferrer"
153
>
154
automated grading
155
</a>{" "}
156
for all students (if available)
157
</div>
158
{mode === "assignment" ? (
159
<div>
160
<Icon name="toggle-on" /> Allow proceeding without grading
161
</div>
162
) : null}
163
<div>
164
<Icon name="pencil" /> Edit grade and comments for this student
165
</div>
166
</Space>
167
);
168
}
169
170
const direction = step_direction(key);
171
const verb = capitalize(step_verb(key));
172
const you = intl.formatMessage(labels.you);
173
const students = intl.formatMessage(course.students);
174
const decoratedTitle =
175
direction === "to" ? (
176
<span>
177
{title}: <Icon name="user-secret" /> {you} <Icon name="arrow-right" />{" "}
178
<Icon name="users" /> {students}
179
</span>
180
) : (
181
<span>
182
{title}: <Icon name="users" /> {students} <Icon name="arrow-right" />{" "}
183
<Icon name="user-secret" /> {you}
184
</span>
185
);
186
const openInfo = (() => {
187
switch (key) {
188
case "assignment":
189
return "Open the student's copy in student's project";
190
case "collect":
191
return "Open this student's collected work in your project";
192
case "peer_assignment":
193
return "Open the student's peer-grading copy in their project";
194
case "peer_collect":
195
return "Open this student's collected peer-grading in your project";
196
case "return_graded":
197
return "Open the returned copy in the student's project";
198
}
199
})();
200
201
return (
202
<Space direction="vertical" size="small" style={{ maxWidth: 380 }}>
203
<Text strong>{decoratedTitle}</Text>
204
<div>{tip}</div>
205
<Text strong>Actions</Text>
206
{mode === "assignment" ? (
207
<div>
208
<Icon name="forward" /> {verb} {direction} all students
209
</div>
210
) : null}
211
{mode === "assignment" &&
212
!peer_grade &&
213
(key === "assignment" || key === "collect") ? (
214
<div>
215
<Icon name="toggle-on" /> Allow proceeding without {step_verb(key)}
216
ing
217
</div>
218
) : null}
219
<div>
220
<Icon name="caret-right" /> {verb} {direction} this student
221
</div>
222
<div>
223
<Icon name="redo" /> {verb} again {direction} this student
224
</div>
225
<div>
226
<Icon name="folder-open" /> {openInfo}
227
</div>
228
</Space>
229
);
230
}
231
232
function render_col(key: AssignmentCopyStep | "grade", flex: string) {
233
const { tip, title } = tip_title(key);
234
const actionNodes =
235
mode === "assignment" && actions != null ? actions[key] : undefined;
236
const renderedActions =
237
actionNodes == null
238
? null
239
: Array.isArray(actionNodes)
240
? actionNodes
241
: [actionNodes];
242
const progressNode =
243
mode === "assignment" && progress != null ? progress[key] : undefined;
244
return (
245
<Col flex={flex} key={key}>
246
<Space direction="vertical">
247
<Space wrap>
248
<Space.Compact>
249
<Popover
250
trigger="click"
251
placement="top"
252
content={render_info_content(key, title, tip)}
253
>
254
<Button
255
type="link"
256
size="small"
257
icon={<Icon name="info-circle" />}
258
aria-label={`Column info: ${title}`}
259
/>
260
</Popover>
261
<Text strong>{title}</Text>
262
</Space.Compact>
263
<Space>{renderedActions}</Space>
264
</Space>
265
{progressNode}
266
</Space>
267
</Col>
268
);
269
}
270
271
function render_headers() {
272
return (
273
<Row>
274
{render_col("assignment", "1")}
275
{render_col("collect", "1")}
276
{render_col("grade", GRADE_FLEX)}
277
{render_col("return_graded", "1")}
278
</Row>
279
);
280
}
281
282
function render_headers_peer() {
283
return (
284
<Row>
285
{render_col("assignment", "1")}
286
{render_col("collect", "1")}
287
{render_col("peer_assignment", "1")}
288
{render_col("peer_collect", "1")}
289
{render_col("grade", GRADE_FLEX)}
290
{render_col("return_graded", "1")}
291
</Row>
292
);
293
}
294
295
const tooltip = intl.formatMessage(
296
{
297
id: "course.student-assignment-info-header.row.tooltip",
298
defaultMessage: `{key, select,
299
Assignment {This column gives the directory name of the assignment.}
300
other {This column gives the name of the student.}}`,
301
description: "student in an online course",
302
},
303
{ key: title },
304
);
305
306
function titleIntl(): string {
307
switch (title) {
308
case "Assignment":
309
return intl.formatMessage(course.assignment);
310
case "Handout":
311
return intl.formatMessage(course.handout);
312
case "Student":
313
return intl.formatMessage(course.student);
314
default:
315
return title;
316
}
317
}
318
319
return (
320
<div>
321
<Row>
322
<Col md={4} key="title" style={{ paddingRight: 16 }}>
323
<div style={{ display: "flex", flexDirection: "column", gap: "4px" }}>
324
<Tip title={title} tip={tooltip}>
325
<Text strong>{capitalize(titleIntl())}</Text>
326
</Tip>
327
{filter}
328
</div>
329
</Col>
330
<Col md={20} key="rest">
331
{peer_grade ? render_headers_peer() : render_headers()}
332
</Col>
333
</Row>
334
</div>
335
);
336
}
337
338