diff --git a/app/graphql/types/exam_version_type.rb b/app/graphql/types/exam_version_type.rb index 36d562f93..0d1864e79 100644 --- a/app/graphql/types/exam_version_type.rb +++ b/app/graphql/types/exam_version_type.rb @@ -123,7 +123,7 @@ def instructions guard ALL_STAFF_OR_PUBLISHED end - field :all_rubrics, [Types::RubricType], null: true do + field :all_rubrics, [Types::RubricType], null: false do guard Guards::ALL_STAFF end def all_rubrics @@ -132,7 +132,7 @@ def all_rubrics .load(object) end - field :rubrics, [Types::RubricType], null: true do + field :rubrics, [Types::RubricType], null: false do guard Guards::ALL_STAFF end def rubrics @@ -141,11 +141,11 @@ def rubrics .load(object) end - field :root_rubric, Types::RubricType, null: true do + field :root_rubric, Types::RubricType, null: false do guard Guards::ALL_STAFF end - field :raw_rubrics, GraphQL::Types::JSON, null: true do + field :raw_rubrics, GraphQL::Types::JSON, null: false do guard Guards::PROFESSORS end def raw_rubrics diff --git a/app/graphql/types/grading_comment_type.rb b/app/graphql/types/grading_comment_type.rb index fae614cae..5d90c23f5 100644 --- a/app/graphql/types/grading_comment_type.rb +++ b/app/graphql/types/grading_comment_type.rb @@ -26,6 +26,5 @@ def bnum field :message, String, null: false field :points, Float, null: false field :creator, Types::UserType, null: false - field :preset_comment, Types::PresetCommentType, null: true end end diff --git a/app/javascript/components/common/NumericInput.tsx b/app/javascript/components/common/NumericInput.tsx index 1f429b57f..8a3776a54 100644 --- a/app/javascript/components/common/NumericInput.tsx +++ b/app/javascript/components/common/NumericInput.tsx @@ -69,7 +69,9 @@ const validateNumericInput : KeyboardEventHandler = (e) => { if (e.key === '.') { const indexOfDot = e.currentTarget.value.indexOf('.'); if (indexOfDot < 0) return; - if (e.currentTarget.selectionStart <= indexOfDot + if (e.currentTarget.selectionStart !== null + && e.currentTarget.selectionStart <= indexOfDot + && e.currentTarget.selectionEnd !== null && indexOfDot < e.currentTarget.selectionEnd) { return; } @@ -77,7 +79,9 @@ const validateNumericInput : KeyboardEventHandler = (e) => { if (e.key === '-') { const indexOfMinus = e.currentTarget.value.indexOf('-'); if (indexOfMinus < 0 && e.currentTarget.selectionStart === 0) return; - if (e.currentTarget.selectionStart <= indexOfMinus + if (e.currentTarget.selectionStart !== null + && e.currentTarget.selectionStart <= indexOfMinus + && e.currentTarget.selectionEnd !== null && indexOfMinus < e.currentTarget.selectionEnd) { return; } @@ -147,7 +151,7 @@ export const NumericInput: React.FC<{ const selEnd = e.currentTarget.selectionEnd; const nextVal = `${curVal.slice(0, selStart)}${data}${curVal.slice(selEnd)}`; const nextAsNum = Number(nextVal); - if (Number.isFinite(nextAsNum)) { + if (Number.isFinite(nextAsNum) && onChange) { onChange(clamp(nextAsNum, min, max), true); } }} @@ -167,7 +171,7 @@ export const NumericInput: React.FC<{ variant={variant} disabled={disabled || (max !== undefined && numValue >= max)} tabIndex={-1} - onClick={() => onChange(clamp(numValue + step, min, max), false)} + onClick={() => onChange && onChange(clamp(numValue + step, min, max), false)} > @@ -177,7 +181,7 @@ export const NumericInput: React.FC<{ variant={variant} disabled={disabled || (min !== undefined && numValue <= min)} tabIndex={-1} - onClick={() => onChange(clamp(numValue - step, min, max), false)} + onClick={() => onChange && onChange(clamp(numValue - step, min, max), false)} > diff --git a/app/javascript/components/common/ReadableDate.tsx b/app/javascript/components/common/ReadableDate.tsx index adc38bbe0..22f9c0182 100644 --- a/app/javascript/components/common/ReadableDate.tsx +++ b/app/javascript/components/common/ReadableDate.tsx @@ -1,12 +1,12 @@ import React from 'react'; -import { DateTime, Duration } from 'luxon'; +import { DateTime } from 'luxon'; function makeReadableDate(dd: DateTime, showTime: boolean, capitalize: boolean): string { const today = DateTime.local().startOf('day'); const yesterday = today.minus({ days: 1 }); const tomorrow = today.plus({ days: 1 }); const twodays = tomorrow.plus({ days: 1 }); - let relDay: string; + let relDay: string | undefined; if (yesterday <= dd && dd < today) { relDay = (capitalize ? 'Yesterday' : 'yesterday'); } else if (today <= dd && dd < tomorrow) { @@ -28,7 +28,6 @@ interface ReadableDateProps { value: DateTime; relative?: boolean; showTime?: boolean; - threshold?: Duration; capitalize?: boolean; } @@ -40,11 +39,11 @@ const ReadableDate: React.FC = (props) => { className, capitalize, } = props; - let str: string; + let str: string | null; if (relative) { str = value.toRelative(); } else { - str = makeReadableDate(value, showTime, capitalize); + str = makeReadableDate(value, showTime, !!capitalize); } return ( {str} diff --git a/app/javascript/components/common/alerts.tsx b/app/javascript/components/common/alerts.tsx index 26e2cdfd0..cd6afd6b8 100644 --- a/app/javascript/components/common/alerts.tsx +++ b/app/javascript/components/common/alerts.tsx @@ -38,7 +38,7 @@ const ShowAlert: React.FC<{ copyButton = false, } = alert; const [show, setShow] = useState(true); - const bodyRef = useRef(); + const bodyRef = useRef(null); return ( { - const text = bodyRef.current.innerText; - await navigator.clipboard.writeText(text); + if (bodyRef.current) { + const text = bodyRef.current.innerText; + await navigator.clipboard.writeText(text); + } }} > diff --git a/app/javascript/components/common/archive/fileMarks.ts b/app/javascript/components/common/archive/fileMarks.ts index f3dd88a73..ca57c2c66 100644 --- a/app/javascript/components/common/archive/fileMarks.ts +++ b/app/javascript/components/common/archive/fileMarks.ts @@ -1,5 +1,8 @@ interface MarksInfo { - byLine: unknown[]; + byLine: { + open: number[], + close: number[], + }[][]; byNum: { [num: number]: { startLine: number; @@ -37,7 +40,7 @@ export function extractMarks(text: string): FileMarksInfo { let idx: number; // eslint-disable-next-line no-cond-assign while ((idx = lines[lineNo].search(/~ro:\d+:[se]~/)) >= 0) { - const m = lines[lineNo].match(/~ro:(\d+):([se])~/); + const m = lines[lineNo].match(/~ro:(\d+):([se])~/) as RegExpMatchArray; lines[lineNo] = lines[lineNo].replace(m[0], ''); if (m[2] === 's') { count += 1; @@ -81,7 +84,7 @@ export function extractMarks(text: string): FileMarksInfo { }, options: { inclusiveLeft: m.lockBefore, - inclusiveRight: m.lockAfter, + inclusiveRight: !!m.lockAfter, }, })), }; diff --git a/app/javascript/components/common/archive/index.tsx b/app/javascript/components/common/archive/index.tsx index 756b26e35..5926dde31 100644 --- a/app/javascript/components/common/archive/index.tsx +++ b/app/javascript/components/common/archive/index.tsx @@ -4,17 +4,19 @@ import mime from '@hourglass/common/mime'; import { extractMarks } from '@hourglass/common/archive/fileMarks'; export async function handleDir(root: JSZip): Promise { - const ret = []; - const promises = []; + const ret: ExamFile[] = []; + const promises: Promise[] = []; root.forEach((path, file) => { if (file.dir) return; const exploded = path.split('/'); - const fileName = exploded.pop(); - let current = ret; - const pathSoFar = []; + // since the split condition is non-empty, the exploded array will be non-empty + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const fileName = exploded.pop()!; + let current: ExamFile[] = ret; + const pathSoFar: string[] = []; exploded.forEach((segment) => { pathSoFar.push(segment); - const dir = current.find((f) => f.text === `${segment}/`); + const dir = current.find((f): f is ExamDir => f.text === `${segment}/`); if (dir) { current = dir.nodes; } else { diff --git a/app/javascript/components/common/helpers.ts b/app/javascript/components/common/helpers.ts index fd876de86..81e591f6c 100644 --- a/app/javascript/components/common/helpers.ts +++ b/app/javascript/components/common/helpers.ts @@ -65,3 +65,7 @@ export type SelectOptions = SelectOption[]; export type MutationReturn = [ MutateWithVariables, MutationState ]; + +export function compact(ts : readonly (T | null | undefined)[]): T[] { + return ts.filter((t : T | null | undefined): t is T => t !== null && t !== undefined); +} diff --git a/app/javascript/components/common/student-dnd/index.tsx b/app/javascript/components/common/student-dnd/index.tsx index 912240041..e22d1c56d 100644 --- a/app/javascript/components/common/student-dnd/index.tsx +++ b/app/javascript/components/common/student-dnd/index.tsx @@ -36,7 +36,7 @@ interface Section { interface Student { id: string; - nuid: number; + nuid: number | null; username: string; displayName: string; } @@ -329,7 +329,7 @@ const StudentDNDForm: React.FC<