Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | 3x 3x | import React, { useCallback, useEffect, useMemo, useRef } from "react";
import { Box, Divider, Typography } from "@mui/material";
import { DrawingGraph, DrawingGraphModes } from "graph2d-react";
import {
normalizeGraphAnswerForImport,
parseGraphAxisBounds,
} from "@/utils/components/question-bank/question-display/graph/graphAnswerUtils";
import {
GRAPH2D_QUESTION_BANK_CANVAS_SIZE,
drawingGraphFrameSx,
} from "@/utils/components/question-bank/question-display/graph/graphConstants";
import { describeInteractiveDotsAnswer } from "@/utils/components/question-bank/question-display/interactive-dots/interactiveDotsUtils";
import { useSubmissionGraphSlot } from "./SubmissionGraphContext";
/**
* Read-only Graph Multiple-Select canvas for submission review.
* Slot (instance + sequential graphId) comes from SubmissionGraphProvider.
*/
const SubmissionInteractiveDotsAnswer = ({ content, variant = "student" }) => {
const { graphInstance, graphId } = useSubmissionGraphSlot();
const readyRef = useRef(false);
const rafRef = useRef([]);
const encoded = normalizeGraphAnswerForImport(content);
const bounds = useMemo(() => parseGraphAxisBounds(content), [content]);
const summary = describeInteractiveDotsAnswer(content);
const drawingMode =
variant === "correct"
? DrawingGraphModes.MARK_CORRECT_INTERACTIVE_DOT
: DrawingGraphModes.TOGGLE_INTERACTIVE_DOT;
const cancelRafs = useCallback(() => {
rafRef.current.forEach((id) => cancelAnimationFrame(id));
rafRef.current = [];
}, []);
const applyCanvas = useCallback(() => {
if (!readyRef.current || !encoded || !graphInstance) return false;
try {
graphInstance.importAnswer(encoded, graphId);
graphInstance.setDrawingMode?.(drawingMode, graphId);
return true;
} catch (err) {
console.warn("Failed to import graph-multiple-select review canvas:", err);
return false;
}
}, [drawingMode, encoded, graphId, graphInstance]);
const importWhenReady = useCallback(() => {
cancelRafs();
applyCanvas();
const outer = requestAnimationFrame(() => {
applyCanvas();
const inner = requestAnimationFrame(() => {
applyCanvas();
});
rafRef.current.push(inner);
});
rafRef.current.push(outer);
}, [applyCanvas, cancelRafs]);
const handleGraphReady = useCallback(() => {
readyRef.current = true;
importWhenReady();
}, [importWhenReady]);
useEffect(() => {
if (!readyRef.current) return undefined;
importWhenReady();
return cancelRafs;
}, [bounds.xMax, bounds.xMin, bounds.yMax, bounds.yMin, cancelRafs, encoded, importWhenReady]);
useEffect(() => () => cancelRafs(), [cancelRafs]);
if (!encoded) {
return (
<Typography variant="body2" color="text.secondary" fontStyle="italic" sx={{ py: 1 }}>
No graph answer submitted.
</Typography>
);
}
const size = GRAPH2D_QUESTION_BANK_CANVAS_SIZE;
return (
<Box>
{summary && (
<Typography variant="caption" color="text.secondary" sx={{ display: "block", mb: 1 }}>
{summary}
</Typography>
)}
<Box sx={drawingGraphFrameSx}>
<DrawingGraph
key={`gms-review-${variant}-${graphId}-${bounds.xMin}-${bounds.xMax}-${bounds.yMin}-${bounds.yMax}`}
graphId={graphId}
width={size}
height={size}
instance={graphInstance}
steps={1}
borderPixel={5}
responsive={false}
readOnly
drawingMode={drawingMode}
xMin={bounds.xMin}
xMax={bounds.xMax}
yMin={bounds.yMin}
yMax={bounds.yMax}
onGraphReady={handleGraphReady}
/>
</Box>
</Box>
);
};
export const SubmissionInteractiveDotsReview = ({ studentContent, correctContent }) => (
<Box sx={{ mt: 1.5, display: "flex", flexDirection: "column", gap: 1.5 }}>
<Box>
<Typography
variant="caption"
color="text.secondary"
sx={{ fontWeight: 600, display: "block", mb: 0.5 }}
>
Student's Answer
</Typography>
<SubmissionInteractiveDotsAnswer content={studentContent} variant="student" />
</Box>
{correctContent != null && correctContent !== "" && (
<>
<Divider />
<Box>
<Typography
variant="caption"
color="text.secondary"
sx={{ fontWeight: 600, display: "block", mb: 0.5 }}
>
Correct Answer
</Typography>
<SubmissionInteractiveDotsAnswer content={correctContent} variant="correct" />
</Box>
</>
)}
</Box>
);
export default SubmissionInteractiveDotsAnswer;
|