Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/frontend/course/assignments/assignments-panel.tsx
Views: 687
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45import { Alert, Button, Col, Row } from "antd";6import { Map, Set } from "immutable";7import { FormattedMessage, useIntl } from "react-intl";89import {10AppRedux,11useActions,12useMemo,13useRedux,14useState,15} from "@cocalc/frontend/app-framework";16import { Gap, Icon, Tip } from "@cocalc/frontend/components";17import ScrollableList from "@cocalc/frontend/components/scrollable-list";18import { course } from "@cocalc/frontend/i18n";19import { cmp_array } from "@cocalc/util/misc";2021import { CourseActions } from "../actions";22import { AddItems, FoldersToolbar } from "../common/folders-tool-bar";23import {24AssignmentRecord,25IsGradingMap,26NBgraderRunInfo,27SortDescription,28StudentRecord,29} from "../store";30import * as styles from "../styles";31import * as util from "../util";32import { Assignment } from "./assignment";3334interface Props {35frame_id?: string;36name: string;37project_id: string;38redux: AppRedux;39actions: CourseActions;40assignments: Map<string, AssignmentRecord>;41students: Map<string, StudentRecord>;42user_map: object;43frameActions;44}4546export function AssignmentsPanel(props: Props) {47const {48frame_id,49name,50project_id,51redux,52assignments,53students,54user_map,55frameActions,56} = props;5758const intl = useIntl();5960const course_actions = useActions<CourseActions>({ name });6162const expanded_assignments: Set<string> = useRedux(63name,64"expanded_assignments",65);66const active_assignment_sort: SortDescription = useRedux(67name,68"active_assignment_sort",69);70const active_student_sort: SortDescription = useRedux(71name,72"active_student_sort",73);74const expanded_peer_configs: Set<string> = useRedux(75name,76"expanded_peer_configs",77);78const active_feedback_edits: IsGradingMap = useRedux(79name,80"active_feedback_edits",81);82const nbgrader_run_info: NBgraderRunInfo | undefined = useRedux(83name,84"nbgrader_run_info",85);8687// search query to restrict which assignments are shown.88const pageFilter = useRedux(name, "pageFilter");89const filter = pageFilter?.get("assignments") ?? "";90const setFilter = (filter: string) => {91course_actions.setPageFilter("assignments", filter);92};9394// whether or not to show deleted assignments on the bottom95const [show_deleted, set_show_deleted] = useState<boolean>(false);9697function get_assignment(id: string): AssignmentRecord {98const assignment = assignments.get(id);99if (assignment == undefined) {100console.warn(`Tried to access undefined assignment ${id}`);101}102return assignment as any;103}104105const { shown_assignments, num_omitted, num_deleted } = useMemo((): {106shown_assignments: any[];107num_omitted: number;108num_deleted: number;109} => {110let f, num_deleted, num_omitted;111let list = util.immutable_to_list(assignments, "assignment_id");112113({ list, num_omitted } = util.compute_match_list({114list,115search_key: "path",116search: filter.trim(),117}));118119if (active_assignment_sort.get("column_name") === "due_date") {120f = (a) => [121a.due_date != null ? a.due_date : 0,122a.path != null ? a.path.toLowerCase() : undefined,123];124} else if (active_assignment_sort.get("column_name") === "dir_name") {125f = (a) => [126a.path != null ? a.path.toLowerCase() : undefined,127a.due_date != null ? a.due_date : 0,128];129}130131({ list, num_deleted } = util.order_list({132list,133compare_function: (a, b) => cmp_array(f(a), f(b)),134reverse: active_assignment_sort.get("is_descending"),135include_deleted: show_deleted,136}));137138return {139shown_assignments: list,140num_omitted,141num_deleted,142};143}, [assignments, active_assignment_sort, show_deleted, filter]);144145function render_sort_link(column_name: string, display_name: string) {146return (147<a148href=""149onClick={(e) => {150e.preventDefault();151return course_actions.assignments.set_active_assignment_sort(152column_name,153);154}}155>156{display_name}157<Gap />158{active_assignment_sort.get("column_name") === column_name ? (159<Icon160style={{ marginRight: "10px" }}161name={162active_assignment_sort.get("is_descending")163? "caret-up"164: "caret-down"165}166/>167) : undefined}168</a>169);170}171172function render_assignment_table_header() {173return (174<div style={{ borderBottom: "1px solid #e5e5e5" }}>175<Row style={{ marginRight: "0px" }}>176<Col md={12}>177{render_sort_link(178"dir_name",179intl.formatMessage({180id: "course.assignments-panel.table-header.assignments",181defaultMessage: "Assignment Name",182}),183)}184</Col>185<Col md={12}>186{render_sort_link("due_date", intl.formatMessage(course.due_date))}187</Col>188</Row>189</div>190);191}192193function render_assignment(assignment_id: string, index: number) {194return (195<Assignment196key={assignment_id}197project_id={project_id}198frame_id={frame_id}199name={name}200redux={redux}201assignment={get_assignment(assignment_id)}202background={index % 2 === 0 ? "#eee" : undefined}203students={students}204user_map={user_map}205is_expanded={expanded_assignments.has(assignment_id)}206active_student_sort={active_student_sort}207expand_peer_config={expanded_peer_configs.has(assignment_id)}208active_feedback_edits={active_feedback_edits}209nbgrader_run_info={nbgrader_run_info}210/>211);212}213214function render_assignments(assignments: { assignment_id: string }[]) {215if (assignments.length == 0) {216return render_no_assignments();217}218return (219<ScrollableList220virtualize221rowCount={assignments.length}222rowRenderer={({ key, index }) => render_assignment(key, index)}223rowKey={(index) => assignments[index]?.assignment_id ?? ""}224cacheId={`course-assignments-${name}-${frame_id}`}225/>226);227}228229function render_no_assignments() {230return (231<div>232<Alert233type="info"234style={{235margin: "15px auto",236fontSize: "12pt",237maxWidth: "800px",238}}239message={240<b>241<a onClick={() => frameActions.setModal("add-assignments")}>242<FormattedMessage243id="course.assignments-panel.no_assignments.message"244defaultMessage={"Add Assignments to your Course"}245description={"online course for students"}246/>247</a>248</b>249}250description={251<div>252<FormattedMessage253id="course.assignments-panel.no_assignments.description"254defaultMessage={`255<p>256An assignment is a <i>directory</i> of files somewhere in your257CoCalc project. You copy the assignment to your students and258they work on it; later, you collect it, grade it, and return the259graded version to them.260</p>261<p>262<A>Add assignments to your course</A> by clicking "Add Assignment..." above.263You can create and select one or more directories and they will become assignments264that you can then customize and distribute to your students.265</p>`}266values={{267A: (c) => (268<a onClick={() => frameActions.setModal("add-assignments")}>269{c}270</a>271),272}}273description={"online course for students"}274/>275</div>276}277/>278</div>279);280}281282function render_show_deleted(num_deleted: number, num_shown: number) {283if (show_deleted) {284return (285<Button286style={styles.show_hide_deleted({ needs_margin: num_shown > 0 })}287onClick={() => set_show_deleted(false)}288>289<Tip290placement="left"291title="Hide deleted"292tip="Assignments are never really deleted. Click this button so that deleted assignments aren't included at the bottom of the list. Deleted assignments are always hidden from the list of grades for a student."293>294Hide {num_deleted} deleted assignments295</Tip>296</Button>297);298} else {299return (300<Button301style={styles.show_hide_deleted({ needs_margin: num_shown > 0 })}302onClick={() => {303set_show_deleted(true);304setFilter("");305}}306>307<Tip308placement="left"309title="Show deleted"310tip="Assignments are not deleted forever even after you delete them. Click this button to show any deleted assignments at the bottom of the list of assignments. You can then click on the assignment and click undelete to bring the assignment back."311>312Show {num_deleted} deleted assignments313</Tip>314</Button>315);316}317}318319function header() {320return (321<div style={{ marginBottom: "15px" }}>322<FoldersToolbar323search={filter}324search_change={setFilter}325num_omitted={num_omitted}326project_id={project_id}327items={assignments}328add_folders={course_actions.assignments.addAssignment}329item_name={"assignment"}330plural_item_name={"assignments"}331/>332</div>333);334}335336return (337<div className={"smc-vfill"} style={{ margin: "0" }}>338{header()}339{shown_assignments.length > 0340? render_assignment_table_header()341: undefined}342<div className="smc-vfill">343{render_assignments(shown_assignments)}{" "}344{num_deleted345? render_show_deleted(num_deleted, shown_assignments.length)346: undefined}347</div>348</div>349);350}351352// used for adding assignments outside of the above component.353export function AddAssignments({ name, actions, close }) {354const assignments = useRedux(name, "assignments");355return (356<AddItems357itemName="assignment"358items={assignments}359addItems={(paths) => {360actions.assignments.addAssignment(paths);361close?.();362}}363selectorStyle={{364position: null,365width: "100%",366boxShadow: null,367zIndex: null,368backgroundColor: null,369}}370defaultOpen371closable={false}372/>373);374}375376377