Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/course/common/course-unit-controls.tsx
10799 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 { Alert, Button, Col, Popconfirm, Row, Space } from "antd";
7
import { MouseEvent, ReactNode } from "react";
8
import { useIntl } from "react-intl";
9
10
import { DateTimePicker, Icon, Tip } from "@cocalc/frontend/components";
11
import { trunc_middle } from "@cocalc/util/misc";
12
13
import { CourseActions } from "../actions";
14
import { ConfigurePeerGrading } from "../assignments/configure-peer";
15
import { ComputeServerButton } from "../compute";
16
import type { AssignmentRecord, HandoutRecord, Unit } from "../store";
17
import { useButtonSize } from "../util";
18
import {
19
deleteLabel,
20
dueDateMessages,
21
exportCollectedMessages,
22
fileActivityMessages,
23
deleteConfirmMessages,
24
openFolderMessages,
25
peerGradingMessages,
26
undeleteMessages,
27
} from "./course-unit-strings";
28
import type { UnitLabel } from "./course-unit-strings";
29
import { isAssignmentUnit } from "./course-unit-types";
30
31
interface CourseUnitControlsCommonProps {
32
actions: CourseActions;
33
onOpenUnitPath: (e?: MouseEvent<HTMLElement>) => void;
34
}
35
36
interface CourseUnitControlsAssignmentProps
37
extends CourseUnitControlsCommonProps {
38
unit: AssignmentRecord;
39
expandPeerConfig?: boolean;
40
showPeerDisabledAlert: boolean;
41
setShowPeerDisabledAlert: (value: boolean) => void;
42
}
43
44
interface CourseUnitControlsHandoutProps extends CourseUnitControlsCommonProps {
45
unit: HandoutRecord;
46
}
47
48
type CourseUnitControlsProps =
49
| CourseUnitControlsAssignmentProps
50
| CourseUnitControlsHandoutProps;
51
52
export function CourseUnitControls(props: CourseUnitControlsProps) {
53
const intl = useIntl();
54
const size = useButtonSize();
55
56
const { actions, onOpenUnitPath } = props;
57
let renderDue: () => ReactNode = () => null;
58
let renderPeerButton: () => ReactNode = () => null;
59
let renderPeerExtras: () => ReactNode = () => null;
60
let renderExportAssignment: () => ReactNode = () => null;
61
let unitLabel: UnitLabel;
62
let unitId: string;
63
let unitPath: string;
64
let unitDeleted: boolean;
65
let deleteUnit: () => void;
66
let undeleteUnit: () => void;
67
68
function renderOpenButton() {
69
const { label, title, tip } = openFolderMessages(intl, unitLabel);
70
return (
71
<Tip title={title} tip={tip}>
72
<Button onClick={onOpenUnitPath} icon={<Icon name="folder-open" />}>
73
{label}
74
</Button>
75
</Tip>
76
);
77
}
78
79
function renderExportFileUseTimes() {
80
const { label, title, tip } = fileActivityMessages(intl, unitLabel);
81
return (
82
<Tip title={title} tip={tip}>
83
<Button
84
onClick={() => actions.export.file_use_times(unitId)}
85
icon={<Icon name="clock" />}
86
>
87
{label}
88
</Button>
89
</Tip>
90
);
91
}
92
93
function renderDeleteButton() {
94
if (unitDeleted) {
95
const { label, title, tip } = undeleteMessages(intl, unitLabel);
96
return (
97
<Tip placement="left" title={title} tip={tip}>
98
<Button onClick={undeleteUnit} icon={<Icon name="undo" />}>
99
{label}
100
</Button>
101
</Tip>
102
);
103
} else {
104
const { title, body } = deleteConfirmMessages(
105
intl,
106
unitLabel,
107
trunc_middle(unitPath, 24),
108
);
109
return (
110
<Popconfirm
111
onConfirm={deleteUnit}
112
title={
113
<div style={{ maxWidth: "400px" }}>
114
<b>{title}</b>
115
<br />
116
{body}
117
</div>
118
}
119
>
120
<Button icon={<Icon name="trash" />}>{deleteLabel(intl)}</Button>
121
</Popconfirm>
122
);
123
}
124
}
125
126
if (isAssignmentUnit(props.unit)) {
127
const {
128
unit,
129
expandPeerConfig,
130
showPeerDisabledAlert,
131
setShowPeerDisabledAlert,
132
} = props as CourseUnitControlsAssignmentProps;
133
const peerMsg = peerGradingMessages(intl);
134
unitLabel = "assignment";
135
unitId = unit.get("assignment_id") ?? "";
136
unitPath = unit.get("path");
137
unitDeleted = unit.get("deleted");
138
deleteUnit = () => actions.assignments.delete_assignment(unitId);
139
undeleteUnit = () => actions.assignments.undelete_assignment(unitId);
140
141
renderDue = () => {
142
const { label, title, tip } = dueDateMessages(intl);
143
return (
144
<Tip title={title} tip={tip}>
145
<span>
146
{label}{" "}
147
<DateTimePicker
148
value={unit.get("due_date")}
149
onChange={(value) => {
150
const due: Date | string | null | undefined =
151
value && typeof (value as any).toDate === "function"
152
? (value as any).toDate()
153
: value;
154
actions.assignments.set_due_date(unitId, due);
155
}}
156
/>
157
</span>
158
</Tip>
159
);
160
};
161
162
renderPeerButton = () => {
163
const nbgraderUsed = !!unit.get("nbgrader");
164
const button = (
165
<Button
166
disabled={expandPeerConfig || nbgraderUsed}
167
onClick={() => actions.toggle_item_expansion("peer_config", unitId)}
168
icon={
169
<Icon
170
name={
171
unit.getIn(["peer_grade", "enabled"])
172
? "check-square-o"
173
: "square-o"
174
}
175
/>
176
}
177
>
178
{peerMsg.label}
179
</Button>
180
);
181
if (nbgraderUsed) {
182
return (
183
<Tip title={peerMsg.disabledTooltip}>
184
<span>{button}</span>
185
</Tip>
186
);
187
} else {
188
return button;
189
}
190
};
191
192
renderExportAssignment = () => {
193
const { label, title, tip } = exportCollectedMessages(intl);
194
return (
195
<Tip title={title} tip={tip}>
196
<Button
197
onClick={() => actions.assignments.export_collected(unitId)}
198
icon={<Icon name="cloud-download" />}
199
>
200
{label}
201
</Button>
202
</Tip>
203
);
204
};
205
206
renderPeerExtras = () => (
207
<>
208
{showPeerDisabledAlert ? (
209
<div style={{ marginTop: 8 }}>
210
<Alert
211
type="warning"
212
showIcon
213
closable
214
onClose={() => setShowPeerDisabledAlert(false)}
215
message={peerMsg.disabledAlert}
216
/>
217
</div>
218
) : null}
219
{expandPeerConfig ? (
220
<ConfigurePeerGrading actions={actions} assignment={unit} />
221
) : null}
222
</>
223
);
224
} else {
225
const { unit } = props as CourseUnitControlsHandoutProps;
226
unitLabel = "handout";
227
unitId = unit.get("handout_id");
228
unitPath = unit.get("path");
229
unitDeleted = unit.get("deleted");
230
deleteUnit = () => actions.handouts.delete_handout(unitId);
231
undeleteUnit = () => actions.handouts.undelete_handout(unitId);
232
}
233
return (
234
<Space
235
key="controls-stack"
236
direction="vertical"
237
size={size === "small" ? "small" : "middle"}
238
style={{ width: "100%" }}
239
>
240
<Row gutter={[8, 4]} align="top" justify="space-between">
241
<Col md={16}>
242
<Space wrap>
243
{renderOpenButton()}
244
{renderDue()}
245
{renderPeerButton()}
246
<ComputeServerButton
247
key="compute"
248
actions={actions}
249
unit={props.unit as unknown as Unit}
250
/>
251
</Space>
252
</Col>
253
<Col md={8} style={{ marginLeft: "auto" }}>
254
<Space wrap style={{ width: "100%", justifyContent: "flex-end" }}>
255
{renderExportFileUseTimes()}
256
{renderExportAssignment()}
257
{renderDeleteButton()}
258
</Space>
259
</Col>
260
</Row>
261
{renderPeerExtras()}
262
</Space>
263
);
264
}
265
266