@hydralms/components 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/StudentProfile-BVfZMbnV.cjs +1 -0
- package/dist/StudentProfile-DeMxdrL3.js +3275 -0
- package/dist/assessment-toolbar/question-navigator.d.ts +1 -1
- package/dist/assessment-toolbar/timer-display.d.ts +1 -1
- package/dist/common/index.d.ts +2 -1
- package/dist/common/pagination.d.ts +26 -0
- package/dist/common/types.d.ts +1 -0
- package/dist/components.css +1 -1
- package/dist/content/audio-player.d.ts +22 -0
- package/dist/content/code-block.d.ts +30 -0
- package/dist/content/embed-block.d.ts +28 -0
- package/dist/content/index.d.ts +6 -0
- package/dist/content/types.d.ts +24 -0
- package/dist/curriculum/course-card.d.ts +51 -0
- package/dist/curriculum/index.d.ts +2 -0
- package/dist/curriculum/types.d.ts +2 -2
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +494 -444
- package/dist/license/HydraContext.d.ts +16 -0
- package/dist/license/ProBadge.d.ts +6 -0
- package/dist/license/index.d.ts +7 -0
- package/dist/license/tiers.d.ts +3 -0
- package/dist/license/useHydraLicense.d.ts +6 -0
- package/dist/license/validate.d.ts +13 -0
- package/dist/license/withProGate.d.ts +6 -0
- package/dist/modules/AssignmentModule/AssignmentModule.d.ts +4 -7
- package/dist/modules/AssignmentModule/types.d.ts +5 -1
- package/dist/modules/CertificateModule/CertificateModule.d.ts +4 -8
- package/dist/modules/CertificateModule/types.d.ts +6 -4
- package/dist/modules/CourseCatalogModule/CourseCatalogModule.d.ts +5 -0
- package/dist/modules/CourseCatalogModule/types.d.ts +43 -0
- package/dist/modules/CoursePlayer/CoursePlayer.d.ts +4 -1
- package/dist/modules/DiscussionModule/DiscussionModule.d.ts +4 -7
- package/dist/modules/ExamModule/ExamModule.d.ts +4 -7
- package/dist/modules/ExamModule/types.d.ts +5 -14
- package/dist/modules/FlashcardLab/FlashcardLab.d.ts +4 -1
- package/dist/modules/FlashcardLab/types.d.ts +2 -0
- package/dist/modules/GradeCenterModule/GradeCenterModule.d.ts +4 -8
- package/dist/modules/GradeCenterModule/types.d.ts +2 -0
- package/dist/modules/QuizModule/QuizModule.d.ts +4 -1
- package/dist/modules/QuizModule/types.d.ts +5 -14
- package/dist/modules/StudentDashboardModule/StudentDashboardModule.d.ts +5 -0
- package/dist/modules/StudentDashboardModule/types.d.ts +54 -0
- package/dist/modules/StudentProfileModule/StudentProfileModule.d.ts +5 -0
- package/dist/modules/StudentProfileModule/types.d.ts +43 -0
- package/dist/modules/SurveyModule/SurveyModule.d.ts +4 -6
- package/dist/modules/SurveyModule/types.d.ts +2 -0
- package/dist/modules/_shared/assessment-intro.d.ts +16 -0
- package/dist/modules/_shared/assessment-results.d.ts +23 -0
- package/dist/modules/_shared/types.d.ts +10 -0
- package/dist/modules/_shared/use-timer.d.ts +9 -0
- package/dist/modules/index.d.ts +6 -0
- package/dist/modules.cjs +1 -1
- package/dist/modules.js +1266 -854
- package/dist/progress/types.d.ts +2 -0
- package/dist/provider/HydraProvider.d.ts +5 -1
- package/dist/questions/choice.d.ts +1 -1
- package/dist/questions/confidence-indicator.d.ts +37 -0
- package/dist/questions/essay.d.ts +1 -1
- package/dist/questions/fill-in-the-blank.d.ts +1 -1
- package/dist/questions/hotspot.d.ts +1 -1
- package/dist/questions/index.d.ts +2 -0
- package/dist/questions/inline-choice.d.ts +1 -1
- package/dist/questions/matching.d.ts +1 -1
- package/dist/questions/multiple-choice.d.ts +1 -1
- package/dist/questions/numeric.d.ts +1 -1
- package/dist/questions/ordering.d.ts +1 -1
- package/dist/questions/question-renderer.d.ts +1 -1
- package/dist/questions/scenario.d.ts +1 -1
- package/dist/questions/spreadsheet.d.ts +1 -1
- package/dist/questions/true-false.d.ts +1 -1
- package/dist/sections/AnnouncementFeed/AnnouncementFeed.d.ts +1 -1
- package/dist/sections/AnnouncementFeed/types.d.ts +15 -1
- package/dist/sections/AssessmentReview/AssessmentReview.d.ts +1 -1
- package/dist/sections/AssessmentReview/types.d.ts +6 -0
- package/dist/sections/AssignmentSubmission/AssignmentSubmission.d.ts +1 -1
- package/dist/sections/AssignmentSubmission/types.d.ts +6 -0
- package/dist/sections/CertificateViewer/CertificateViewer.d.ts +1 -1
- package/dist/sections/CertificateViewer/certificate-variants.d.ts +42 -0
- package/dist/sections/CertificateViewer/types.d.ts +6 -0
- package/dist/sections/CourseCatalog/CourseCatalog.d.ts +2 -0
- package/dist/sections/CourseCatalog/types.d.ts +80 -0
- package/dist/sections/CourseOutline/CourseOutline.d.ts +1 -1
- package/dist/sections/CourseOutline/types.d.ts +6 -0
- package/dist/sections/DiscussionThread/DiscussionThread.d.ts +1 -1
- package/dist/sections/DiscussionThread/types.d.ts +6 -0
- package/dist/sections/EnrollmentWizard/EnrollmentWizard.d.ts +2 -0
- package/dist/sections/EnrollmentWizard/types.d.ts +66 -0
- package/dist/sections/ExamSession/ExamSession.d.ts +1 -1
- package/dist/sections/ExamSession/types.d.ts +6 -0
- package/dist/sections/FlashcardStudySession/FlashcardStudySession.d.ts +1 -1
- package/dist/sections/FlashcardStudySession/types.d.ts +6 -0
- package/dist/sections/ForumBoard/ForumBoard.d.ts +1 -1
- package/dist/sections/ForumBoard/types.d.ts +14 -0
- package/dist/sections/GradebookTable/GradebookTable.d.ts +1 -1
- package/dist/sections/GradebookTable/types.d.ts +14 -0
- package/dist/sections/LecturePlayer/LecturePlayer.d.ts +1 -1
- package/dist/sections/LecturePlayer/types.d.ts +8 -0
- package/dist/sections/LessonPage/LessonPage.d.ts +1 -1
- package/dist/sections/LessonPage/types.d.ts +6 -0
- package/dist/sections/PracticeQuiz/PracticeQuiz.d.ts +1 -1
- package/dist/sections/PracticeQuiz/types.d.ts +6 -0
- package/dist/sections/ProgressDashboard/ProgressDashboard.d.ts +1 -1
- package/dist/sections/ProgressDashboard/types.d.ts +6 -0
- package/dist/sections/QuizSession/QuizSession.d.ts +1 -1
- package/dist/sections/QuizSession/types.d.ts +6 -0
- package/dist/sections/RequirementsChecklist/RequirementsChecklist.d.ts +1 -1
- package/dist/sections/RequirementsChecklist/types.d.ts +6 -0
- package/dist/sections/ResourceLibrary/ResourceLibrary.d.ts +1 -1
- package/dist/sections/ResourceLibrary/types.d.ts +15 -1
- package/dist/sections/RubricView/RubricView.d.ts +1 -1
- package/dist/sections/RubricView/types.d.ts +6 -0
- package/dist/sections/ScrollableQuiz/ScrollableQuiz.d.ts +1 -1
- package/dist/sections/ScrollableQuiz/types.d.ts +6 -0
- package/dist/sections/StudentProfile/StudentProfile.d.ts +2 -0
- package/dist/sections/StudentProfile/types.d.ts +98 -0
- package/dist/sections/SurveyForm/SurveyForm.d.ts +1 -1
- package/dist/sections/SurveyForm/types.d.ts +6 -0
- package/dist/sections/_shared/merge-answers.d.ts +9 -0
- package/dist/sections/_shared/section-shell.d.ts +20 -0
- package/dist/sections/_shared/use-assessment-session.d.ts +30 -0
- package/dist/sections/index.d.ts +6 -0
- package/dist/sections.cjs +1 -1
- package/dist/sections.js +268 -307
- package/dist/tabs-BsfVo2Bl.cjs +173 -0
- package/dist/{tabs-Wf3h_Cx3.js → tabs-BuY1iNJE.js} +7532 -6807
- package/dist/ui/badge.d.ts +1 -1
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/progress.d.ts +1 -1
- package/dist/ui/rich-text-editor.d.ts +3 -1
- package/dist/ui/toast.d.ts +43 -0
- package/dist/utils/debounce.d.ts +5 -1
- package/dist/utils/pick-palette-color.d.ts +19 -0
- package/dist/video/types.d.ts +15 -0
- package/dist/video/video-player.d.ts +1 -1
- package/dist/withProGate-BWqcKdPM.js +137 -0
- package/dist/withProGate-DX6XqKLp.cjs +1 -0
- package/package.json +34 -220
- package/src/assessment-toolbar/question-navigator.tsx +10 -5
- package/src/assessment-toolbar/timer-display.tsx +4 -3
- package/src/assessment-toolbar/use-countdown.ts +1 -1
- package/src/common/empty-state.tsx +1 -0
- package/src/common/index.ts +2 -0
- package/src/common/pagination.tsx +135 -0
- package/src/common/search-input.tsx +2 -1
- package/src/common/types.ts +2 -0
- package/src/content/attachment-list.tsx +2 -0
- package/src/content/audio-player.tsx +196 -0
- package/src/content/code-block.tsx +113 -0
- package/src/content/content-block.tsx +64 -0
- package/src/content/embed-block.tsx +78 -0
- package/src/content/file-upload-zone.tsx +10 -0
- package/src/content/index.ts +6 -0
- package/src/content/types.ts +5 -0
- package/src/curriculum/course-card.tsx +199 -0
- package/src/curriculum/curriculum-item.tsx +3 -3
- package/src/curriculum/curriculum-tree.tsx +20 -13
- package/src/curriculum/index.ts +2 -0
- package/src/curriculum/types.ts +2 -2
- package/src/flashcards/flashcard.tsx +28 -8
- package/src/index.ts +3 -0
- package/src/license/HydraContext.tsx +62 -0
- package/src/license/ProBadge.tsx +43 -0
- package/src/license/index.ts +7 -0
- package/src/license/tiers.ts +24 -0
- package/src/license/useHydraLicense.ts +10 -0
- package/src/license/validate.ts +90 -0
- package/src/license/withProGate.tsx +21 -0
- package/src/modules/AssignmentModule/AssignmentModule.tsx +17 -8
- package/src/modules/AssignmentModule/types.ts +5 -1
- package/src/modules/CertificateModule/CertificateModule.tsx +21 -9
- package/src/modules/CertificateModule/types.ts +6 -4
- package/src/modules/CourseCatalogModule/CourseCatalogModule.tsx +126 -0
- package/src/modules/CourseCatalogModule/types.ts +47 -0
- package/src/modules/CoursePlayer/CoursePlayer.tsx +37 -22
- package/src/modules/DiscussionModule/DiscussionModule.tsx +57 -22
- package/src/modules/ExamModule/ExamModule.tsx +64 -198
- package/src/modules/ExamModule/types.ts +5 -14
- package/src/modules/FlashcardLab/FlashcardLab.tsx +10 -5
- package/src/modules/FlashcardLab/types.ts +2 -0
- package/src/modules/GradeCenterModule/GradeCenterModule.tsx +7 -2
- package/src/modules/GradeCenterModule/types.ts +2 -0
- package/src/modules/QuizModule/QuizModule.tsx +49 -169
- package/src/modules/QuizModule/types.ts +5 -15
- package/src/modules/StudentDashboardModule/StudentDashboardModule.tsx +117 -0
- package/src/modules/StudentDashboardModule/types.ts +56 -0
- package/src/modules/StudentProfileModule/StudentProfileModule.tsx +289 -0
- package/src/modules/StudentProfileModule/types.ts +45 -0
- package/src/modules/SurveyModule/SurveyModule.tsx +9 -4
- package/src/modules/SurveyModule/types.ts +2 -0
- package/src/modules/_shared/assessment-intro.tsx +75 -0
- package/src/modules/_shared/assessment-results.tsx +133 -0
- package/src/modules/_shared/types.ts +11 -0
- package/src/modules/_shared/use-timer.ts +49 -0
- package/src/modules/index.ts +9 -0
- package/src/progress/achievement-badge.tsx +3 -3
- package/src/progress/grade-indicator.tsx +9 -1
- package/src/progress/progress-ring.tsx +2 -1
- package/src/progress/stat-card.tsx +8 -1
- package/src/progress/types.ts +2 -0
- package/src/provider/HydraProvider.tsx +15 -6
- package/src/questions/choice.tsx +13 -6
- package/src/questions/confidence-indicator.tsx +107 -0
- package/src/questions/essay.tsx +6 -4
- package/src/questions/fill-in-the-blank.tsx +8 -4
- package/src/questions/hotspot.tsx +4 -4
- package/src/questions/index.ts +2 -0
- package/src/questions/inline-choice.tsx +5 -4
- package/src/questions/matching.tsx +5 -4
- package/src/questions/multiple-choice.tsx +13 -6
- package/src/questions/numeric.tsx +8 -4
- package/src/questions/ordering.tsx +12 -4
- package/src/questions/question-renderer.tsx +3 -2
- package/src/questions/scenario.tsx +4 -4
- package/src/questions/spreadsheet.tsx +5 -4
- package/src/questions/true-false.tsx +13 -6
- package/src/sections/AnnouncementFeed/AnnouncementFeed.tsx +64 -8
- package/src/sections/AnnouncementFeed/types.ts +15 -1
- package/src/sections/AssessmentReview/AssessmentReview.tsx +37 -0
- package/src/sections/AssessmentReview/types.ts +6 -0
- package/src/sections/AssignmentSubmission/AssignmentSubmission.tsx +37 -1
- package/src/sections/AssignmentSubmission/types.ts +6 -0
- package/src/sections/CertificateViewer/CertificateViewer.tsx +29 -227
- package/src/sections/CertificateViewer/certificate-variants.tsx +170 -0
- package/src/sections/CertificateViewer/types.ts +6 -0
- package/src/sections/CourseCatalog/CourseCatalog.tsx +220 -0
- package/src/sections/CourseCatalog/types.ts +76 -0
- package/src/sections/CourseOutline/CourseOutline.tsx +41 -0
- package/src/sections/CourseOutline/types.ts +6 -0
- package/src/sections/DiscussionThread/DiscussionThread.tsx +42 -1
- package/src/sections/DiscussionThread/types.ts +6 -0
- package/src/sections/EnrollmentWizard/EnrollmentWizard.tsx +343 -0
- package/src/sections/EnrollmentWizard/types.ts +65 -0
- package/src/sections/ExamSession/ExamSession.tsx +100 -94
- package/src/sections/ExamSession/types.ts +6 -0
- package/src/sections/FlashcardStudySession/FlashcardStudySession.tsx +53 -36
- package/src/sections/FlashcardStudySession/types.ts +6 -0
- package/src/sections/ForumBoard/ForumBoard.tsx +59 -1
- package/src/sections/ForumBoard/types.ts +14 -0
- package/src/sections/GradebookTable/GradebookTable.tsx +54 -1
- package/src/sections/GradebookTable/types.ts +14 -0
- package/src/sections/LecturePlayer/LecturePlayer.tsx +63 -37
- package/src/sections/LecturePlayer/types.ts +8 -0
- package/src/sections/LessonPage/LessonPage.tsx +36 -5
- package/src/sections/LessonPage/types.ts +6 -0
- package/src/sections/PracticeQuiz/PracticeQuiz.tsx +106 -74
- package/src/sections/PracticeQuiz/types.ts +6 -0
- package/src/sections/ProgressDashboard/ProgressDashboard.tsx +64 -10
- package/src/sections/ProgressDashboard/types.ts +6 -0
- package/src/sections/QuizSession/QuizSession.tsx +71 -82
- package/src/sections/QuizSession/types.ts +6 -0
- package/src/sections/RequirementsChecklist/RequirementsChecklist.tsx +41 -1
- package/src/sections/RequirementsChecklist/types.ts +6 -0
- package/src/sections/ResourceLibrary/ResourceLibrary.tsx +64 -8
- package/src/sections/ResourceLibrary/types.ts +15 -1
- package/src/sections/RubricView/RubricView.tsx +37 -1
- package/src/sections/RubricView/types.ts +6 -0
- package/src/sections/ScrollableQuiz/ScrollableQuiz.tsx +36 -15
- package/src/sections/ScrollableQuiz/types.ts +6 -0
- package/src/sections/StudentProfile/StudentProfile.tsx +279 -0
- package/src/sections/StudentProfile/types.ts +99 -0
- package/src/sections/SurveyForm/SurveyForm.tsx +32 -5
- package/src/sections/SurveyForm/types.ts +6 -0
- package/src/sections/_shared/merge-answers.ts +22 -0
- package/src/sections/_shared/section-shell.tsx +64 -0
- package/src/sections/_shared/use-assessment-session.ts +125 -0
- package/src/sections/index.ts +22 -0
- package/src/social/user-avatar.tsx +9 -5
- package/src/styles/globals.css +39 -41
- package/src/ui/badge.tsx +8 -0
- package/src/ui/index.ts +2 -0
- package/src/ui/progress.tsx +4 -0
- package/src/ui/rich-text-editor.tsx +10 -0
- package/src/ui/rich-text-toolbar.tsx +2 -1
- package/src/ui/toast.tsx +170 -0
- package/src/utils/debounce.ts +8 -2
- package/src/utils/pick-palette-color.ts +33 -0
- package/src/video/types.ts +16 -0
- package/src/video/video-player.tsx +13 -1
- package/dist/ForumBoard-CHXU3mjC.js +0 -2207
- package/dist/ForumBoard-d1w5-r6n.cjs +0 -1
- package/dist/tabs-DRM2Iq_J.cjs +0 -172
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { useState, useRef, useEffect } from "react";
|
|
1
|
+
import { useCallback, useState, useRef, useEffect } from "react";
|
|
2
2
|
import { ArrowLeft } from "lucide-react";
|
|
3
3
|
import { ForumBoard } from "../../sections/ForumBoard/ForumBoard";
|
|
4
4
|
import { DiscussionThread } from "../../sections/DiscussionThread/DiscussionThread";
|
|
5
|
+
import { EmptyState } from "../../common";
|
|
5
6
|
import { Button } from "../../ui/button";
|
|
6
7
|
import { cn } from "../../lib/utils";
|
|
7
8
|
import type { ForumSortOrder } from "../../sections/ForumBoard/types";
|
|
9
|
+
import { withProGate } from "../../license/withProGate";
|
|
8
10
|
import type { DiscussionModuleProps } from "./types";
|
|
9
11
|
|
|
10
12
|
/**
|
|
@@ -13,11 +15,11 @@ import type { DiscussionModuleProps } from "./types";
|
|
|
13
15
|
* Panel-based layout: ForumBoard (topic listing) ↔ DiscussionThread (thread detail).
|
|
14
16
|
* Clicking a topic drills into the thread; a back button returns to the board.
|
|
15
17
|
*/
|
|
16
|
-
|
|
18
|
+
function DiscussionModuleBase({
|
|
17
19
|
forumTitle,
|
|
18
|
-
topics,
|
|
20
|
+
topics = [],
|
|
19
21
|
currentUser,
|
|
20
|
-
threads,
|
|
22
|
+
threads = {},
|
|
21
23
|
onCreateTopic,
|
|
22
24
|
onReply,
|
|
23
25
|
onToggleLike,
|
|
@@ -36,20 +38,44 @@ export function DiscussionModule({
|
|
|
36
38
|
contentRef.current?.focus({ preventScroll: true });
|
|
37
39
|
}, [activeTopicUid]);
|
|
38
40
|
|
|
39
|
-
|
|
41
|
+
const onTopicOpenRef = useRef(onTopicOpen);
|
|
42
|
+
onTopicOpenRef.current = onTopicOpen;
|
|
43
|
+
|
|
44
|
+
const handleTopicClick = useCallback((topicUid: string) => {
|
|
40
45
|
setActiveTopicUid(topicUid);
|
|
41
|
-
|
|
42
|
-
}
|
|
46
|
+
onTopicOpenRef.current?.(topicUid);
|
|
47
|
+
}, []);
|
|
43
48
|
|
|
44
|
-
|
|
49
|
+
const handleBack = useCallback(() => {
|
|
45
50
|
setActiveTopicUid(null);
|
|
46
|
-
}
|
|
51
|
+
}, []);
|
|
47
52
|
|
|
48
53
|
const activeThread = activeTopicUid ? threads[activeTopicUid] : null;
|
|
49
54
|
const activeTopic = activeTopicUid
|
|
50
55
|
? topics.find((t) => t.uid === activeTopicUid)
|
|
51
56
|
: null;
|
|
52
57
|
|
|
58
|
+
const handleReply = useCallback(
|
|
59
|
+
(parentUid: string, content: string) => {
|
|
60
|
+
if (activeTopicUid) onReply?.(activeTopicUid, parentUid, content);
|
|
61
|
+
},
|
|
62
|
+
[activeTopicUid, onReply],
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const handleToggleLike = useCallback(
|
|
66
|
+
(postUid: string) => {
|
|
67
|
+
if (activeTopicUid) onToggleLike?.(activeTopicUid, postUid);
|
|
68
|
+
},
|
|
69
|
+
[activeTopicUid, onToggleLike],
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const handleMarkAnswer = useCallback(
|
|
73
|
+
(postUid: string) => {
|
|
74
|
+
if (activeTopicUid) onMarkAnswer?.(activeTopicUid, postUid);
|
|
75
|
+
},
|
|
76
|
+
[activeTopicUid, onMarkAnswer],
|
|
77
|
+
);
|
|
78
|
+
|
|
53
79
|
return (
|
|
54
80
|
<div
|
|
55
81
|
ref={contentRef}
|
|
@@ -74,22 +100,29 @@ export function DiscussionModule({
|
|
|
74
100
|
rootPost={activeThread.rootPost}
|
|
75
101
|
replies={activeThread.replies}
|
|
76
102
|
currentUser={currentUser}
|
|
77
|
-
onReply={
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
onToggleLike={
|
|
81
|
-
onToggleLike
|
|
82
|
-
? (postUid) => onToggleLike(activeTopicUid, postUid)
|
|
83
|
-
: undefined
|
|
84
|
-
}
|
|
85
|
-
onMarkAnswer={
|
|
86
|
-
onMarkAnswer
|
|
87
|
-
? (postUid) => onMarkAnswer(activeTopicUid, postUid)
|
|
88
|
-
: undefined
|
|
89
|
-
}
|
|
103
|
+
onReply={handleReply}
|
|
104
|
+
onToggleLike={onToggleLike ? handleToggleLike : undefined}
|
|
105
|
+
onMarkAnswer={onMarkAnswer ? handleMarkAnswer : undefined}
|
|
90
106
|
readOnly={readOnly}
|
|
91
107
|
/>
|
|
92
108
|
</div>
|
|
109
|
+
) : activeTopicUid && !activeThread ? (
|
|
110
|
+
/* ─── Empty Thread State ─── */
|
|
111
|
+
<div>
|
|
112
|
+
<Button
|
|
113
|
+
variant="ghost"
|
|
114
|
+
size="sm"
|
|
115
|
+
onClick={handleBack}
|
|
116
|
+
className="mb-4"
|
|
117
|
+
>
|
|
118
|
+
<ArrowLeft className="size-4 mr-1.5" />
|
|
119
|
+
Back to Topics
|
|
120
|
+
</Button>
|
|
121
|
+
<EmptyState
|
|
122
|
+
title="No discussion yet"
|
|
123
|
+
description="Be the first to start this conversation."
|
|
124
|
+
/>
|
|
125
|
+
</div>
|
|
93
126
|
) : (
|
|
94
127
|
/* ─── Forum Board View ─── */
|
|
95
128
|
<ForumBoard
|
|
@@ -108,3 +141,5 @@ export function DiscussionModule({
|
|
|
108
141
|
</div>
|
|
109
142
|
);
|
|
110
143
|
}
|
|
144
|
+
|
|
145
|
+
export const DiscussionModule = withProGate(DiscussionModuleBase, "DiscussionModule");
|
|
@@ -1,28 +1,16 @@
|
|
|
1
|
-
import { useState, useRef, useEffect } from "react";
|
|
2
|
-
import {
|
|
3
|
-
ShieldCheck,
|
|
4
|
-
RotateCcw,
|
|
5
|
-
Clock,
|
|
6
|
-
HelpCircle,
|
|
7
|
-
CheckCircle2,
|
|
8
|
-
XCircle,
|
|
9
|
-
Trophy,
|
|
10
|
-
Play,
|
|
11
|
-
} from "lucide-react";
|
|
1
|
+
import { useState, useRef, useEffect, useCallback } from "react";
|
|
2
|
+
import { ShieldCheck } from "lucide-react";
|
|
12
3
|
import { ExamSession } from "../../sections/ExamSession/ExamSession";
|
|
13
|
-
import { AssessmentReview } from "../../sections/AssessmentReview/AssessmentReview";
|
|
14
|
-
import { ProgressRing } from "../../progress/progress-ring";
|
|
15
|
-
import { StatCard } from "../../progress/stat-card";
|
|
16
|
-
import { Button } from "../../ui/button";
|
|
17
4
|
import { Badge } from "../../ui/badge";
|
|
18
|
-
import { Card, CardContent } from "../../ui/card";
|
|
19
5
|
import { Alert, AlertDescription } from "../../ui/alert";
|
|
20
|
-
import { Separator } from "../../ui/separator";
|
|
21
|
-
import { formatDuration } from "../../utils/format-duration";
|
|
22
6
|
import { cn } from "../../lib/utils";
|
|
23
7
|
import type { SessionAnswer } from "../../questions/types";
|
|
24
8
|
import type { ExamSubmitMetadata } from "../../sections/ExamSession/types";
|
|
25
9
|
import { scoreAssessment } from "../../questions/scoring";
|
|
10
|
+
import { useTimer } from "../_shared/use-timer";
|
|
11
|
+
import { AssessmentIntro } from "../_shared/assessment-intro";
|
|
12
|
+
import { AssessmentResults } from "../_shared/assessment-results";
|
|
13
|
+
import { withProGate } from "../../license/withProGate";
|
|
26
14
|
import type { ExamModuleProps, ExamModuleResult } from "./types";
|
|
27
15
|
|
|
28
16
|
type InternalStep =
|
|
@@ -36,11 +24,11 @@ type InternalStep =
|
|
|
36
24
|
* Steps: Intro (rules/instructions) → Exam (timed ExamSession) → Results (score + review).
|
|
37
25
|
* Manages an external timer that feeds elapsed time to ExamSession.
|
|
38
26
|
*/
|
|
39
|
-
|
|
27
|
+
function ExamModuleBase({
|
|
40
28
|
title,
|
|
41
29
|
description,
|
|
42
30
|
instructions,
|
|
43
|
-
questions,
|
|
31
|
+
questions = [],
|
|
44
32
|
timeLimitSeconds,
|
|
45
33
|
passingScore,
|
|
46
34
|
allowBackNavigation = true,
|
|
@@ -49,45 +37,24 @@ export function ExamModule({
|
|
|
49
37
|
allowRetake = false,
|
|
50
38
|
showReview = true,
|
|
51
39
|
onComplete,
|
|
40
|
+
readOnly = false,
|
|
52
41
|
className,
|
|
53
42
|
style,
|
|
54
43
|
}: ExamModuleProps) {
|
|
55
44
|
const [step, setStep] = useState<InternalStep>({ tag: "intro" });
|
|
56
|
-
const [timeElapsed, setTimeElapsed] = useState(0);
|
|
57
|
-
const startTimeRef = useRef<number | null>(null);
|
|
58
|
-
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
59
45
|
const contentRef = useRef<HTMLDivElement>(null);
|
|
46
|
+
const { timeElapsed, reset: resetTimer } = useTimer(step.tag === "exam");
|
|
60
47
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}, [step.tag]);
|
|
48
|
+
const onCompleteRef = useRef(onComplete);
|
|
49
|
+
onCompleteRef.current = onComplete;
|
|
64
50
|
|
|
65
|
-
// Timer for exam step
|
|
66
51
|
useEffect(() => {
|
|
67
|
-
|
|
68
|
-
startTimeRef.current = Date.now();
|
|
69
|
-
intervalRef.current = setInterval(() => {
|
|
70
|
-
if (startTimeRef.current) {
|
|
71
|
-
setTimeElapsed(Math.floor((Date.now() - startTimeRef.current) / 1000));
|
|
72
|
-
}
|
|
73
|
-
}, 1000);
|
|
74
|
-
} else {
|
|
75
|
-
if (intervalRef.current) {
|
|
76
|
-
clearInterval(intervalRef.current);
|
|
77
|
-
intervalRef.current = null;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return () => {
|
|
81
|
-
if (intervalRef.current) clearInterval(intervalRef.current);
|
|
82
|
-
};
|
|
52
|
+
contentRef.current?.focus({ preventScroll: true });
|
|
83
53
|
}, [step.tag]);
|
|
84
54
|
|
|
85
|
-
|
|
86
|
-
answers: SessionAnswer[],
|
|
87
|
-
metadata: ExamSubmitMetadata
|
|
88
|
-
): ExamModuleResult {
|
|
55
|
+
const handleSubmit = useCallback((answers: SessionAnswer[], metadata: ExamSubmitMetadata) => {
|
|
89
56
|
const { correct, total, percentage } = scoreAssessment(questions, answers);
|
|
90
|
-
|
|
57
|
+
const result: ExamModuleResult = {
|
|
91
58
|
answers,
|
|
92
59
|
correct,
|
|
93
60
|
total,
|
|
@@ -96,72 +63,36 @@ export function ExamModule({
|
|
|
96
63
|
timeElapsedSeconds: metadata.timeElapsedSeconds,
|
|
97
64
|
wasAutoSubmitted: metadata.wasAutoSubmitted,
|
|
98
65
|
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function handleSubmit(answers: SessionAnswer[], metadata: ExamSubmitMetadata) {
|
|
102
|
-
const result = scoreAnswers(answers, metadata);
|
|
103
66
|
setStep({ tag: "results", result });
|
|
104
|
-
|
|
105
|
-
}
|
|
67
|
+
onCompleteRef.current?.(result);
|
|
68
|
+
}, [questions, passingScore]);
|
|
106
69
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
startTimeRef.current = null;
|
|
70
|
+
const handleRetake = useCallback(() => {
|
|
71
|
+
resetTimer();
|
|
110
72
|
setStep({ tag: "intro" });
|
|
111
|
-
}
|
|
73
|
+
}, [resetTimer]);
|
|
112
74
|
|
|
113
75
|
// ─── Intro Screen ───
|
|
114
76
|
if (step.tag === "intro") {
|
|
115
77
|
return (
|
|
116
|
-
<div
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
{/* Instructions */}
|
|
135
|
-
{instructions && (
|
|
136
|
-
<Alert className="text-left mb-6">
|
|
137
|
-
<AlertDescription>{instructions}</AlertDescription>
|
|
138
|
-
</Alert>
|
|
139
|
-
)}
|
|
140
|
-
|
|
141
|
-
{/* Metadata chips */}
|
|
142
|
-
<div className="flex flex-wrap justify-center gap-2 mb-8">
|
|
143
|
-
<Badge variant="outline" className="gap-1.5">
|
|
144
|
-
<HelpCircle className="size-3.5" />
|
|
145
|
-
{questions.length} questions
|
|
146
|
-
</Badge>
|
|
147
|
-
<Badge variant="outline" className="gap-1.5">
|
|
148
|
-
<Clock className="size-3.5" />
|
|
149
|
-
{formatDuration(timeLimitSeconds)} time limit
|
|
150
|
-
</Badge>
|
|
151
|
-
{passingScore !== undefined && (
|
|
152
|
-
<Badge variant="outline" className="gap-1.5">
|
|
153
|
-
<CheckCircle2 className="size-3.5" />
|
|
154
|
-
{passingScore}% to pass
|
|
155
|
-
</Badge>
|
|
156
|
-
)}
|
|
157
|
-
</div>
|
|
158
|
-
|
|
159
|
-
<Button size="lg" onClick={() => setStep({ tag: "exam" })}>
|
|
160
|
-
<Play className="size-4 mr-2" />
|
|
161
|
-
Begin Exam
|
|
162
|
-
</Button>
|
|
163
|
-
</CardContent>
|
|
164
|
-
</Card>
|
|
78
|
+
<div ref={contentRef} tabIndex={-1} className={cn("max-w-2xl mx-auto outline-none", className)} style={style}>
|
|
79
|
+
<AssessmentIntro
|
|
80
|
+
icon={<ShieldCheck className="size-7 text-primary" />}
|
|
81
|
+
title={title}
|
|
82
|
+
description={description}
|
|
83
|
+
questionCount={questions.length}
|
|
84
|
+
timeLimitSeconds={timeLimitSeconds}
|
|
85
|
+
passingScore={passingScore}
|
|
86
|
+
startLabel="Begin Exam"
|
|
87
|
+
onStart={() => setStep({ tag: "exam" })}
|
|
88
|
+
readOnly={readOnly}
|
|
89
|
+
>
|
|
90
|
+
{instructions && (
|
|
91
|
+
<Alert className="text-left mb-6">
|
|
92
|
+
<AlertDescription>{instructions}</AlertDescription>
|
|
93
|
+
</Alert>
|
|
94
|
+
)}
|
|
95
|
+
</AssessmentIntro>
|
|
165
96
|
</div>
|
|
166
97
|
);
|
|
167
98
|
}
|
|
@@ -169,12 +100,7 @@ export function ExamModule({
|
|
|
169
100
|
// ─── Exam Screen ───
|
|
170
101
|
if (step.tag === "exam") {
|
|
171
102
|
return (
|
|
172
|
-
<div
|
|
173
|
-
ref={contentRef}
|
|
174
|
-
tabIndex={-1}
|
|
175
|
-
className={cn("outline-none", className)}
|
|
176
|
-
style={style}
|
|
177
|
-
>
|
|
103
|
+
<div ref={contentRef} tabIndex={-1} className={cn("outline-none", className)} style={style}>
|
|
178
104
|
<ExamSession
|
|
179
105
|
questions={questions}
|
|
180
106
|
timeLimitSeconds={timeLimitSeconds}
|
|
@@ -185,6 +111,7 @@ export function ExamModule({
|
|
|
185
111
|
confirmBeforeSubmit
|
|
186
112
|
examTitle={title}
|
|
187
113
|
onSubmit={handleSubmit}
|
|
114
|
+
readOnly={readOnly}
|
|
188
115
|
/>
|
|
189
116
|
</div>
|
|
190
117
|
);
|
|
@@ -192,94 +119,33 @@ export function ExamModule({
|
|
|
192
119
|
|
|
193
120
|
// ─── Results Screen ───
|
|
194
121
|
const { result } = step;
|
|
195
|
-
const passed = result.passed;
|
|
196
122
|
|
|
197
123
|
return (
|
|
198
|
-
<div
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
<Badge
|
|
216
|
-
|
|
217
|
-
className="text-sm px-3 py-1 mb-2"
|
|
218
|
-
>
|
|
219
|
-
{passed ? "Passed" : "Failed"}
|
|
124
|
+
<div ref={contentRef} tabIndex={-1} className={cn("max-w-2xl mx-auto outline-none", className)} style={style}>
|
|
125
|
+
<AssessmentResults
|
|
126
|
+
title={title}
|
|
127
|
+
percentage={result.percentage}
|
|
128
|
+
passed={result.passed}
|
|
129
|
+
correct={result.correct}
|
|
130
|
+
total={result.total}
|
|
131
|
+
timeElapsedSeconds={result.timeElapsedSeconds}
|
|
132
|
+
answers={result.answers}
|
|
133
|
+
questions={questions}
|
|
134
|
+
allowRetake={allowRetake}
|
|
135
|
+
onRetake={handleRetake}
|
|
136
|
+
retakeLabel="Retake Exam"
|
|
137
|
+
showReview={showReview}
|
|
138
|
+
readOnly={readOnly}
|
|
139
|
+
extraBadges={
|
|
140
|
+
result.wasAutoSubmitted ? (
|
|
141
|
+
<Badge variant="outline" className="text-sm px-3 py-1 mb-2 ml-2">
|
|
142
|
+
Auto-submitted
|
|
220
143
|
</Badge>
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
</Badge>
|
|
225
|
-
)}
|
|
226
|
-
<h2 className="text-xl font-bold text-foreground">{title}</h2>
|
|
227
|
-
</div>
|
|
228
|
-
|
|
229
|
-
{/* Stats grid */}
|
|
230
|
-
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3 mb-8">
|
|
231
|
-
<StatCard
|
|
232
|
-
icon={<CheckCircle2 />}
|
|
233
|
-
label="Correct"
|
|
234
|
-
description="Questions answered right"
|
|
235
|
-
value={`${result.correct}/${result.total}`}
|
|
236
|
-
/>
|
|
237
|
-
<StatCard
|
|
238
|
-
icon={<XCircle />}
|
|
239
|
-
label="Incorrect"
|
|
240
|
-
description="Questions to review"
|
|
241
|
-
value={`${result.total - result.correct}/${result.total}`}
|
|
242
|
-
/>
|
|
243
|
-
<StatCard
|
|
244
|
-
icon={<Trophy />}
|
|
245
|
-
label="Score"
|
|
246
|
-
description="Overall percentage"
|
|
247
|
-
value={`${result.percentage}%`}
|
|
248
|
-
/>
|
|
249
|
-
<StatCard
|
|
250
|
-
icon={<Clock />}
|
|
251
|
-
label="Time"
|
|
252
|
-
description="Total elapsed"
|
|
253
|
-
value={formatDuration(result.timeElapsedSeconds)}
|
|
254
|
-
/>
|
|
255
|
-
</div>
|
|
256
|
-
|
|
257
|
-
{/* Actions */}
|
|
258
|
-
{allowRetake && (
|
|
259
|
-
<div className="flex justify-center mb-8">
|
|
260
|
-
<Button variant="outline" onClick={handleRetake}>
|
|
261
|
-
<RotateCcw className="size-4 mr-2" />
|
|
262
|
-
Retake Exam
|
|
263
|
-
</Button>
|
|
264
|
-
</div>
|
|
265
|
-
)}
|
|
266
|
-
</CardContent>
|
|
267
|
-
</Card>
|
|
268
|
-
|
|
269
|
-
{/* Per-question review */}
|
|
270
|
-
{showReview && (
|
|
271
|
-
<>
|
|
272
|
-
<Separator className="my-6" />
|
|
273
|
-
<h3 className="text-lg font-semibold text-foreground mb-4">
|
|
274
|
-
Question Review
|
|
275
|
-
</h3>
|
|
276
|
-
<AssessmentReview
|
|
277
|
-
questions={questions}
|
|
278
|
-
sessionAnswers={result.answers}
|
|
279
|
-
showCorrectAnswers
|
|
280
|
-
/>
|
|
281
|
-
</>
|
|
282
|
-
)}
|
|
144
|
+
) : null
|
|
145
|
+
}
|
|
146
|
+
/>
|
|
283
147
|
</div>
|
|
284
148
|
);
|
|
285
149
|
}
|
|
150
|
+
|
|
151
|
+
export const ExamModule = withProGate(ExamModuleBase, "ExamModule");
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
import type { QuestionData
|
|
2
|
+
import type { QuestionData } from "../../questions/types";
|
|
3
|
+
import type { AssessmentResult } from "../_shared/types";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* ExamModule — a formal timed exam experience with intro, exam, and results steps.
|
|
@@ -42,25 +43,15 @@ export interface ExamModuleProps {
|
|
|
42
43
|
showReview?: boolean;
|
|
43
44
|
/** Called when the exam is completed (submitted) */
|
|
44
45
|
onComplete?: (result: ExamModuleResult) => void;
|
|
46
|
+
/** When true, disables interactions for preview/demo mode. @default false */
|
|
47
|
+
readOnly?: boolean;
|
|
45
48
|
/** CSS class name for the root element */
|
|
46
49
|
className?: string;
|
|
47
50
|
/** Inline styles for the root element */
|
|
48
51
|
style?: React.CSSProperties;
|
|
49
52
|
}
|
|
50
53
|
|
|
51
|
-
export interface ExamModuleResult {
|
|
52
|
-
/** The user's submitted answers */
|
|
53
|
-
answers: SessionAnswer[];
|
|
54
|
-
/** Number of correct answers */
|
|
55
|
-
correct: number;
|
|
56
|
-
/** Total number of gradable questions */
|
|
57
|
-
total: number;
|
|
58
|
-
/** Score as a percentage (0-100) */
|
|
59
|
-
percentage: number;
|
|
60
|
-
/** Whether the user passed (only meaningful when passingScore is set) */
|
|
61
|
-
passed: boolean;
|
|
62
|
-
/** Total time taken in seconds */
|
|
63
|
-
timeElapsedSeconds: number;
|
|
54
|
+
export interface ExamModuleResult extends AssessmentResult {
|
|
64
55
|
/** Whether the submission was triggered by timeout */
|
|
65
56
|
wasAutoSubmitted: boolean;
|
|
66
57
|
}
|
|
@@ -17,6 +17,7 @@ import { Card, CardContent } from "../../ui/card";
|
|
|
17
17
|
import { formatDuration } from "../../utils/format-duration";
|
|
18
18
|
import { cn } from "../../lib/utils";
|
|
19
19
|
import type { FlashcardData } from "../../flashcards/types";
|
|
20
|
+
import { withProGate } from "../../license/withProGate";
|
|
20
21
|
import type {
|
|
21
22
|
FlashcardLabProps,
|
|
22
23
|
FlashcardLabResult,
|
|
@@ -27,12 +28,13 @@ type InternalStep =
|
|
|
27
28
|
| { tag: "study"; cards: FlashcardData[]; deckUids: string[]; shuffled: boolean }
|
|
28
29
|
| { tag: "completion"; result: FlashcardLabResult };
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
decks,
|
|
31
|
+
function FlashcardLabBase({
|
|
32
|
+
decks = [],
|
|
32
33
|
showShuffleToggle = true,
|
|
33
34
|
defaultShuffled = false,
|
|
34
35
|
allowMultiSelect = true,
|
|
35
36
|
onComplete,
|
|
37
|
+
readOnly = false,
|
|
36
38
|
className,
|
|
37
39
|
style,
|
|
38
40
|
}: FlashcardLabProps) {
|
|
@@ -187,7 +189,7 @@ export function FlashcardLab({
|
|
|
187
189
|
</div>
|
|
188
190
|
<Button
|
|
189
191
|
onClick={startStudying}
|
|
190
|
-
disabled={selectedUids.size === 0}
|
|
192
|
+
disabled={selectedUids.size === 0 || readOnly}
|
|
191
193
|
>
|
|
192
194
|
Start Studying
|
|
193
195
|
</Button>
|
|
@@ -212,6 +214,7 @@ export function FlashcardLab({
|
|
|
212
214
|
title={deckNames}
|
|
213
215
|
shuffled={step.shuffled}
|
|
214
216
|
onComplete={handleStudyComplete}
|
|
217
|
+
readOnly={readOnly}
|
|
215
218
|
/>
|
|
216
219
|
</div>
|
|
217
220
|
);
|
|
@@ -272,11 +275,11 @@ export function FlashcardLab({
|
|
|
272
275
|
</div>
|
|
273
276
|
|
|
274
277
|
<div className="flex justify-center gap-3">
|
|
275
|
-
<Button variant="outline" onClick={handlePickNewDeck}>
|
|
278
|
+
<Button variant="outline" onClick={handlePickNewDeck} disabled={readOnly}>
|
|
276
279
|
<ArrowLeft className="size-4 mr-2" />
|
|
277
280
|
Pick New Deck
|
|
278
281
|
</Button>
|
|
279
|
-
<Button onClick={handleStudyAgain}>
|
|
282
|
+
<Button onClick={handleStudyAgain} disabled={readOnly}>
|
|
280
283
|
<RotateCcw className="size-4 mr-2" />
|
|
281
284
|
Study Again
|
|
282
285
|
</Button>
|
|
@@ -286,3 +289,5 @@ export function FlashcardLab({
|
|
|
286
289
|
</div>
|
|
287
290
|
);
|
|
288
291
|
}
|
|
292
|
+
|
|
293
|
+
export const FlashcardLab = withProGate(FlashcardLabBase, "FlashcardLab");
|
|
@@ -27,6 +27,8 @@ export interface FlashcardLabProps {
|
|
|
27
27
|
allowMultiSelect?: boolean;
|
|
28
28
|
/** Called when the user completes a study session */
|
|
29
29
|
onComplete?: (result: FlashcardLabResult) => void;
|
|
30
|
+
/** When true, disables interactions for preview/demo mode. @default false */
|
|
31
|
+
readOnly?: boolean;
|
|
30
32
|
/** CSS class name for the root element */
|
|
31
33
|
className?: string;
|
|
32
34
|
/** Inline styles for the root element */
|
|
@@ -10,6 +10,7 @@ import { Tabs, TabsList, TabsTrigger, TabsContent } from "../../ui/tabs";
|
|
|
10
10
|
import { Separator } from "../../ui/separator";
|
|
11
11
|
import { cn } from "../../lib/utils";
|
|
12
12
|
import type { GradeItem } from "../../sections/GradebookTable/types";
|
|
13
|
+
import { withProGate } from "../../license/withProGate";
|
|
13
14
|
import type { GradeCenterModuleProps } from "./types";
|
|
14
15
|
|
|
15
16
|
/**
|
|
@@ -19,14 +20,15 @@ import type { GradeCenterModuleProps } from "./types";
|
|
|
19
20
|
* Uses a panel-based layout (like CoursePlayer) with tabs for Grades and Progress,
|
|
20
21
|
* and a slide-in detail panel for reviewing individual assessments.
|
|
21
22
|
*/
|
|
22
|
-
|
|
23
|
+
function GradeCenterModuleBase({
|
|
23
24
|
courseTitle,
|
|
24
|
-
gradeItems,
|
|
25
|
+
gradeItems = [],
|
|
25
26
|
categories,
|
|
26
27
|
overallGrade,
|
|
27
28
|
showWeights,
|
|
28
29
|
progressData,
|
|
29
30
|
reviewData,
|
|
31
|
+
readOnly = false,
|
|
30
32
|
className,
|
|
31
33
|
style,
|
|
32
34
|
}: GradeCenterModuleProps) {
|
|
@@ -147,6 +149,7 @@ export function GradeCenterModule({
|
|
|
147
149
|
overallGrade={overallGrade}
|
|
148
150
|
showWeights={showWeights}
|
|
149
151
|
onItemClick={handleItemClick}
|
|
152
|
+
readOnly={readOnly}
|
|
150
153
|
/>
|
|
151
154
|
</TabsContent>
|
|
152
155
|
|
|
@@ -167,3 +170,5 @@ export function GradeCenterModule({
|
|
|
167
170
|
</div>
|
|
168
171
|
);
|
|
169
172
|
}
|
|
173
|
+
|
|
174
|
+
export const GradeCenterModule = withProGate(GradeCenterModuleBase, "GradeCenterModule");
|
|
@@ -56,6 +56,8 @@ export interface GradeCenterModuleProps {
|
|
|
56
56
|
score?: AssessmentScore;
|
|
57
57
|
}
|
|
58
58
|
>;
|
|
59
|
+
/** When true, disables interactions for preview/demo mode. @default false */
|
|
60
|
+
readOnly?: boolean;
|
|
59
61
|
/** CSS class name for the root element */
|
|
60
62
|
className?: string;
|
|
61
63
|
/** Inline styles for the root element */
|