Skip to content

Commit ddce4c1

Browse files
committed
course: migrating more configuration to also be in menus
- also fixing some subtle sync issues that were not properly addressed in initial implementation - modernize ui components a little
1 parent 2030294 commit ddce4c1

File tree

8 files changed

+361
-149
lines changed

8 files changed

+361
-149
lines changed

src/packages/frontend/course/configuration/actions-panel.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,16 @@ export const ActionsPanel: React.FC<Props> = React.memo(
3535
<Col md={12} style={{ padding: "15px 15px 15px 0" }}>
3636
<StartAllProjects name={name} project_map={project_map} />
3737
<br />
38-
<TerminalCommandPanel name={name} />
39-
<br />
40-
<ExportGrades actions={actions} />
41-
</Col>
42-
<Col md={12} style={{ padding: "15px" }}>
4338
<ReconfigureAllProjects
4439
configuring_projects={configuring_projects}
4540
actions={actions}
4641
/>
4742
<br />
43+
<TerminalCommandPanel name={name} />
44+
<br />
45+
<ExportGrades actions={actions} />
46+
</Col>
47+
<Col md={12} style={{ padding: "15px" }}>
4848
<ResendInvites
4949
actions={actions}
5050
reinviting_students={reinviting_students}

src/packages/frontend/course/configuration/configuration-panel.tsx

Lines changed: 142 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import {
1919
LabeledRow,
2020
MarkdownInput,
2121
TextInput,
22-
ErrorDisplay,
2322
} from "@cocalc/frontend/components";
2423
import { StudentProjectUpgrades } from "./upgrades";
2524
import { CourseActions } from "../actions";
@@ -34,6 +33,7 @@ import { KUCALC_ON_PREMISES } from "@cocalc/util/db-schema/site-defaults";
3433
import { EnvironmentVariablesConfig } from "./envvars-config";
3534
import StudentPay from "./student-pay";
3635
//import Mirror from "./mirror";
36+
import ShowError from "@cocalc/frontend/components/error";
3737

3838
interface Props {
3939
name: string;
@@ -44,81 +44,10 @@ interface Props {
4444

4545
export const ConfigurationPanel: React.FC<Props> = React.memo(
4646
({ name, project_id, settings, configuring_projects }) => {
47-
const [email_body_error, set_email_body_error] = useState<
48-
string | undefined
49-
>(undefined);
50-
5147
const actions = useActions<CourseActions>({ name });
52-
const store = useStore<CourseStore>({ name });
5348
const is_commercial = useTypedRedux("customize", "is_commercial");
5449
const kucalc = useTypedRedux("customize", "kucalc");
5550

56-
/*
57-
* Custom invitation email body
58-
*/
59-
60-
const check_email_body = debounce(
61-
(value) => {
62-
const allow_urls: boolean = redux
63-
.getStore("projects")
64-
.allow_urls_in_emails(project_id);
65-
if (!allow_urls && contains_url(value)) {
66-
set_email_body_error(
67-
"URLs in emails are not allowed for free trial projects. Please upgrade or delete the URL. This is an anti-spam measure.",
68-
);
69-
} else {
70-
set_email_body_error(undefined);
71-
}
72-
},
73-
500,
74-
{ leading: true, trailing: true },
75-
);
76-
77-
function render_email_body_error() {
78-
if (email_body_error == null) return;
79-
return <ErrorDisplay error={email_body_error} />;
80-
}
81-
82-
function render_email_invite_body() {
83-
const template_instr =
84-
" Also, {title} will be replaced by the title of the course and {name} by your name.";
85-
return (
86-
<Card
87-
title={
88-
<>
89-
<Icon name="envelope" /> Email Invitation
90-
</>
91-
}
92-
>
93-
<div
94-
style={{
95-
border: "1px solid lightgrey",
96-
padding: "10px",
97-
borderRadius: "5px",
98-
}}
99-
>
100-
{render_email_body_error()}
101-
<MarkdownInput
102-
persist_id={name + "email-invite-body"}
103-
attach_to={name}
104-
rows={6}
105-
default_value={store.get_email_invite()}
106-
on_save={(body) => actions.configuration.set_email_invite(body)}
107-
save_disabled={email_body_error != null}
108-
on_change={check_email_body}
109-
on_cancel={() => set_email_body_error(undefined)}
110-
/>
111-
</div>
112-
<hr />
113-
<span style={{ color: "#666" }}>
114-
If you add a student to this course using their email address, and
115-
they do not have a CoCalc account, then they will receive this email
116-
invitation. {template_instr}
117-
</span>
118-
</Card>
119-
);
120-
}
121-
12251
function render_require_institute_pay() {
12352
if (!is_commercial) return;
12453
return (
@@ -165,32 +94,6 @@ export const ConfigurationPanel: React.FC<Props> = React.memo(
16594
);
16695
}
16796

168-
function render_disable_students() {
169-
return (
170-
<DisableStudentCollaboratorsPanel
171-
checked={!!settings.get("allow_collabs")}
172-
on_change={(val) => actions.configuration.set_allow_collabs(val)}
173-
/>
174-
);
175-
}
176-
177-
function render_student_project_functionality() {
178-
const functionality =
179-
settings.get("student_project_functionality")?.toJS() ?? {};
180-
return (
181-
<CustomizeStudentProjectFunctionality
182-
functionality={functionality}
183-
onChange={async (opts) =>
184-
await actions.configuration.set_student_project_functionality(opts)
185-
}
186-
/>
187-
);
188-
}
189-
190-
function render_nbgrader() {
191-
return <Nbgrader name={name} />;
192-
}
193-
19497
return (
19598
<div className="smc-vfill" style={{ overflowY: "scroll" }}>
19699
<Row>
@@ -207,14 +110,19 @@ export const ConfigurationPanel: React.FC<Props> = React.memo(
207110
name={name}
208111
/>
209112
<br />
210-
{render_email_invite_body()}
113+
<EmailInvitation
114+
actions={actions}
115+
redux={redux}
116+
project_id={project_id}
117+
name={name}
118+
/>
211119
<br />
212-
{render_nbgrader()}
120+
<Nbgrader name={name} />
213121
</Col>
214122
<Col md={12} style={{ padding: "15px" }}>
215-
{render_disable_students()}
123+
<CollaboratorPolicy settings={settings} actions={actions} />
216124
<br />
217-
{render_student_project_functionality()}
125+
<RestrictStudentProjects settings={settings} actions={actions} />
218126
<br />
219127
<StudentProjectSoftwareEnvironment
220128
actions={actions.configuration}
@@ -224,14 +132,16 @@ export const ConfigurationPanel: React.FC<Props> = React.memo(
224132
/>
225133
<br />
226134
<Parallel name={name} />
227-
<DatastoreConfig
228-
actions={actions.configuration}
229-
datastore={settings.get("datastore")}
135+
<NetworkFilesystem
136+
actions={actions}
137+
settings={settings}
138+
project_id={project_id}
230139
/>
231140
<br />
232-
<EnvironmentVariablesConfig
233-
actions={actions.configuration}
234-
envvars={settings.get("envvars")}
141+
<EnvVariables
142+
actions={actions}
143+
settings={settings}
144+
project_id={project_id}
235145
/>
236146
{/*<br />
237147
<Mirror
@@ -291,3 +201,127 @@ export function TitleAndDescription({ actions, settings, name }) {
291201
</Card>
292202
);
293203
}
204+
205+
export function EmailInvitation({ actions, redux, project_id, name }) {
206+
const [error, setError] = useState<string>("");
207+
const store = useStore<CourseStore>({ name });
208+
209+
const check_email_body = debounce(
210+
(value) => {
211+
const allow_urls: boolean = redux
212+
.getStore("projects")
213+
.allow_urls_in_emails(project_id);
214+
if (!allow_urls && contains_url(value)) {
215+
setError(
216+
"URLs in emails are not allowed for free trial projects. Please upgrade or delete the URL. This is an anti-spam measure.",
217+
);
218+
} else {
219+
setError("");
220+
}
221+
},
222+
500,
223+
{ leading: true, trailing: true },
224+
);
225+
226+
const template_instr =
227+
" Also, {title} will be replaced by the title of the course and {name} by your name.";
228+
return (
229+
<Card
230+
title={
231+
<>
232+
<Icon name="envelope" /> Email Invitation
233+
</>
234+
}
235+
>
236+
<div
237+
style={{
238+
border: "1px solid lightgrey",
239+
padding: "10px",
240+
borderRadius: "5px",
241+
}}
242+
>
243+
<ShowError error={error} />
244+
<MarkdownInput
245+
persist_id={name + "email-invite-body"}
246+
attach_to={name}
247+
rows={6}
248+
default_value={store.get_email_invite()}
249+
on_save={(body) => actions.configuration.set_email_invite(body)}
250+
save_disabled={!!error}
251+
on_change={check_email_body}
252+
on_cancel={() => setError("")}
253+
/>
254+
</div>
255+
<hr />
256+
<span style={{ color: "#666" }}>
257+
If you add a student to this course using their email address, and they
258+
do not have a CoCalc account, then they will receive this email
259+
invitation. {template_instr}
260+
</span>
261+
</Card>
262+
);
263+
}
264+
265+
export function CollaboratorPolicy({ settings, actions }) {
266+
return (
267+
<DisableStudentCollaboratorsPanel
268+
checked={!!settings.get("allow_collabs")}
269+
on_change={(val) => actions.configuration.set_allow_collabs(val)}
270+
/>
271+
);
272+
}
273+
274+
export function RestrictStudentProjects({ settings, actions }) {
275+
const functionality =
276+
settings.get("student_project_functionality")?.toJS() ?? {};
277+
return (
278+
<CustomizeStudentProjectFunctionality
279+
functionality={functionality}
280+
onChange={async (opts) =>
281+
await actions.configuration.set_student_project_functionality(opts)
282+
}
283+
/>
284+
);
285+
}
286+
287+
export function NetworkFilesystem({
288+
settings,
289+
actions,
290+
project_id,
291+
close,
292+
}: {
293+
settings;
294+
actions;
295+
project_id;
296+
close?;
297+
}) {
298+
return (
299+
<DatastoreConfig
300+
actions={actions.configuration}
301+
datastore={settings.get("datastore")}
302+
project_id={project_id}
303+
close={close}
304+
/>
305+
);
306+
}
307+
308+
export function EnvVariables({
309+
settings,
310+
actions,
311+
project_id,
312+
close,
313+
}: {
314+
settings;
315+
actions;
316+
project_id;
317+
close?;
318+
}) {
319+
return (
320+
<EnvironmentVariablesConfig
321+
actions={actions.configuration}
322+
envvars={settings.get("envvars")}
323+
project_id={project_id}
324+
close={close}
325+
/>
326+
);
327+
}

src/packages/frontend/course/configuration/datastore-config.tsx

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,13 @@
1010

1111
import { Button, Card, Form, Switch, Typography } from "antd";
1212
import { List } from "immutable";
13-
14-
import { React, useState, useTypedRedux } from "@cocalc/frontend/app-framework";
13+
import { useEffect } from "react";
14+
import {
15+
redux,
16+
React,
17+
useState,
18+
useTypedRedux,
19+
} from "@cocalc/frontend/app-framework";
1520
import { Icon } from "@cocalc/frontend/components";
1621
import { Datastore } from "@cocalc/frontend/projects/actions";
1722
import {
@@ -23,6 +28,8 @@ import { ConfigurationActions } from "./actions";
2328
interface Props {
2429
actions: ConfigurationActions;
2530
datastore?: Datastore | List<string>; // List<string> is not used yet
31+
project_id: string;
32+
close?: Function;
2633
}
2734

2835
export const DatastoreConfig: React.FC<Props> = (props: Props) => {
@@ -36,16 +43,22 @@ export const DatastoreConfig: React.FC<Props> = (props: Props) => {
3643
const inherit = typeof datastore === "boolean" ? datastore : true;
3744
const [next_val, set_next_val] = useState<boolean>(inherit);
3845

46+
useEffect(() => {
47+
// needed because of realtime collaboration, multiple frames, modal, etc!
48+
set_next_val(inherit);
49+
}, [inherit]);
50+
3951
function on_inherit_change(inherit: boolean) {
4052
set_next_val(inherit);
4153
}
4254

43-
React.useEffect(() => {
55+
useEffect(() => {
4456
set_need_save(next_val != inherit);
4557
}, [next_val, inherit]);
4658

4759
function save() {
4860
actions.set_datastore(next_val);
61+
props.close?.();
4962
}
5063

5164
// this selector only make sense for cocalc.com or onprem with datastore enabled
@@ -91,9 +104,19 @@ export const DatastoreConfig: React.FC<Props> = (props: Props) => {
91104
If enabled, all student projects will have{" "}
92105
<Typography.Text strong>read-only</Typography.Text> access to the same
93106
cloud stores and remote file systems as this instructor project. To
94-
configure them, please check this project's settings for more details.
95-
Any changes to the configuration of this project will be reflected
96-
after the next start of a student project.
107+
configure them, please check{" "}
108+
<a
109+
onClick={() => {
110+
redux
111+
.getProjectActions(props.project_id)
112+
.set_active_tab("settings");
113+
props.close?.();
114+
}}
115+
>
116+
this project's settings
117+
</a>{" "}
118+
for more details. Any changes to the configuration of this project
119+
will be reflected after the next start of a student project.
97120
</p>
98121
{render_control()}
99122
</Card>

0 commit comments

Comments
 (0)