@schoolio/player 1.0.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/README.md +143 -0
- package/dist/index.d.mts +146 -0
- package/dist/index.d.ts +146 -0
- package/dist/index.js +602 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +595 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# @schoolio/player
|
|
2
|
+
|
|
3
|
+
A React component for loading and playing quizzes from Quiz Engine.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @schoolio/player
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { QuizPlayer } from '@schoolio/player';
|
|
15
|
+
|
|
16
|
+
function MyQuizPage() {
|
|
17
|
+
return (
|
|
18
|
+
<QuizPlayer
|
|
19
|
+
quizId="QZ123"
|
|
20
|
+
lessonId="L12"
|
|
21
|
+
assignLessonId="assignLesson123"
|
|
22
|
+
courseId="course456"
|
|
23
|
+
childId="child789"
|
|
24
|
+
parentId="parent012"
|
|
25
|
+
apiBaseUrl="https://your-quiz-engine-url.com"
|
|
26
|
+
onComplete={(result) => {
|
|
27
|
+
console.log('Quiz completed!', result);
|
|
28
|
+
// result contains: attemptId, score, correctAnswers, totalQuestions, answers, timeSpentSeconds
|
|
29
|
+
}}
|
|
30
|
+
onError={(error) => {
|
|
31
|
+
console.error('Quiz error:', error);
|
|
32
|
+
}}
|
|
33
|
+
onProgress={(progress) => {
|
|
34
|
+
console.log(`Question ${progress.currentQuestion} of ${progress.totalQuestions}`);
|
|
35
|
+
}}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Props
|
|
42
|
+
|
|
43
|
+
| Prop | Type | Required | Description |
|
|
44
|
+
|------|------|----------|-------------|
|
|
45
|
+
| `quizId` | string | Yes | The ID of the quiz to load |
|
|
46
|
+
| `lessonId` | string | Yes | Lesson ID from your app |
|
|
47
|
+
| `assignLessonId` | string | Yes | Assigned lesson ID for tracking |
|
|
48
|
+
| `courseId` | string | Yes | Course ID for tracking |
|
|
49
|
+
| `childId` | string | Yes | Student/child ID |
|
|
50
|
+
| `parentId` | string | Yes | Parent ID |
|
|
51
|
+
| `apiBaseUrl` | string | Yes | Base URL of your Quiz Engine API |
|
|
52
|
+
| `authToken` | string | No | Optional auth token for API requests |
|
|
53
|
+
| `onComplete` | function | No | Callback when quiz is completed |
|
|
54
|
+
| `onError` | function | No | Callback when an error occurs |
|
|
55
|
+
| `onProgress` | function | No | Callback on question navigation |
|
|
56
|
+
| `className` | string | No | CSS class for the container |
|
|
57
|
+
|
|
58
|
+
## Quiz Result
|
|
59
|
+
|
|
60
|
+
When the quiz is completed, the `onComplete` callback receives:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
interface QuizResult {
|
|
64
|
+
attemptId: string; // Unique attempt ID
|
|
65
|
+
score: number; // Percentage score (0-100)
|
|
66
|
+
correctAnswers: number; // Number of correct answers
|
|
67
|
+
totalQuestions: number; // Total questions in quiz
|
|
68
|
+
answers: QuizAnswerDetail[]; // Detailed answer data
|
|
69
|
+
timeSpentSeconds: number; // Time taken to complete
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Attempt Tracking
|
|
74
|
+
|
|
75
|
+
The component automatically:
|
|
76
|
+
- Creates a new attempt when started
|
|
77
|
+
- Saves progress as the student answers questions
|
|
78
|
+
- Resumes from where the student left off if they return
|
|
79
|
+
- Tracks attempt number (1st, 2nd, 3rd attempt, etc.)
|
|
80
|
+
- Records time spent on the quiz
|
|
81
|
+
|
|
82
|
+
## API Endpoints
|
|
83
|
+
|
|
84
|
+
The component expects these endpoints on your Quiz Engine:
|
|
85
|
+
|
|
86
|
+
- `GET /api/external/quizzes/:id` - Get quiz by ID
|
|
87
|
+
- `POST /api/external/quiz-attempts` - Create new attempt
|
|
88
|
+
- `PATCH /api/external/quiz-attempts/:id` - Update attempt
|
|
89
|
+
- `GET /api/external/quiz-attempts/:id` - Get attempt by ID
|
|
90
|
+
- `GET /api/external/quiz-attempts?assignLessonId=X&childId=Y` - Get attempts
|
|
91
|
+
|
|
92
|
+
## Customization
|
|
93
|
+
|
|
94
|
+
You can provide custom styling via the `className` prop and CSS:
|
|
95
|
+
|
|
96
|
+
```css
|
|
97
|
+
.my-quiz-player {
|
|
98
|
+
/* Custom container styles */
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.my-quiz-player .quiz-question {
|
|
102
|
+
/* Custom question styles */
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Using the API Client Directly
|
|
107
|
+
|
|
108
|
+
For more control, you can use the API client directly:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import { QuizApiClient } from '@schoolio/player';
|
|
112
|
+
|
|
113
|
+
const client = new QuizApiClient({
|
|
114
|
+
baseUrl: 'https://your-quiz-engine-url.com',
|
|
115
|
+
authToken: 'optional-auth-token',
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Fetch a quiz
|
|
119
|
+
const quiz = await client.getQuiz('quiz-id');
|
|
120
|
+
|
|
121
|
+
// Create an attempt
|
|
122
|
+
const attempt = await client.createAttempt({
|
|
123
|
+
quizId: 'quiz-id',
|
|
124
|
+
lessonId: 'lesson-id',
|
|
125
|
+
assignLessonId: 'assign-lesson-id',
|
|
126
|
+
courseId: 'course-id',
|
|
127
|
+
childId: 'child-id',
|
|
128
|
+
parentId: 'parent-id',
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Update attempt with answers
|
|
132
|
+
await client.updateAttempt(attempt.id, {
|
|
133
|
+
answers: [...],
|
|
134
|
+
status: 'completed',
|
|
135
|
+
score: 80,
|
|
136
|
+
correctAnswers: 8,
|
|
137
|
+
timeSpentSeconds: 300,
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## License
|
|
142
|
+
|
|
143
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
type QuestionType = "single" | "multiple" | "true-false" | "free" | "essay" | "fill" | "sorting" | "matrix" | "assessment";
|
|
4
|
+
interface QuizQuestion {
|
|
5
|
+
id: string;
|
|
6
|
+
question: string;
|
|
7
|
+
type: QuestionType;
|
|
8
|
+
points: number;
|
|
9
|
+
explanation?: string;
|
|
10
|
+
hint?: string;
|
|
11
|
+
imageUrl?: string;
|
|
12
|
+
options?: string[];
|
|
13
|
+
correctAnswer?: string | string[] | Record<string, string>;
|
|
14
|
+
blanks?: string[];
|
|
15
|
+
items?: string[];
|
|
16
|
+
correctOrder?: number[];
|
|
17
|
+
leftItems?: string[];
|
|
18
|
+
rightItems?: string[];
|
|
19
|
+
correctMatches?: Record<string, string>;
|
|
20
|
+
scaleType?: "likert" | "rating" | "yes-no";
|
|
21
|
+
scaleMin?: number;
|
|
22
|
+
scaleMax?: number;
|
|
23
|
+
}
|
|
24
|
+
interface Quiz {
|
|
25
|
+
id: string;
|
|
26
|
+
title: string;
|
|
27
|
+
grade: number;
|
|
28
|
+
questions: QuizQuestion[];
|
|
29
|
+
status: string;
|
|
30
|
+
}
|
|
31
|
+
interface QuizAnswerDetail {
|
|
32
|
+
questionId: string;
|
|
33
|
+
questionText: string;
|
|
34
|
+
questionType: QuestionType;
|
|
35
|
+
points: number;
|
|
36
|
+
pointsEarned: number;
|
|
37
|
+
selectedAnswer?: string | string[] | number[] | Record<string, string> | number;
|
|
38
|
+
correctAnswer?: string | string[] | Record<string, string>;
|
|
39
|
+
isCorrect: boolean;
|
|
40
|
+
explanation?: string;
|
|
41
|
+
hint?: string;
|
|
42
|
+
}
|
|
43
|
+
type AttemptStatus = "in_progress" | "completed" | "abandoned";
|
|
44
|
+
interface ExternalQuizAttempt {
|
|
45
|
+
id: string;
|
|
46
|
+
quizId: string;
|
|
47
|
+
lessonId: string;
|
|
48
|
+
assignLessonId: string;
|
|
49
|
+
courseId: string;
|
|
50
|
+
childId: string;
|
|
51
|
+
parentId: string;
|
|
52
|
+
attemptNumber: number;
|
|
53
|
+
status: AttemptStatus;
|
|
54
|
+
score?: number;
|
|
55
|
+
correctAnswers?: number;
|
|
56
|
+
totalQuestions: number;
|
|
57
|
+
answers: QuizAnswerDetail[];
|
|
58
|
+
startedAt: string;
|
|
59
|
+
completedAt?: string;
|
|
60
|
+
timeSpentSeconds?: number;
|
|
61
|
+
}
|
|
62
|
+
interface QuizPlayerProps {
|
|
63
|
+
quizId: string;
|
|
64
|
+
lessonId: string;
|
|
65
|
+
assignLessonId: string;
|
|
66
|
+
courseId: string;
|
|
67
|
+
childId: string;
|
|
68
|
+
parentId: string;
|
|
69
|
+
apiBaseUrl: string;
|
|
70
|
+
authToken?: string;
|
|
71
|
+
onComplete?: (result: QuizResult) => void;
|
|
72
|
+
onError?: (error: Error) => void;
|
|
73
|
+
onProgress?: (progress: QuizProgress) => void;
|
|
74
|
+
className?: string;
|
|
75
|
+
styles?: QuizPlayerStyles;
|
|
76
|
+
}
|
|
77
|
+
interface QuizResult {
|
|
78
|
+
attemptId: string;
|
|
79
|
+
score: number;
|
|
80
|
+
correctAnswers: number;
|
|
81
|
+
totalQuestions: number;
|
|
82
|
+
answers: QuizAnswerDetail[];
|
|
83
|
+
timeSpentSeconds: number;
|
|
84
|
+
}
|
|
85
|
+
interface QuizProgress {
|
|
86
|
+
currentQuestion: number;
|
|
87
|
+
totalQuestions: number;
|
|
88
|
+
answeredQuestions: number;
|
|
89
|
+
}
|
|
90
|
+
interface QuizPlayerStyles {
|
|
91
|
+
containerClassName?: string;
|
|
92
|
+
questionClassName?: string;
|
|
93
|
+
optionClassName?: string;
|
|
94
|
+
buttonClassName?: string;
|
|
95
|
+
progressClassName?: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
declare function QuizPlayer({ quizId, lessonId, assignLessonId, courseId, childId, parentId, apiBaseUrl, authToken, onComplete, onError, onProgress, className, }: QuizPlayerProps): react_jsx_runtime.JSX.Element;
|
|
99
|
+
|
|
100
|
+
interface ApiClientConfig {
|
|
101
|
+
baseUrl: string;
|
|
102
|
+
authToken?: string;
|
|
103
|
+
}
|
|
104
|
+
declare class QuizApiClient {
|
|
105
|
+
private baseUrl;
|
|
106
|
+
private authToken?;
|
|
107
|
+
constructor(config: ApiClientConfig);
|
|
108
|
+
private request;
|
|
109
|
+
getQuiz(quizId: string): Promise<Quiz>;
|
|
110
|
+
createAttempt(params: {
|
|
111
|
+
quizId: string;
|
|
112
|
+
lessonId: string;
|
|
113
|
+
assignLessonId: string;
|
|
114
|
+
courseId: string;
|
|
115
|
+
childId: string;
|
|
116
|
+
parentId: string;
|
|
117
|
+
}): Promise<ExternalQuizAttempt>;
|
|
118
|
+
updateAttempt(attemptId: string, data: {
|
|
119
|
+
answers?: QuizAnswerDetail[];
|
|
120
|
+
status?: 'in_progress' | 'completed' | 'abandoned';
|
|
121
|
+
score?: number;
|
|
122
|
+
correctAnswers?: number;
|
|
123
|
+
timeSpentSeconds?: number;
|
|
124
|
+
}): Promise<ExternalQuizAttempt>;
|
|
125
|
+
getAttempt(attemptId: string): Promise<ExternalQuizAttempt>;
|
|
126
|
+
getAttempts(params: {
|
|
127
|
+
assignLessonId?: string;
|
|
128
|
+
childId?: string;
|
|
129
|
+
quizId?: string;
|
|
130
|
+
}): Promise<ExternalQuizAttempt[]>;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
declare function checkAnswer(question: QuizQuestion, selectedAnswer: unknown): {
|
|
134
|
+
isCorrect: boolean;
|
|
135
|
+
pointsEarned: number;
|
|
136
|
+
};
|
|
137
|
+
declare function createAnswerDetail(question: QuizQuestion, selectedAnswer: unknown): QuizAnswerDetail;
|
|
138
|
+
declare function calculateScore(answers: QuizAnswerDetail[]): {
|
|
139
|
+
score: number;
|
|
140
|
+
correctAnswers: number;
|
|
141
|
+
totalPoints: number;
|
|
142
|
+
earnedPoints: number;
|
|
143
|
+
};
|
|
144
|
+
declare function formatTime(seconds: number): string;
|
|
145
|
+
|
|
146
|
+
export { type ApiClientConfig, type AttemptStatus, type ExternalQuizAttempt, type QuestionType, type Quiz, type QuizAnswerDetail, QuizApiClient, QuizPlayer, type QuizPlayerProps, type QuizPlayerStyles, type QuizProgress, type QuizQuestion, type QuizResult, calculateScore, checkAnswer, createAnswerDetail, formatTime };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
type QuestionType = "single" | "multiple" | "true-false" | "free" | "essay" | "fill" | "sorting" | "matrix" | "assessment";
|
|
4
|
+
interface QuizQuestion {
|
|
5
|
+
id: string;
|
|
6
|
+
question: string;
|
|
7
|
+
type: QuestionType;
|
|
8
|
+
points: number;
|
|
9
|
+
explanation?: string;
|
|
10
|
+
hint?: string;
|
|
11
|
+
imageUrl?: string;
|
|
12
|
+
options?: string[];
|
|
13
|
+
correctAnswer?: string | string[] | Record<string, string>;
|
|
14
|
+
blanks?: string[];
|
|
15
|
+
items?: string[];
|
|
16
|
+
correctOrder?: number[];
|
|
17
|
+
leftItems?: string[];
|
|
18
|
+
rightItems?: string[];
|
|
19
|
+
correctMatches?: Record<string, string>;
|
|
20
|
+
scaleType?: "likert" | "rating" | "yes-no";
|
|
21
|
+
scaleMin?: number;
|
|
22
|
+
scaleMax?: number;
|
|
23
|
+
}
|
|
24
|
+
interface Quiz {
|
|
25
|
+
id: string;
|
|
26
|
+
title: string;
|
|
27
|
+
grade: number;
|
|
28
|
+
questions: QuizQuestion[];
|
|
29
|
+
status: string;
|
|
30
|
+
}
|
|
31
|
+
interface QuizAnswerDetail {
|
|
32
|
+
questionId: string;
|
|
33
|
+
questionText: string;
|
|
34
|
+
questionType: QuestionType;
|
|
35
|
+
points: number;
|
|
36
|
+
pointsEarned: number;
|
|
37
|
+
selectedAnswer?: string | string[] | number[] | Record<string, string> | number;
|
|
38
|
+
correctAnswer?: string | string[] | Record<string, string>;
|
|
39
|
+
isCorrect: boolean;
|
|
40
|
+
explanation?: string;
|
|
41
|
+
hint?: string;
|
|
42
|
+
}
|
|
43
|
+
type AttemptStatus = "in_progress" | "completed" | "abandoned";
|
|
44
|
+
interface ExternalQuizAttempt {
|
|
45
|
+
id: string;
|
|
46
|
+
quizId: string;
|
|
47
|
+
lessonId: string;
|
|
48
|
+
assignLessonId: string;
|
|
49
|
+
courseId: string;
|
|
50
|
+
childId: string;
|
|
51
|
+
parentId: string;
|
|
52
|
+
attemptNumber: number;
|
|
53
|
+
status: AttemptStatus;
|
|
54
|
+
score?: number;
|
|
55
|
+
correctAnswers?: number;
|
|
56
|
+
totalQuestions: number;
|
|
57
|
+
answers: QuizAnswerDetail[];
|
|
58
|
+
startedAt: string;
|
|
59
|
+
completedAt?: string;
|
|
60
|
+
timeSpentSeconds?: number;
|
|
61
|
+
}
|
|
62
|
+
interface QuizPlayerProps {
|
|
63
|
+
quizId: string;
|
|
64
|
+
lessonId: string;
|
|
65
|
+
assignLessonId: string;
|
|
66
|
+
courseId: string;
|
|
67
|
+
childId: string;
|
|
68
|
+
parentId: string;
|
|
69
|
+
apiBaseUrl: string;
|
|
70
|
+
authToken?: string;
|
|
71
|
+
onComplete?: (result: QuizResult) => void;
|
|
72
|
+
onError?: (error: Error) => void;
|
|
73
|
+
onProgress?: (progress: QuizProgress) => void;
|
|
74
|
+
className?: string;
|
|
75
|
+
styles?: QuizPlayerStyles;
|
|
76
|
+
}
|
|
77
|
+
interface QuizResult {
|
|
78
|
+
attemptId: string;
|
|
79
|
+
score: number;
|
|
80
|
+
correctAnswers: number;
|
|
81
|
+
totalQuestions: number;
|
|
82
|
+
answers: QuizAnswerDetail[];
|
|
83
|
+
timeSpentSeconds: number;
|
|
84
|
+
}
|
|
85
|
+
interface QuizProgress {
|
|
86
|
+
currentQuestion: number;
|
|
87
|
+
totalQuestions: number;
|
|
88
|
+
answeredQuestions: number;
|
|
89
|
+
}
|
|
90
|
+
interface QuizPlayerStyles {
|
|
91
|
+
containerClassName?: string;
|
|
92
|
+
questionClassName?: string;
|
|
93
|
+
optionClassName?: string;
|
|
94
|
+
buttonClassName?: string;
|
|
95
|
+
progressClassName?: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
declare function QuizPlayer({ quizId, lessonId, assignLessonId, courseId, childId, parentId, apiBaseUrl, authToken, onComplete, onError, onProgress, className, }: QuizPlayerProps): react_jsx_runtime.JSX.Element;
|
|
99
|
+
|
|
100
|
+
interface ApiClientConfig {
|
|
101
|
+
baseUrl: string;
|
|
102
|
+
authToken?: string;
|
|
103
|
+
}
|
|
104
|
+
declare class QuizApiClient {
|
|
105
|
+
private baseUrl;
|
|
106
|
+
private authToken?;
|
|
107
|
+
constructor(config: ApiClientConfig);
|
|
108
|
+
private request;
|
|
109
|
+
getQuiz(quizId: string): Promise<Quiz>;
|
|
110
|
+
createAttempt(params: {
|
|
111
|
+
quizId: string;
|
|
112
|
+
lessonId: string;
|
|
113
|
+
assignLessonId: string;
|
|
114
|
+
courseId: string;
|
|
115
|
+
childId: string;
|
|
116
|
+
parentId: string;
|
|
117
|
+
}): Promise<ExternalQuizAttempt>;
|
|
118
|
+
updateAttempt(attemptId: string, data: {
|
|
119
|
+
answers?: QuizAnswerDetail[];
|
|
120
|
+
status?: 'in_progress' | 'completed' | 'abandoned';
|
|
121
|
+
score?: number;
|
|
122
|
+
correctAnswers?: number;
|
|
123
|
+
timeSpentSeconds?: number;
|
|
124
|
+
}): Promise<ExternalQuizAttempt>;
|
|
125
|
+
getAttempt(attemptId: string): Promise<ExternalQuizAttempt>;
|
|
126
|
+
getAttempts(params: {
|
|
127
|
+
assignLessonId?: string;
|
|
128
|
+
childId?: string;
|
|
129
|
+
quizId?: string;
|
|
130
|
+
}): Promise<ExternalQuizAttempt[]>;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
declare function checkAnswer(question: QuizQuestion, selectedAnswer: unknown): {
|
|
134
|
+
isCorrect: boolean;
|
|
135
|
+
pointsEarned: number;
|
|
136
|
+
};
|
|
137
|
+
declare function createAnswerDetail(question: QuizQuestion, selectedAnswer: unknown): QuizAnswerDetail;
|
|
138
|
+
declare function calculateScore(answers: QuizAnswerDetail[]): {
|
|
139
|
+
score: number;
|
|
140
|
+
correctAnswers: number;
|
|
141
|
+
totalPoints: number;
|
|
142
|
+
earnedPoints: number;
|
|
143
|
+
};
|
|
144
|
+
declare function formatTime(seconds: number): string;
|
|
145
|
+
|
|
146
|
+
export { type ApiClientConfig, type AttemptStatus, type ExternalQuizAttempt, type QuestionType, type Quiz, type QuizAnswerDetail, QuizApiClient, QuizPlayer, type QuizPlayerProps, type QuizPlayerStyles, type QuizProgress, type QuizQuestion, type QuizResult, calculateScore, checkAnswer, createAnswerDetail, formatTime };
|