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